Spring 官方文档阅读疑问一

 

今天看Spring官方文档,看到这一段话,看的一脸懵逼:

Spring 官方文档阅读疑问一_第1张图片

 

查了下百度,大概意思是:

在Component中使用@Bean注解和在@Configuration中使用@Bean注解是不同的。在@Component类中使用方法或字段时不会使用CGLIB 代理。而在@Configuration类中使用方法或字段时则使用CGLIB创造代理对象;当调用@Bean注解的方法时它不是普通的Java语义,而是从容器中拿到由Spring生命周期管理、被Spring代理甚至依赖于其他Bean的对象引用。在@Component中调用@Bean注解的方法和字段则是普通的Java语义,不经过CGLIB处理。

 

还是一脸懵逼???讲的什么???

然后又是一顿搜索,查资料,自己大概试了下。

场景:首先是有TestService TestDao两个类需要实例化,并且TestService需要依赖TestDao

1.@Configuration中定义@Bean

@Configuration
public class Config {
    @Bean
    public TestService testService() {
        return new TestService(testDao());
    }
    @Bean
    public TestDao testDao() {
        return new TestDao();
    }
}

2.@Component中定义@Bean

@Component
public class TestBean {
    @Bean
    public TestService testService() {
        return new TestService(testDao());
    }
    @Bean
    public TestDao testDao() {
        return new TestDao();
    }
}

这里有个疑问:

TestService中依赖TestDao,使用的是调用testDao()方法,那么testService里面依赖的testDao会是@Bean实例化的那个testDao吗?

因为,根据自己的实际使用情况,基本是使用第一种(@Configuration中定义@Bean),所以姑且猜测testDao为同一个实例。

实际运行下:

第一种结果:TestDao的构造方法只打印了一次

TestDao create...

第二种结果:

TestDao create...
TestDao create...

尝试结论如下:

@Configuration修饰的Config类,本身实例化的时候,会创建一个代理对象,然后实例化@Bean修饰的TestService时,调用代理对象的testService()方法,此时触发调用代理对象的testDao()方法,猜测此时会触发getBean("testDao")先获取TestDao的实例,然后把实例传给 new TestService();

而,@Compoent修饰的Config类,实例化testServce时触发的testDao()方法调用,只是纯粹执行testDao()方法,也就是new TestDao(),该对象并不会被容器管理生命周期。


现在又产生一个问题:

这段代码,相当于调用this.testDao(),为什么会调用代理对象的testDao()???

 new TestService(testDao());

根据实际经验,比如下面这段代码,是不会走代理的啊。

// 异步未生效,没有走代理
public void method(){
    System.out.println("method()......");
    this.aysncMethod(); 
}
​
@Async
public void aysncMethod(){
    System.out.println("aysncMethod()......");
}

难道我的经验有误?

又拿出项目跑了一把,确实异步未生效。

懵逼 +2 !!!

 

把CGLIB调用的代码拿出来,一顿调试

被代理的类如下:

public class TestBean {
    public void method1() {
        System.out.println("TestBean method1...");
        method2();
    }
    public void method2() {
        System.out.println("TestBean method2...");
    }
}

测试代码:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestBean.class);
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before method run...");
        Object result = proxy.invokeSuper(obj, args); // @1
        System.out.println("after method run...");
        return result;
    }
});
TestBean sample = (TestBean) enhancer.create();
sample.method1();

输出:

before method run...
TestBean method1...
before method run...
TestBean method2...
after method run...
after method run...

结论:@1,proxy.invokeSuper(obj, args),这里调用的是CGLIB对象的方法,此时,在TestBean中断点的话,this对象为CGLIB生成的对象。所以结果是method1(),method2()都是会被拦截使用。

换一种写法,此时调用目标对象的方法:

测试代码:

TestBean testBean = new TestBean();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestBean.class);
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before method run...");
        Object result = method.invoke(testBean, args);  // @2
        System.out.println("after method run...");
        return result;
    }
});
TestBean sample = (TestBean) enhancer.create();
sample.method1();

输出:

before method run...
TestBean method1...
TestBean method2...
after method run...

结论: @2,method.invoke(testBean, args); 这里直接调用目标对象的方法,此时,在TestBean中断点的话,this对象一定为手动new TestBean()产生的对象。

 

总结:结合以前的AOP知识,使用SpringAOP的时候,代理对象内部会调用目标对象的方法。即 method.invoke(target,args);而@Configuration中@Bean使用时,调用的是代理对象自身的方法。

 

撒花花。浑身舒畅!!!

 

你可能感兴趣的:(Spring 官方文档阅读疑问一)