Spring AOP学习

Posted 记录点点滴滴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring AOP学习相关的知识,希望对你有一定的参考价值。

sSpring Aop术语

连接点(Joinpoint)

  连接点指的是程序执行的特点位置,如 类初始化前,类初始化后,某个方法调用的前后,方法抛出异常后等等。Spring 仅仅支持方法的连接点,即能在方法调用前,方法调用后,方法抛出异常时及方法调用前后 这些程序执行点植入增强逻辑。

切点(Pointcut)

  切点描述的是一系列连接点的集合。    

增强(Advice)

  增强指的是植入目标连接点上的一段代码,增强除了描述一段代码还包含连接点的方位信息,如BeforeAadvice,ThrowsAdvice,AfterReturnAdvice等等

目标对象(Target)

  指的是增强逻辑植入的目标类

引介(Introdution)

  引介是一种特殊的增强(Advice),它为类添加一些属性和方法,这样 即使一个类没有实现某个接口,aop引介也可以动态的加入接口和接口实现方法。让类成为某个接口的实现类。

织入(weaving)

  织入是将增强(Advice)加入到具体连接点的过程,aop有3种织入方式,编译期织入,类加载织入,动态代理织入。Spring aop采用动态代理织(JDK动态代理,CGLib动态代理)入 在运行期为目标类添加增强生成子类的方式,而AspectJ采用编译期织入和类加载织入。

代理(Proxy)

  一个类被aop织入增强后,就产生了一个结果类,这个结果类融合的原类和增强逻辑的代理类。

切面(Aspect)

  切面是由切点和增加组成,它既包含横切逻辑,也包含连接点的定义,Spring Aop就是负责实施切面的工作。

  JDK动态代理

  jdk动态代理是java在1.3的时候提供的技术,它允许开发者在运行时创建接口的代理实例,jdk动态代理主要涉及到Proxy类和 InvocationHandler接口,

下面使用动态代理实现在被代理对象执行的每一个方法前后都输出1和2,

public interface Car {
    
    public void run();
    
    public void whistle();

}
public class SmallCar implements Car{

    @Override
    public void run() {
        System.out.println("我是小车,我在执行run方法");
    }

    @Override
    public void whistle() {
        System.out.println("我是小车,我在执行whistle方法");
    }

}
public class BigCar implements Car{

    @Override
    public void run() {
        System.out.println("我是大车,我在执行run方法");        
    }

    @Override
    public void whistle() {
        System.out.println("我是大车,我在执行run方法");        
    }

}
public class CarHandler<T> implements InvocationHandler{
    private T target;//这个是被代理对象目标
    public CarHandler(T target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //proxy 是最终生成的代理对象, 一般我们不会用到。method是目标的方法,args 是方法入参
        System.out.println(1);
        Object result = method.invoke(target, args);//调用用反射调用目标的方法
        System.out.println(2);
        return result;
    }

}
public class TestCar {
    public static void main(String[] args) {
        Car smallCar = new SmallCar();
        CarHandler<Car> handle = new CarHandler<Car>(smallCar);
        Car proxyCar = (Car) Proxy.newProxyInstance(smallCar.getClass().getClassLoader(), smallCar.getClass().getInterfaces(), handle);
        proxyCar.run();
        proxyCar.whistle();
        
        Car bigCar = new BigCar();
        CarHandler<Car> handle1 = new CarHandler<Car>(bigCar);
        Car proxyCar1 = (Car) Proxy.newProxyInstance(bigCar.getClass().getClassLoader(), bigCar.getClass().getInterfaces(), handle1);
        proxyCar1.run();
        proxyCar1.whistle();
    }
}

输出结果

1
我是小车,我在执行run方法
2
1
我是小车,我在执行whistle方法
2
1
我是大车,我在执行run方法
2
1
我是大车,我在执行run方法
2

 CGLib代理

  jdk动态代理有一个限制,它只能为接口创建代理实例,从Proxy.newProxyInstance就能看出来,CGLib采用底层字节码技术,可以为一个类创建子类,在子类使用方法拦截拦截所有父类方法并顺势加入增强代码

public class SmallCar implements Car{

    @Override
    public void run() {
        System.out.println("我是小车,我在执行run方法");
    }

    @Override
    public void whistle() {
        System.out.println("我是小车,我在执行whistle方法");
    }

}
import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {
    
    private Enhancer enhancer = new Enhancer();
    
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(1);
        Object result = proxy.invokeSuper(obj, args);
        System.out.println(2);
        return result;
    }
    
}
public class CglibProxyTest {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        SmallCar smallCar = (SmallCar) cglibProxy.getProxy(SmallCar.class);
        smallCar.run();
        smallCar.whistle();
    }
}

输出结果

1
我是小车,我在执行run方法
2
1
我是小车,我在执行whistle方法
2

动态代理和CGLib代理小结

  研究表明CGLib所创建的对象的性能比jdk动态代理创建的对象性能高很多(大概10倍),但是CGLib创建代理对象所花费的时间比jdk动态代理多(大概8倍),

所以对于singleton的代理对象或者具有实例池的代理对象,因为无需频繁的创建对象,所以应该采用CGLib技术,反之应该采用jdk动态代理技术,此外CGLib技术也有一定的限制:不能对目标中的final或者private方法进行代理。

  上面两个代理的例子存在3个问题

    1 目标类的所有方法都被增加了我们的增强业务,而有时候我们只需要对特定的方法添加横向逻辑,

    2 通过硬编码的方式在目标业务方法前后加入增强业务

    3 代理的创建过程是手写的,在为不同类创建代理时 无法做到通用性。

  Spring AOP的主要工作就是围绕以上3点来开展的

  AOP联盟为增强Advice,以下aoppalliance为aop联盟定义的接口,spring为spring定义扩展的增强接口,Spring 支持5种类型的增强。

前置增强:MethodBeforeAdvice,在目标方法执行前实施增强

后置增强:AfterReturningAdvice,在目标方法执行后实施增强

环绕增强:MethodInterceptor,在目标方法执行前后实施增强

异常增强:ThrowsAdvice,在目标方法抛出异常后实施增强

引介增强:IntroductionInterceptor,在目标类添加一些新的属性和方法

   前置增强,

public interface Waiter {
    public void greetTo(String name);
    public void serverTo(String name);
}
public class NaiveWaiter implements Waiter{
    
    public void greetTo(String name) {
        System.out.println("欢迎"+name);
    }
    
    public void serverTo(String name) {
        System.out.println("服务"+name);
    }
}
import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class GreetingBeforeAdvice implements MethodBeforeAdvice {

    //在目标方法执行前执行
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        String clinetName = (String) args[0];
        System.out.println("您好,"+clinetName);
    }

}
public class BeforeTest {
    public static void main(String[] args) {
        Waiter target = new NaiveWaiter();
        BeforeAdvice advice  = new GreetingBeforeAdvice();
        //Spirng  提供的代理工厂
        ProxyFactory pf = new ProxyFactory();
//        pf.setInterfaces(target.getClass().getInterfaces()); //这里是指定使用接口代理(jdk动态代理)
        pf.setOptimize(true);//启用代理优化 如果这里开启那么ProxyFactory还有可以使用Cglib进行代理
        //设置代理目标
        pf.setTarget(target);
        //为代理目标增强 这里也可以添加多个增强,形成增强链,他们的调用顺序和添加顺序保持一致,当然也可以指定添加顺序
        pf.addAdvice(advice);
        //生成代理实例
        Waiter proxy = (Waiter) pf.getProxy();
        proxy.greetTo("张三");
        proxy.serverTo("李四");
    }
}

输出结果

您好,张三
欢迎张三
您好,李四
服务李四

 后置增强

public class GreetingAfterAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("请享用您的实物");
    }
    
}
    public static void main(String[] args) {
        Waiter target = new NaiveWaiter();
        AfterReturningAdvice advice  = new GreetingAfterAdvice();
        //Spirng  提供的代理工厂
        ProxyFactory pf = new ProxyFactory();
//        pf.setInterfaces(target.getClass().getInterfaces()); //这里是指定使用接口代理(jdk动态代理)
        pf.setOptimize(true);//启用代理优化 如果这里开启那么ProxyFactory还有可以使用Cglib进行代理
        //设置代理目标
        pf.setTarget(target);
        //为代理目标增强 这里也可以添加多个增强,形成增强链,他们的调用顺序和添加顺序保持一致,当然也可以指定添加顺序
        pf.addAdvice(advice);
        //生成代理实例
        Waiter proxy = (Waiter) pf.getProxy();
        proxy.serverTo("李四");
    }

 环绕增强

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class GreetingMehodInterceptor implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args = invocation.getArguments();//目标方法入参
        String clientName = (String) args[0];
        System.out.println(clientName+":吃饭前,请擦嘴");
        Object result  = invocation.proceed();//使用反射机制调用目标类的方法
        System.out.println(clientName+":吃饭后,请擦嘴");
        return result;
    }

}
    public static void main(String[] args) {
        Waiter target = new NaiveWaiter();
        GreetingMehodInterceptor advice  = new GreetingMehodInterceptor();
        //Spirng  提供的代理工厂
        ProxyFactory pf = new ProxyFactory();
//        pf.setInterfaces(target.getClass().getInterfaces()); //这里是指定使用接口代理(jdk动态代理)
        pf.setOptimize(true);//启用代理优化 如果这里开启那么ProxyFactory还有可以使用Cglib进行代理
        //设置代理目标
        pf.setTarget(target);
        //为代理目标增强 这里也可以添加多个增强,形成增强链,他们的调用顺序和添加顺序保持一致,当然也可以指定添加顺序
        pf.addAdvice(advice);
        //生成代理实例
        Waiter proxy = (Waiter) pf.getProxy();
        proxy.serverTo("李四");
    }

输出结果

李四:吃饭前,请擦嘴
服务李四
李四:吃饭后,请擦嘴

异常抛出增强

  异常增强适合事物管理,发生异常时触发事物回滚

public class OrderServiceImpl {
    
    public void testTransationManager()throws Exception {
        System.out.println("-----开始业务操作--------");
        System.out.println("插入数据1");
        throw new SQLException("模拟的sql异常出现");
    }
}
public class TransationManager implements ThrowsAdvice{
    public void afterThrowing(Method method,Object[] args,Object target,Exception e) {
        System.out.println("----------------");
        System.out.println("method:"+method.getName());
        System.out.println("抛出异常:"+e.getMessage());
        System.out.println("事物已经被回滚");
    }
}
    public static void main(String[] args) throws Exception{
        OrderServiceImpl target = new OrderServiceImpl();
        TransationManager advice  = new TransationManager();
        //Spirng  提供的代理工厂
        ProxyFactory pf = new ProxyFactory();
//        pf.setInterfaces(target.getClass().getInterfaces()); //这里是指定使用接口代理(jdk动态代理)
        pf.setOptimize(true);//启用代理优化 如果这里开启那么ProxyFactory还有可以使用Cglib进行代理
        //设置代理目标
        pf.setTarget(target);
        //为代理目标增强 这里也可以添加多个增强,形成增强链,他们的调用顺序和添加顺序保持一致,当然也可以指定添加顺序
        pf.addAdvice(advice);
        //生成代理实例
        OrderServiceImpl proxy = (OrderServiceImpl) pf.getProxy();
        proxy.testTransationManager();
    }

输出结果

-----开始业务操作--------
插入数据1
----------------
method:testTransationManager
抛出异常:模拟的sql异常出现
事物已经被回滚
Exception in thread "main" java.sql.SQLException: 模拟的sql异常出现
    at com.bujiang.magic.springAOP.OrderServiceImpl.testTransationManager(OrderServiceImpl.java:10)
    at com.bujiang.magic.springAOP.OrderServiceImpl$$FastClassBySpringCGLIB$$7ad45694.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:127)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
    at com.bujiang.magic.springAOP.OrderServiceImpl$$EnhancerBySpringCGLIB$$d3d9cdd2.testTransationManager(<generated>)
    at com.bujiang.magic.springAOP.BeforeTest.main(BeforeTest.java:69)

引介增强

  引介增强是一种特殊的增强,他与上面的几种都不一样。他是为目标类建立新的属性和方法,通过引介增强,原来的目标类未实现某个接口,引介增强可以为目标类实现创建实现类的接口代理,这种功能非常富有吸引力,因为能在横向的定义接口的实现方法。Spring 定义了引介增强接口

public interface Monitorable {
    //这个方法控制监控的开关
    void setMonitorActive(boolean active);
}
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

public class ControllableMonitor  extends DelegatingIntroductionInterceptor implements Monitorable{
    //ThreadLocal这里是保存监控开关的状态,
    //之所以是使用ThreadLocal 是因为使代理类非线程安全的类变成线程安全的类 通过ThreadLocal使每个线程都单独使用一个状态,因此是线程安全的
    private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<>();

    @Override
    public void setMonitorActive(boolean active) {
        MonitorStatusMap.set(active);        
    }
    
    public Object invoke(MethodInvocation mi) throws Throwable{
        Object result =null;
        //根据监控状态,来确定是否开启监控行为
        if(MonitorStatusMap.get()!=null&&MonitorStatusMap.get()) {
            System.out.println("开始监控");
            result = super.invoke(mi);
            System.out.println("监控完毕");
        }else {
            result = super.invoke(mi);
        }
        return result;
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byName">
    <bean id="pmonitor" class="com.bujiang.magic.t1.ControllableMonitor"   />
    <bean id="carTarget" class="com.bujiang.magic.proxy.BigCar"   />
    <bean id="car" class="org.springframework.aop.framework.ProxyFactoryBean"
                    p:interfaces="com.bujiang.magic.t1.Monitorable"
                    p:target-ref= "carTarget"
                    p:interceptorNames="pmonitor"
                    p:proxyTargetClass="true" />
</beans>
public class Test {
    public static void main(String[] args) {
        String path = "com/bujiang/magic/beans.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
        Car car =  (Car) applicationContext.getBean("car");
        car.run();
        System.out.println("-----------------开关开启,再次执行-----------------");
        Monitorable monitor = (Monitorable) car;
        monitor.setMonitorActive(true);
        car.run();
    }
}

输出结果

 我是大车,我在执行run方法
-----------------开关开启,再次执行-----------------
开始监控
我是大车,我在执行run方法
监控完毕

 切面

  Spring 提供的切面使用起来比较繁琐,需要实现专门的接口,并进行复杂的配置,这里不做介绍,我们可以使用基于@AspectJ实现的AOP的功能

public class Waiter {
    public void greetTo(String name) {
        System.out.println("欢迎"+name);
    }
    
    public void serverTo(String name) {
        System.out.println("服务"+name);
    }
}
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//标注为切面类
@Aspect
public class PreGreetAspect {
    
    //前置增强 Aspect有自己的语法,这里不做介绍,具体可百度
    @Before("execution(* greetTo(..))")
    public void beforeGreetingAspect() {
        System.out.println("你好");
    }
    
    //后置增强
    @After("execution(* serverTo(..))")
    public void afterGreetingAspect() {
        System.out.println("再见");
    }
}
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

public class Test {
    public static void main(String[] args) {
        Waiter target = new Waiter();
        AspectJProxyFactory factory =  new AspectJProxyFactory();
        factory.setTarget(target);//设置目标对象
        factory.addAspect(PreGreetAspect.class);//添加切面
        Waiter proxy = factory.getProxy();//生成代理对象
        proxy.greetTo("张三");
        System.out.println("--------------------");
        proxy.serverTo("李四");
    }
}

输出结果

你好
欢迎张三
--------------------
服务李四
再见

   其他 如:@AfterReturning @Around @AfterThrowing @After 等等与前面的spring AOP作用一致,这里不做介绍,引介增强 @DeclareParents

  283页

  

以上是关于Spring AOP学习的主要内容,如果未能解决你的问题,请参考以下文章

spring aop中this和target区别

spring学习笔记AOP

Spring框架学习05——AOP相关术语详解

18spring注解学习(AOP)——AOP功能测试

SpringBoot AOP学习:Spring AOP实现日志功能

Spring AOP官方文档学习笔记之AOP概述