Cglib与JDK动态代理的区别

  • JDK代理只能针对实现了接口的类以反射的方式生成代理,而不能针对类
  • CGLIB是针对类实现代理的,主要对指定的类以字节码转换的方式(ASM框架)生成一个子类,并重写其中的方法。
       因为是创建目标类的子类,所以目标类必须要有无参构造函数(子类的有参构造函数会调用父类无参),否则报错
    另外因为是继承,所以我们的目标类最好不要使用Final声明

Spring如何切换不同的代理

1、如果目标对象实现了接口,默认会采用JDK的动态代理机制实现AOP,但是可以强制使用CGLIB实现AOP ;
    缺点:必须实现接口,并且生成的代理对象也只能声明成为其中一个接口,其他接口的方法和自己的方法访问不到。
2、如果目标对象没有实现接口,必须使用CGLIB生成代理,spring会自动在CGLIB和JDK动态代理之间切换 。

通过注解 @EnableAspectJAutoProxy(proxyTargetClass=true, exposeProxy=true)启动cglib动态代理
proxyTargetClass() default false:启用cglib代理,proxyTargetClass默认为false
exposeProxy() default false: 如果在一个方法中,调用内部方法,需要在调用内部方法时也能够进行代理,比如内部调用时,使用(IService)AopContext.currentProxy().sayHello(),需要将exposeProx设置为true,默认为false

动态代理的原理

参考

  • ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);通过该方法生成代理类的二进制文件
  • 调用native Class<?> defineClass0(ClassLoader loader, String name,byte[] b, int off, int len)方法,将二进制文件生成Class类
  • final Constructor<?> cons = cl.getConstructor(constructorParams);获取构造函数
  • cons.newInstance(new Object[]{h});生成对应的实例
  • 生成的代理类对应的Class文件,在该方法中调用父类Proxy中的h(即自定的Handler)的invoke方法;该方法的入参为
    • this: 生成的该proxy类的实例
    • m3: 欲代理接口对应的method,具体为静态代码块中的m3 = Class.forName("com.lanhuigu.spring.proxy.jdk.IHello").getMethod("sayHello", new Class[0]);
    • null: 该参数为执行目标方法的参数,因该方法无入参,故此处为null,如果有的话,会以new Object[] { }的形式传入
public final void sayHello()
            throws  {
        try {
            this.h.invoke(this, m3, null);
            return;
        } catch (RuntimeException localRuntimeException) {
            throw localRuntimeException;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
  • 通过上述步骤即执行了自定义Handler的invoke方法;在该方法中可在执行目标方法前后,执行欲抛入的功能;通过method.invoke(target,args);执行代理的目标类的方法
public class MyInvocationHandler implements InvocationHandler {
 
    /** 目标对象:即接口的实现类,欲代理的目标类 */
    private Object target;
 
    public MyInvocationHandler(Object target){
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------插入前置通知代码-------------");
        // 执行相应的目标方法
        Object rs = method.invoke(target,args);
        System.out.println("------插入后置处理代码-------------");
        return rs;
    }
}
  • 如果直接代理无实现类的某接口,则在Handler中因无目标实现类,故不需要调用method.invoke(target,args),直接插入功能后返回即可;那如何操作代理类Proxy中的toString等方法如何执行?因为在动态代理类的toString中完全调用h.invoke()方法,在handler的invoke()中只插入代码未执行method.invoke(target,args),如何正常的获取返回值,可从spring容器中获取一下feign的代理类,调用下toString方法,查看下结果
    FeignClientFactoryBean:https://blog.csdn.net/qq_39470742/article/details/83583635

4f404047-3086-4bab-b7e2-5e83ce660282

在执行feign等代理方法时,会判断是否是hashcode或equals,toString方法,如果是的话,直接在此处执行相应的代码

public final String toString()
    throws 
  {
    try
    {
      return ((String)this.h.invoke(this, m2, null));
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
}

System.getSecurityManager();