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

发布于 2022-01-04 18:30:25 字数 2411 浏览 969 评论 7

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

无边思念无边月 2022-01-08 06:34:43

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

爱你是孤单的心事 2022-01-08 06:28:58

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

甜扑 2022-01-08 06:18:07

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

偏爱自由 2022-01-08 04:20:13

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

已下线请稍等 2022-01-08 00:43:46

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

正题:

       大家都知道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

 

墨洒年华 2022-01-07 14:26:17

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

嘦怹 2022-01-07 01:37:19

你的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管理;

 

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文