Spring AOP

一、AOP术语

AOP(面向切面编程)技术是OOP(面向对象编程)技术的补充,OOP无法解决分散组件调用公共业务的解耦问题。利用AOP,将分布在多个组件中的公共业务模块化,并把它们声明式地应用在需要它们的地方,使得那些组件可以更加专注于自身核心业务。

1、切面

切点和通知的结合体,定义通知应该应用到哪些切入点上。

2、连接点

应用执行过程中可以插入切面的点。

3、切点

定义了切面何处工作。匹配通知所要应用的一个或多个连接点。

4、通知

切面所要完成的功能叫做通知,定义了切面是什么以及何时工作。一般有5种类型的通知:

Before:在方法调用之前

After:在方法调用之后,无论方法是否成功

After-returning:在方法成功之后

After-throwing:方法抛出异常之后

Around:在方法调用之前和之后执行自定义的行为

5、织入:

将切面应用到目标对象的过程


二、AOP实现底层机制

1、JDK动态代理

  通过proxy类和InvocationHandler接口创建代理对象,要求被代理的目标对象实现一个接口,底层通过反射技术实现。每个代理对象都有一个与之关联的InvocationHandler对象,当调用代理对象的方法时,会被InvocationHandler对象的invoke方法代替。

看一个简单的例子,声明一个A类并实现ExInterface接口,利用JDK动态代理技术在execute()执行前后织入权限验证和日志记录,注意这里仅是演示代码并不代表实际应用。

/**
 * Created by zejian on 2017/2/11.
 * Blog : http://blog.youkuaiyun.com/javazejian [原文地址,请尊重原创]
 */
//自定义的接口类,JDK动态代理的实现必须有对应的接口类
public interface ExInterface {
    void execute();
}

//A类,实现了ExInterface接口类
public class A implements ExInterface{
    public void execute(){
        System.out.println("执行A的execute方法...");
    }
}
//代理类的实现
public class JDKProxy implements InvocationHandler{

    /**
     * 要被代理的目标对象
     */
    private A target;

    public JDKProxy(A target){
        this.target=target;
    }

    /**
     * 创建代理类
     * @return
     */
    public ExInterface createProxy(){
        return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    /**
     * 调用被代理类(目标对象)的任意方法都会触发invoke方法
     * @param proxy 代理类
     * @param method 被代理类的方法
     * @param args 被代理类的方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //过滤不需要该业务的方法
        if("execute".equals(method.getName())) {
            //调用前验证权限
            AuthCheck.authCheck();
            //调用目标对象的方法
            Object result = method.invoke(target, args);
            //记录日志数据
            Report.recordLog();
            return result;
        }eles if("delete".equals(method.getName())){
            //.....
        }
        //如果不需要增强直接执行原方法
        return method.invoke(target,args);
    }
}

 //测试验证
 public static void main(String args[]){
      A a=new A();
      //创建JDK代理
      JDKProxy jdkProxy=new JDKProxy(a);
      //创建代理对象
      ExInterface proxy=jdkProxy.createProxy();
      //执行代理对象方法
      proxy.execute();
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

运行结果:

这里写图片描述

在A的execute方法里面并没有调用任何权限和日志的代码,也没有直接操作a对象,相反地只是调用了proxy代理对象的方法,最终结果却是预期的,这就是动态代理技术,是不是跟Spring AOP似曾相识?实际上动态代理的底层是通过反射技术来实现,只要拿到A类的class文件和A类的实现接口,很自然就可以生成相同接口的代理类并调用a对象的方法了,关于底层反射技术的实现,暂且不过多讨论,请注意实现java的动态代理是有先决条件的,该条件是目标对象必须带接口,如A类的接口是ExInterface,通过ExInterface接口动态代理技术便可以创建与A类类型相同的代理对象,如下代码演示了创建并调用时利用多态生成的proxy对象:

 A a=new A();
 //创建JDK代理类
 JDKProxy jdkProxy=new JDKProxy(a);
 //创建代理对象,代理对象也实现了ExInterface,通过Proxy实现
 ExInterface proxy=jdkProxy.createProxy();
 //执行代理对象方法
 proxy.execute();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

代理对象的创建是通过Proxy类达到的,Proxy类由Java JDK提供,利用Proxy#newProxyInstance方法便可以动态生成代理对象(proxy),底层通过反射实现的,该方法需要3个参数

/**
* @param loader 类加载器,一般传递目标对象(A类即被代理的对象)的类加载器
* @param interfaces 目标对象(A)的实现接口
* @param h 回调处理句柄(后面会分析到)
*/
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

创建代理类proxy的代码如下:

public ExInterface createProxy(){
   return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

到此并没结束,因为有接口还是远远不够,代理类(Demo中的JDKProxy)还需要实现InvocationHandler接口,也是由JDK提供,代理类必须实现的并重写invoke方法,完全可以把InvocationHandler看成一个回调函数(Callback),Proxy方法创建代理对象proxy后,当调用execute方法(代理对象也实现ExInterface)时,将会回调InvocationHandler#invoke方法,因此我们可以在invoke方法中来控制被代理对象(目标对象)的方法执行,从而在该方法前后动态增加其他需要执行的业务,Demo中的代码很好体现了这点:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //过滤不需要该业务的方法
    if("execute".equals(method.getName())) {
        //调用前验证权限(动态添加其他要执行业务)
        AuthCheck.authCheck();

        //调用目标对象的方法(执行A对象即被代理对象的execute方法)
        Object result = method.invoke(target, args);

        //记录日志数据(动态添加其他要执行业务)
        Report.recordLog();

        return result;
    }eles if("delete".equals(method.getName())){
     //.....
     return method.invoke(target, args);
    }
    //如果不需要增强直接执行原方法
    return method.invoke(target,args);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

invoke方法有三个参数:

  • Object proxy :生成的代理对象
  • Method method:目标对象的方法,通过反射调用
  • Object[] args:目标对象方法的参数

这就是Java JDK动态代理的代码实现过程,小结一下,运用JDK动态代理,被代理类(目标对象,如A类),必须已有实现接口如(ExInterface),因为JDK提供的Proxy类将通过目标对象的类加载器ClassLoader和Interface,以及句柄(Callback)创建与A类拥有相同接口的代理对象proxy,该代理对象将拥有接口ExInterface中的所有方法,同时代理类必须实现一个类似回调函数的InvocationHandler接口并重写该接口中的invoke方法,当调用proxy的每个方法(如案例中的proxy#execute())时,invoke方法将被调用,利用该特性,可以在invoke方法中对目标对象(被代理对象如A)方法执行的前后动态添加其他外围业务操作,此时无需触及目标对象的任何代码,也就实现了外围业务的操作与目标对象(被代理对象如A)完全解耦合的目的。当然缺点也很明显需要拥有接口,这也就有了后来的CGLIB动态代理了

2、CGLIB动态代理

  首先需要实现方法拦截器接口MethodInterceptor接口,重写intercept方法,代理对象的创建通过Enhancer类来实现。

  通过CGLIB动态代理实现上述功能并不要求目标对象实现一个接口,实际上CGLIB动态代理是通过继承的方式实现的,因此可以减少没必要的接口,下面直接通过简单案例协助理解。

//被代理的类即目标对象
public class A {
    public void execute(){
        System.out.println("执行A的execute方法...");
    }
}

//代理类
public class CGLibProxy implements MethodInterceptor {

    /**
     * 被代理的目标类
     */
    private A target;

    public CGLibProxy(A target) {
        super();
        this.target = target;
    }

    /**
     * 创建代理对象
     * @return
     */
    public A createProxy(){
        // 使用CGLIB生成代理:
        // 1.声明增强类实例,用于生产代理类
        Enhancer enhancer = new Enhancer();
        // 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
        enhancer.setSuperclass(target.getClass());
        // 3.//设置回调函数,即一个方法拦截
        enhancer.setCallback(this);
        // 4.创建代理:
        return (A) enhancer.create();
    }

    /**
     * 回调函数
     * @param proxy 代理对象
     * @param method 委托类方法
     * @param args 方法参数
     * @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
     *                    methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   //过滤不需要该业务的方法
      if("execute".equals(method.getName())) {
          //调用前验证权限(动态添加其他要执行业务)
          AuthCheck.authCheck();

          //调用目标对象的方法(执行A对象即被代理对象的execute方法)
          Object result = methodProxy.invokeSuper(proxy, args);

          //记录日志数据(动态添加其他要执行业务)
          Report.recordLog();

          return result;
      }else if("delete".equals(method.getName())){
          //.....
          return methodProxy.invokeSuper(proxy, args);
      }
      //如果不需要增强直接执行原方法
      return methodProxy.invokeSuper(proxy, args);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

从代码看被代理的类无需接口即可实现动态代理,而CGLibProxy代理类需要实现一个方法拦截器接口MethodInterceptor并重写intercept方法,类似JDK动态代理的InvocationHandler接口,也是理解为回调函数,同理每次调用代理对象的方法时,intercept方法都会被调用,利用该方法便可以在运行时对方法执行前后进行动态增强。关于代理对象创建则通过Enhancer类来设置的,Enhancer是一个用于产生代理对象的类,作用类似JDK的Proxy类,因为CGLib底层是通过继承实现的动态代理,因此需要传递目标对象(如A)的Class,同时需要设置一个回调函数对调用方法进行拦截并进行相应处理,最后通过create()创建目标对象(如A)的代理对象,运行结果与前面的JDK动态代理效果相同。

public A createProxy(){
    // 1.声明增强类实例,用于生产代理类
    Enhancer enhancer = new Enhancer();
    // 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
    enhancer.setSuperclass(target.getClass());
    // 3.设置回调函数,即一个方法拦截
    enhancer.setCallback(this);
    // 4.创建代理:
    return (A) enhancer.create();
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

关于JDK代理技术 和 CGLIB代理技术到这就讲完了,我们也应该明白 Spring AOP 确实是通过 CGLIB或者JDK代理 来动态地生成代理对象,这个代理对象指的就是 AOP 代理类,而 AOP 代理类的方法则通过在目标对象的切入点动态地织入增强处理,从而完成了对目标方法的增强。

三、Spring中的事务

Spring中的事务管理基于AOP实现。事务管理通常有两种方式:

编程式事务管理

推荐使用TransactionTemplate实现,其封装了打开和关闭资源等常用重复代码,符合Spring的模板模式。使用时需要在Spring的配置文件中声明事务管理器和定义TransactionTemplate模板。

声明式事务管理

通过AOP技术实现事务管理,在使用声明式事务时不用编写任何代码即可实现基于容器的事务管理。声明式的唯一不足在于其最细粒度最多到方法级别,无法像编程式那样到代码块级别。常用的是基于@Transactional的声明式事务管理,当作用于方法上时,其本质是对目标方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

在Spring中常使用TransactionProxyFactoryBean完成声明式事务管理,在使用时在配置文件中定义数据源DataSource和事务管理器,该管理器被注入到TransactionProxyFactoryBean中,设置代理对象和事务属性。目标对象的定义以内部类方式定义。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值