Spring AOP的实现原理

来源:javazejian

Spring AOP的实现原理是基于动态织入的动态代理技术而AspectJ则是静态织入(所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强),而动态代理技术又分为Java JDK动态代理和CGLIB动态代理,前者是基于反射技术的实现,后者是基于继承的机制实现,下面通过一个简单的例子来分析这两种技术的代码实现。

JDK动态代理

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

/**
 * Created by zejian on 2017/2/11.
 * Blog : http://blog.csdn.net/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();
  }

运行结果:

这里写图片描述

在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();

代理对象的创建是通过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)

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

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

到此并没结束,因为有接口还是远远不够,代理类(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);
}

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动态代理了

CGLIB动态代理

通过CGLIB动态代理实现上述功能并不要求目标对象拥有接口类,实际上CGLIB动态代理是通过继承的方式实现的,因此可以减少没必要的接口(不能对final修饰的方法进行处理),下面直接通过简单案例协助理解(CGLIB是一个开源项目,github网址是:https://github.com/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);

    }
}

从代码看被代理的类无需接口即可实现动态代理,而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();
  }

关于JDK代理技术 和 CGLIB代理技术到这就讲完了,我们也应该明白 Spring AOP 确实是通过 CGLIB或者JDK代理 来动态地生成代理对象,这个代理对象指的就是 AOP 代理类,而 AOP 代理类的方法则通过在目标对象的切入点动态地织入增强处理,从而完成了对目标方法的增强。这里并没有非常深入去分析这两种技术,更倾向于向大家演示Spring AOP 底层实现的最简化的模型代码,Spring AOP内部已都实现了这两种技术,Spring AOP 在使用时机上也进行自动化调整,当有接口时会自动选择JDK动态代理技术,如果没有则选择CGLIB技术,当然Spring AOP的底层实现并没有这么简单,为更简便生成代理对象,Spring AOP 内部实现了一个专注于生成代理对象的工厂类,这样就避免了大量的手动编码,这点也是十分人性化的,但最核心的还是动态代理技术。从性能上来说,Spring AOP 虽然无需特殊编译器协助,但性能上并不优于AspectJ的静态织入,这点了解一下即可。最后给出Spring AOP简化的原理模型如下图,附属简要源码,【GITHUB源码下载】

此条目发表在Java基础分类目录,贴了标签。将固定链接加入收藏夹。

发表评论

邮箱地址不会被公开。 必填项已用*标注