SpringAOP-JDK 动态代理和 CGLIB 代理

Posted 妮蔻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringAOP-JDK 动态代理和 CGLIB 代理相关的知识,希望对你有一定的参考价值。

在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。

1.JDK 动态代理

那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl)、代理对象($Proxy0)三者具体关系可以使用下图表示:

正如上图可知 JDK 动态代理是对接口进行的代理;代理类实现了接口,并继承了 Proxy 类;目标对象与代理对象没有什么直接关系,只是它们都实现了接口,并且代理对象执行方法时候内部最终是委托目标对象执行具体的方法。

 

示例如下:

JDK 代理是对接口进行代理,所以首先写一个接口类:

public interface UserService {

    public  int add();
}

 

然后实现该接口如下:

public class UserServiceImpl implements UserService {

    @Override
    public int add() {
        System.out.println("执行add方法");
        return 0;
    }
}

JDK 动态代理是需要实现 InvocationHandler 接口,因此创建一个 InvocationHandler 的实现类:

public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1
        System.out.println("********begin "+method.getName()+"********");
        //2
        Object result = method.invoke(target, args);
        //3
        System.out.println("********end "+method.getName()+"*********");
        return result;
    }

    public Object getProxy(){
        //4
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }

}

 

接着进行测试,代码如下:

public static void main(String[] args) {

  //5打开这个开关,可以把生成的代理类保存到磁盘
 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
  //6创建目标对象(被代理对象)
  UserService service = new UserServiceImpl();
  //7创建一个InvocationHandler实例,并传递被代理对象
  MyInvocationHandler handler = new MyInvocationHandler(service);
  //8生成代理类
  UserService proxy = (UserService) handler.getProxy();
  proxy.add();
}

其中代码6 创建了一个 UserServiceImpl 的实例对象,这个对象就是要被代理的目标对象。

代码7 创建了一个 InvocationHandler 实例,并传递被代理目标对象 service 给内部变量 target。

代码8 调用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一个代理对象。

代码5 设置系统属性变量 sun.misc.ProxyGenerator.saveGeneratedFiles 为 true,这是为了让代码(8)生成的代理对象的字节码文件保存到磁盘。

 

接着我们进行反编译看核心的代码,运行后会在项目的 com.sun.proxy 下面会生成 $Proxy0.class 类,经反编译后核心 Java 代码如下:

package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo;

public final class $Proxy0
  extends Proxy
  implements UserService
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;

  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  public final int add()
  {
    try
    {
    //9第一个参数是代理类本身,第二个是实现类的方法,第三个是参数
      return h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

 ...省略

  static
  {
    try
    {

      m3 = Class.forName("proxy.JDK.UserService").getMethod("add", new Class[0]);
    ...省略
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    ...省略
  }
}

可以看到代理类 $Proxy0 中 代码9  中的 h 就是 main 函数里面创建的 MyInvocationHandler 的实例,h.invoke(this, m3, null) 实际就是调用的 MyInvocationHandler 的 invoke 方法,

而后者则是委托给被代理对象进行执行,这里可以对目标对象方法进行拦截,然后对其功能进行增强。另外 代码8 生成的 proxy 对象实际就是 $Proxy0 的一个实例,当调用 proxy.add() 时候,

实际是调用的代理类 $Proxy0 的 add 方法,后者内部则委托给 MyInvocationHandler 的 invoke 方法,invoke 方法内部有调用了目标对象 service 的 add 方法。

 

因此,JDK 动态代理机制总结如下:

JDK 动态代理机制只能对接口进行代理,其原理是动态生成一个代理类,这个代理类实现了目标对象的接口,目标对象和代理类都实现了接口,但是目标对象和代理类的 Class 对象是不一样的,所以两者是没法相互赋值的。

 

 

CGLIB 动态代理

相比 JDK 动态代理对接口进行代理,CGLIB 则是对实现类进行代理,这意味着无论目标对象是否有接口,都可以使用 CGLIB 进行代理。

那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl),代理对象(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a)三者具体关系可以使用下图表示:

可知接口和代理对象没有啥关系,代理对象是继承了目标对象和实现了 Factory 接口。

例子如下:

使用 CGLIB 进行代理需要实现 MethodInterceptor,创建一个方法拦截器 CglibProxy 类:

 

public class CglibProxy implements MethodInterceptor {
    //10
    private Enhancer enhancer = new Enhancer();
    //11
    public Object getProxy(Class clazz) {
        //12 设置被代理类的Class对象
        enhancer.setSuperclass(clazz);
        //13 设置拦截器回调
        enhancer.setCallback( this);
        return enhancer.create();
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(obj.getClass().getName()+"."+method.getName());
       Object result = proxy.invokeSuper(obj, args);

        return result;
    }
}

测试类如下:

public void testCglibProxy() {

//14 生成代理类到本地
 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");
    //15 生成目标对象
    UserServiceImpl service = new UserServiceImpl();
    //16 创建CglibProxy对象
    CglibProxy cp = new CglibProxy();
    //17 生成代理类
    UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass());
    proxy.add();
}

 

执行上面代码会在 /Users/huangjuncong/Downloads 目录生成代理类 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class 文件,反编译后部分代码如下:

 

public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
  implements Factory
{
static void CGLIB$STATICHOOK1()
  {
    //18 空参数
    CGLIB$emptyArgs = new Object[0];

    //19 获取UserServiceImpl的add方法列表
    Method[] tmp191_188 = ReflectUtils.findMethods(new String[] { "add", "()I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods());
    CGLIB$add$0$Method = tmp191_188[0];

    //20  创建CGLIB$add$0,根据创建一个MethodProxy
    CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "add", "CGLIB$add$0");

  }
  static
  {
    CGLIB$STATICHOOK1();
  }
  //(21)
  final int CGLIB$add$0()
  {
    return super.add();
  }
  //(22)
  public final int add()
  {
    ...省略
    //23 获取拦截器,这里为CglibProxy的实例
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    { 
    //24 调用拦截器的intercept方法 Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy); tmp36_31; return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue(); } return super.add(); } }

代码18 创建了一个 CGLIB$emptyArgs,因为 add 方法是无入参的,所以这里创建的 Object 对象个数为0,这对象是 CglibProxy 拦截器的 intercept 的第三个参数。

代码19 获取 UserServiceImpl 的 add 方法列表,并把唯一方法赋值给 CGLIB$add$0$Method,这个对象是 CglibProxy 拦截器的 intercept 第二个参数。

代码20 创建了一个 MethodProxy 对象,当调用 MethodProxy 对象的 invokeSuper 方法时候会直接调用代理对象的 CGLIB$add$0 方法,也就是直接调用父类 UserServiceImpl 的 add 方法,这避免了一次反射调用,创建的 MethodProxy 对象是 CglibProxy 拦截器的 intercept 的第四个参数。

代码22 是代理类的 add 方法,代码(17)生成的代理对象 proxy 就是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a 的一个实例,当调用 proxy.add() 方法时候就是调用的代码(22),从代码(22)可知内部调用了拦截器 CglibProxy 的 intercept 方法,可知 intercept 的第一个参数就是代理对象本身。

 

  因此CGLIB代理的总结如下:

    CGLIB 是对目标对象本身进行代理,所以无论目标对象是否有接口,都可以对目标对象进行代理,其原理是使用字节码生成工具在内存生成一个继承目标对象的代理类,然后创建代理对象实例。

    由于代理类的父类是目标对象,所以代理类是可以赋值给目标对象的,自然如果目标对象有接口,代理对象也是可以赋值给接口的。

    CGLIB 动态代理中生成的代理类的字节码相比 JDK 来说更加复杂。

 

总的来说

  JDK 使用反射机制调用目标类的方法,CGLIB 则使用类似索引的方式直接调用目标类方法,所以 JDK 动态代理生成代理类的速度相比 CGLIB 要快一些,但是运行速度比 CGLIB 低一些,并且 JDK 代理只能对有接口的目标对象进行代理。

以上是关于SpringAOP-JDK 动态代理和 CGLIB 代理的主要内容,如果未能解决你的问题,请参考以下文章

jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)

jdk动态代理和cglib动态代理的区别

03动态代理--JDK动态代理和CGLib动态代理的组合实例

CGlib和JDK动态代理

SSMJDK动态代理和Cglib动态代理

final类又没实现接口应该用哪一种代理, jdk动态代理还是cglib代理