cglib动态代理
最近在写aop时遇到一个问题:被cglib代理的类注解丢失了;
举个列子:现在有一个Model类,@Component,@Autowired,@Value是自定义的注解
@Component
public class Model{
@Autowired
private User user;
@Value("model-1")
private String modelName;
@Value("TYPE-1")
private String modelType;
}
//cglib代理
public <T>T create(Object target){
Enhancer enhancer = new Enhancer();
enhancer.setSuperClass(target.getClass());//设置父类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj,args);
}
});
return (T)enhancer.create();
}
现在Model的一个对象model,通过自定义的ioc容器给model对象的三个字段注解解析之后注入值,在cglib动态代理生成的新对象后,这三个字段的值均为null;
Model modelProxy = create(model);
modelProxy 这个对象中,三个字段的值都是null;
原因是什么呢?
cglib会利用代理对象生成一个子类;其实modelProxy 就是生成子类的一个对象,而不是原Model的对象;
由Model类经过cglib生成的子类
public class Model$$EnhancerByCGLIB$$eb117e56 extends Model implements Factory {
private MethodInterceptor CGLIB$CALLBACK_0;
private static final MethodProxy CGLIB$getName$1$Proxy;
public final String getName() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$getName$1$Method, CGLIB$emptyArgs, CGLIB$getName$1$Proxy) : super.getName();
}
...
...
...
modelProxy 其实是 “Model\$$EnhancerByCGLIB\$$eb117e56”类的对象;
我们可以看到modelProxy 的生成过程中,只使用了model对象提供的class信息,用来作为代理类的父类;
新生成的对象modelProxy 和model对象本身没有关系,因此之前在model对象上赋予的字段属性值,在新生成的对象modelProxy 上当然是null,因为根本就没有在新生成的对象上重新赋值;
原因找到了,想要给modelProxy 的User,modelName,modelType属性赋值怎么办呢?
这就简单了,因为 modelProxy 对象是 Model对象的子类生成的对象,想要给父类的字段赋值,只需要利用反射找到父类,获取字段,以及字段的注解,然后就可以通过反射来给父类的字段注入值了;
Field[] fields = modelProxy.getClass().getSuperclass().getDeclaredFields();===>获取到Model类的所有字段;
for(Field field :fields){
field.setAccessible(true);
Value value = field.getAnnotation(Value.class);//获取字段的Value注解,Autowired注解类似;
if(value != null){
String value = value.value();//获取注解值
field.set(modelProxy,value);//给字段设置值,在这设置值之前还要判断字段的类型。。这细节就不展开了
}
}
JDK代理
在使用2种代理的时候发现cglib新生成的代理类会丢失掉注解,而使用Proxy生成的代理类还能使用被代理类的注解,为什么呢?
我们可以通过查看动态生成的代理类源码;
准备的被代理类:U2 和接口: T:
public interface T{}
public class U2 implements T{
private String name;
public void t1(){
System.out.println(getClass().getSimpleName()+";method=t1");
}
public void t2()
{
t3();
System.out.println(getClass().getSimpleName()+";method=t2");
}
public void t3(){
t1();
System.out.println(getClass().getSimpleName()+";method=t3");
}
public void t4(String arg){
System.out.println("*******************************");
System.out.println("this method not aop...."+name);
System.out.println("*******************************");
}
}
JDK动态代理:
public class JDKProxy implements InvocationHandler {
private Object target;//目标类
public JDKProxy(Object taregt){
this.target = target;
}
public <T>T newInstance(){
return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnVal=null;
//beforeMethod
returnVal= method.invoke(target,args);
//afterMethod
...
...
return returnVal;
}
}
动态生成的代理类代码:
public final class MyProxy extends Proxy implements T {
private static Method m1;
private static Method m4;
private static Method m6;
private static Method m2;
private static Method m5;
private static Method m3;
private static Method m0;
public MyProxy(InvocationHandler var1) throws {
super(var1);
}
public final void t4(String var1) throws {
try {
super.h.invoke(this, m6, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void t3() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void t2() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void t1() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m6 = Class.forName("parse.aop.T").getMethod("t4", Class.forName("java.lang.String"));
m4 = Class.forName("parse.aop.T").getMethod("t3");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m5 = Class.forName("parse.aop.T").getMethod("t2");
m3 = Class.forName("parse.aop.T").getMethod("t1");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们通过jdk动态代理生成的类就张这个样子;通过代码我们可以看到,生成的代理类继承了Proxy类并且实现了被代理类的接口;因此通过Proxy生成的代理类实际上都是Proxy的子类;
回到我们关注的问题:为什么通过JDK动态代理生成的代理类还可以使用被代理类的注解?
我们看所有的方法调用都是通过下列形式:
super.h.invoke(this, 方法, 参数);
我们知道代理类的父类的是Proxy,也就是说super.h ===> Proxy.h;
我们查看Proxy的h字段,实际是一个InvocationHandler 类型:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
再来看看使用Proxy生成代理类调用的newProxyInstance:
//参数类型
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
//实际传递参数:
Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);// JDKProxy
有3个参数,最后一个参数类型是:InvocationHandler ,我们传的是this,也就是JDKProxy 的一个对象;
super.h.invoke(this, 方法, 参数) <===> JDKProxy.invoke(this,方法, 参数);
我们在JDKProxy的invoke方法的方法调用:method.invoke(target,args);==>实际的方法调对象用是:target
因此,使用jdk代理,新生成的代理对象看起来没有丢失目标类的注解;
我们通过上面的分析不难看出,为什么cglib的注解丢失而JDK动态代理的没有丢失?
因为cglib新生成的代理对象和原target对象没有任何关联,并且cglib生成的对象也没有处理注解,因此直接使用cglib生成对象,注解就会丢失;而JDK新生成的对象对原target对象保持引用,因而不会丢失掉注解;
spring事务@Transactional注解在同一个类中的方法之间调用不生效的原因
问题:
在Spring项目中,方法A使用了Transactional注解。但当同一个class中的方法B调用方法A时,会发现方法A中的异常不再导致回滚,也即事务失效了。
public class Test implements XXservice{
@Transanctional
public void A(){}
public void B(){
A();
}
}
答:可以在Test中注解自己:@Autowired注解自己,再用注解的对象调用就可以解决问题;
public class Test implements XXservice{
@Autowired
private XXservice test;
@Transactional
public void A(){}
public void B(){
test.A();
}
}
为什么这样使用可以解决问题呢?
// 没有查看源码,但是可以通过原理分析一下原因。
在spring实际运行时,Test会被动态代理生成一个类似于TestProxy的代理类,这个类会将使用了注解@Transactional添加事务处理;而没有使用的不会添加事务处理;
Test类在实际运行时,是使用的TestProxy的对象;因此使用了注解@Autowired的test对象其实是TestProxy的类的对象;
因此调用过程可以简化成下图: