今天看Spring官方文档,看到这一段话,看的一脸懵逼:
查了下百度,大概意思是:
在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使用时,调用的是代理对象自身的方法。
撒花花。浑身舒畅!!!