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
在执行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);
}
}