下面的代码,照着复制就能跑起来
今天看了下Spring的@Configuration,即java类配置bean,(这个spring3的新功能,虽然现在已经spring5了,但是这种配置bean的方式也是比较火的)
做了如下测试,发现一个现象,先说这个现象,后面用自己的理解再简单实现一下。
先说现象:
在java配置类中加@Configuration,下面的声明bean的方法,就只会被调一次,也就是初始化的时候,哪怕是下面的方法直接互相引用,返回的new的对象的构造方法也只会调一次
而如果不加@Configuration,那么下面的方法如果有相互调用,那么返回的new的对象的构造方法就会被调多次
下面是测试代码:
@Configuration
@ComponentScan("com.zs.cglib")
//这个类作为配置类
public class CglibConfig {
@Bean
public TestDomain testDomain(){
return new TestDomain();
}
@Bean
public TestDomainTwo testDomainTwo(){
//这个方法会预先调用上一个方法
testDomain();
return new TestDomainTwo();
}
}
@Component("testDomain")
public class TestDomain {
public TestDomain() {
//构造参数打印,证明被调过
System.out.println("new TestDomain-------------");
}
}
@Component("testDomainTwo")
public class TestDomainTwo {
public TestDomainTwo() {
//构造参数打印,证明被调过
System.out.println("new TestDomainTwo-------------");
}
}
public class StartMain {
//启动测试
public static void main(String[] args) {
AnnotationConfigApplicationContext anno = new AnnotationConfigApplicationContext(CglibConfig.class);
System.out.println(anno.getBean(TestDomain.class));
System.out.println(anno.getBean(TestDomainTwo.class));
}
}
可以发现,如果CglibConfig加上@Configuration,就会打印出:
new TestDomain-------------
new TestDomainTwo-------------
如果把@Configuration去掉,就会打印出:
new TestDomain-------------
new TestDomain-------------
new TestDomainTwo-------------
也就是说,加上@Configuration,new出TestDomain实例只执行了一次,也就是说testDomainTwo()中调用的testDomain(),并没有new出新的TestDomain实例。
而把@Configuration去掉,TestDomain实例就会被new两次,也就是testDomainTwo()中调用的testDomain()也有new出TestDomain实例。
这是为什么呢?当然,肯定和@Configuration有关。
一般情况,我们把带有@Configuration的类叫做全注解配置类,也叫Full配置类;
我们把不带@Configuration的类叫Lite配置类;
源码解释:
追了下源码,一直找到org.springframework.context.annotation.ConfigurationClassPostProcessor#enhanceConfigurationClasses
突然看到enhancer,这不是cglib的东西么,根据这个线索再追,就知道了,加上@Configuration,其实是用了Cglib代理了
所以方法,已经被增强了,那肯定还有其他逻辑,
再找到org.springframework.context.annotation.ConfigurationClassEnhancer#newEnhancer
这就是cglib了,那就找callback,再找intercept方法,org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept
这个方法里面增强了原方法,其实就是用map实现的,下面来个简单的模仿
根据cglib实现简单的效果
亲自写了个简单的cglib实现,还原了@Configuration的这种现象
测试代码如下:
CglibConfig这个类,把注解都去掉,咱们自己实现这个只调一次的功能
public class CglibConfig {
public TestDomain testDomain(){
return new TestDomain();
}
public TestDomainTwo testDomainTwo(){
testDomain();
return new TestDomainTwo();
}
}
TestDomain和TestDomainTwo两个类不变
新增一个callback,实现以下逻辑。这都是cglib的知识点,不动可以查下cglib简单实现:
public class MyCallBack implements MethodInterceptor {
//这个map就记录了方法每次调用的痕迹,并把调用后的结果保存起来,不是第一次调用的话,就直接将结果返回就行了
private static Map<String,Object> map = new HashMap<>();
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//获取方法名字
String name = method.getName();
if(isFirst(name)){
//如果方法是第一次调用
Object invoke = methodProxy.invokeSuper(o, objects);
//调用完将结果保存在map中
map.put(name,invoke);
return invoke;
}else{
//第二次,第三次调用,就走这里,直接返回map中的结果
return map.get(name);
}
}
private boolean isFirst(String name) {
//判断是不是第一次调用,其实就是看这个name在map中是不是已经注册了
Object invoke = map.get(name);
if(invoke == null){
return true;
}
return false;
}
}
再写个cglib的util,以便main方法调用
public class CglibUtil {
public static Object getBean(){
//看到这个,应该就要想到cglib
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibConfig.class);
enhancer.setCallback(new MyCallBack());
CglibConfig proxy= (CglibConfig) enhancer.create();
return proxy;
}
}
//主方法测试
public class StartMain {
public static void main(String[] args) {
CglibConfig bean = (CglibConfig) CglibUtil.getBean();
bean.testDomain();
bean.testDomainTwo();
}
}
执行的结果是:
new TestDomain-------------
new TestDomainTwo-------------
总结
利用cglib代理增强,
如果这个方法第一次调用,就把调用的方法名和返回的结果保存在map中,
后面再有调用,就直接返回结果了,不会真正再去执行了