Spring的@Configuration使用cglib代理的效果和我自己写的简单实现

下面的代码,照着复制就能跑起来

今天看了下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中,
后面再有调用,就直接返回结果了,不会真正再去执行了

猜你喜欢

转载自blog.csdn.net/java_zhangshuai/article/details/86603683