下面的代码,照着复制就能跑起来
今天看了下Spring的@Configuration,即java类配置bean,(这个spring3的新功能,虽然现在已经spring5了,但是这种配置bean的方式也是比较火的)
做了如下测试,发现一个现象,先说这个现象,后面用自己的理解再简单实现一下。
先说现象:
在java配置类中加@Configuration,下面的声明bean的方法,就只会被调一次,也就是初始化的时候,哪怕是下面的方法直接互相引用,返回的new的对象的构造方法也只会调一次
而如果不加@Configuration,那么下面的方法如果有相互调用,那么返回的new的对象的构造方法就会被调多次
下面是测试代码:
@Configuration
@ComponentScan("com.zs.cglib")
//这个类作为配置类
public class CglibConfig {@Beanpublic TestDomain testDomain(){return new TestDomain();}@Beanpublic 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<>();@Overridepublic 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(){//看到这个,应该就要想到cglibEnhancer 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中,
后面再有调用,就直接返回结果了,不会真正再去执行了