cglib新生成的对象注解丢失原因及解决办法

本文深入探讨了CGLib动态代理与JDK动态代理的区别,特别是在处理注解和事务方面。在CGLib代理中,由于生成的是目标类的子类,注解信息丢失,而JDK代理则通过接口实现,能保留注解。针对Spring事务@Transactional注解在同一类内调用失效的问题,提出了通过@Autowired自我注入以解决。
摘要由CSDN通过智能技术生成

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的类的对象;
因此调用过程可以简化成下图:

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值