为什么通过spring的cglib代理类无法取到被代理对象的public成员属性?
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创建的代理却无法取到被代理类的成员属性呢?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
以前对spring熟悉一点,现在帮不了了。~~
我用的也是spring boot,service没有实现接口的情况下默认是用的cglib的方法创建代理的。
springboot没有碰到,我记得springboost也是用cglib,spring区分是不是接口选择,我记得是;
这样就解释通了为什么在构造方法打日志,但是spring创建代理的时候没有打日志。因为绕过了构造器
研究了一下源码,和最优解答有点出入,贴主就当随便看看,开拓下思路。
正题:
大家都知道Spring在面没有接口的类实现代理时,使用了CGLIB,具体可以定位到ObjenesisCglibAopProxy类的createProxyClassAndInstance方法,在这个方法里,通过Enhancer构造了proxy class,然后使用Objenesis 使用proxy class构造一个proxy instance。截图如下:
Enhancer这个大家都明白,是CGLIB动态生成类的一个入口,网上也有很多相关教程,那么Objenesis是什么?
Objenesis是一个轻量级的类库,作用是绕过一个构造器创建一个实例。结合贴主出现的问题,那么就有方法解释了:Spring使用Objenesis构造这个代理实例时,并没有通过构造器初始化父类对象以及相关属性,自然str属性也为空了。
验证:
对比验证以下两种方式:
部分核心代码如下:
输出结果:
结论:
运行环境:
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
应该连MyService类的默认构造方法也没有执行。因为试过在MyService类的默认构造方法中打印日志、打断点、给成员属性赋值,均没效
你的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管理;