为什么通过spring的cglib代理类无法取到被代理对象的public成员属性?

陈祖煌 发布于 01/22 23:17
阅读 307
收藏 2

service层:

@Service
public class MyService {
    public String str = new String("hello world");
    
    @Transactional(rollbackFor = Throwable.class)
    public void test() {
       XXXX
    }
}

controller层:

@Controller
public class MyController {
    @Autowired
    private MyService myService;
    
    @RequestMapping("/test")
    public String test() {
       log.info("{}", myService.str); // 输出null

       XXXX
    }
}

在控制器MyController的test方法中,获取不到myService.str的值。

由于MyService类使用了@Transactional开启了事务,所以spring默认会通过cglib创建了一个代理子类(MyService$$EnhancerBySpringCGLIB$$XXXX)代理MyService的行为,在MyController通过@Autowired注入的myService对象实际是cglib动态生成的MyService子类(代理类)对象。因为代理类是MyService的子类,那么被注入的myService对象的父类MyService的str成员属性也应该被实例化才对,为什么在controller中取到的是null呢?

 

难道是cglib生成的代理类取不到父类的成员属性?

于是自己用cglib创建代理类试了一下:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyProxyFactory implements MethodInterceptor {
    //维护目标对象
    private Object target;

    public MyProxyFactory(Object target) {
        this.target = target;
    }

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);
        return returnValue;
    }
}
public class SuperA {
    public String str = new String("hello world");

    public static void main(String[] args) {
        SuperA superA = new SuperA();
        SuperA proxy = (SuperA) new MyProxyFactory(superA).getProxyInstance();
        log.info("proxy: {}", proxy.str);  // 输出"hello world"
    }
}

 

发现自己通过cglib创建的代理对象是能够取得到父类的成员属性的。

既然自己通过cglib创建的代理对象是能够取得到父类的成员属性,那么为什么spring通过cglib创建的代理却无法取到被代理类的成员属性呢?

以下是问题补充:

@陈祖煌:反编译cglib创建出来的代理类,发现里面也没有覆盖被代理类(父类)的成员属性。 (01/23 12:39)
加载中
0
码晒客
码晒客

你的Controller中通过Spring注入的myService对象是cglib生成的代理对象,并且代理对象的父类对象MyService也是Spring生成的,即通过BeanUtils.initiateClass(Constructor)生成的,只执行了MyService类的默认构造函数,这两个对象都没有通过new指令去获得;所以也就无法按照JVM正常的对象初始化顺序得到对象;

jvm在执行正常的new指令时才会按照类和对象的初始化顺序进行对象的初始化,即

1.加载父类(初始化static属性,赋默认值,执行static块)

2.加载子类(初始化static属性,赋默认值,执行static块)

3.初始化父类对象(初始化非static属性,赋默认值,执行instance块,执行构造函数)

4.初始化子类对象(初始化非static属性,赋默认值,执行instance块,执行构造函数);

你的测试代码中之所以能取到父类的public成员,是因为你在用cglib的api创建代理对象的时候手动传入了父类对象,new SuperA();所以jvm执行了正常的对象初始化过程;

-----------------------------分割线

这也就很好的解释了Spring的设计理念,代码中除了POJO等数据承载对象,其他的对象都应该从Spring容器中获取,包括你代码中的new String("hello world");也是对象,你一旦自己手动new,它就不受Spring管理;

 

陈祖煌
陈祖煌
应该连MyService类的默认构造方法也没有执行。因为试过在MyService类的默认构造方法中打印日志、打断点、给成员属性赋值,均没效
2
black-star
black-star

研究了一下源码,和最优解答有点出入,贴主就当随便看看,开拓下思路。

正题:

       大家都知道Spring在面没有接口的类实现代理时,使用了CGLIB,具体可以定位到ObjenesisCglibAopProxy类的createProxyClassAndInstance方法,在这个方法里,通过Enhancer构造了proxy class,然后使用Objenesis 使用proxy class构造一个proxy instance。截图如下:

Enhancer这个大家都明白,是CGLIB动态生成类的一个入口,网上也有很多相关教程,那么Objenesis是什么?

Objenesis是一个轻量级的类库,作用是绕过一个构造器创建一个实例。结合贴主出现的问题,那么就有方法解释了:Spring使用Objenesis构造这个代理实例时,并没有通过构造器初始化父类对象以及相关属性,自然str属性也为空了。

验证:

对比验证以下两种方式:

  1. 使用enhancer构造出来的proxy instance
  2. 使用Objenesis+Cglib构造出来的proxy instance

部分核心代码如下:

//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(MyService.class);
en.setCallbackType(MyMethodInterceptor.class);
Class proxyClass = en.createClass();
//使用cglib构造代理对象并输出str
System.out.println("cglib:"+((MyService) en.create()).str);
//使用objenesis+cglib构造代理对象
Objenesis objenesis = new ObjenesisStd(true);
Object proxyInstance = objenesis.newInstance(proxyClass);
System.out.println("cglib+objenesis:"+((MyService) proxyInstance).str);

输出结果:

cglib:hello world
cglib+objenesis:null

结论:

  1. 使用cglib构造出来的代理对象依然使用了构造器
  2. objenesis可以绕过构造器创建实例,但父类的相关属性可能为空

运行环境:

win8+idea2018.3+jdk8+SpringBoot1.4.6.RELEASE

参考地址:

objenesis官方:http://objenesis.org/

objenesis入门教程:https://yq.aliyun.com/ziliao/264583

cglib及其基本使用:https://www.cnblogs.com/xrq730/p/6661692.html

 

陈祖煌
陈祖煌
这样就解释通了为什么在构造方法打日志,但是spring创建代理的时候没有打日志。因为绕过了构造器
0
JPer
JPer

springboot没有碰到,我记得springboost也是用cglib,spring区分是不是接口选择,我记得是;

陈祖煌
陈祖煌
我用的也是spring boot,service没有实现接口的情况下默认是用的cglib的方法创建代理的。
0
大东哥
大东哥

以前对spring熟悉一点,现在帮不了了。~~

返回顶部
顶部