从spring源码角度分析@Configuration和@Component区别

很多人都用过@Configuration和@Component,但并不一定了解他们的区别,或者所了解到的区别仅限于理论层面,并不知道真实原因,最近本人在学习spring 5.2.x源码,特记录并分享一下。
首先,我们可以看到@Configuration的代码是这样的:
在这里插入图片描述
从图中可以看出,@Configuration这个类是加上了@Component注解的,所以姑且认为@Component有的功能他都有,但是今天的主题是讨论他们不一样的地方。首先通过代码直接测试,然后再分析源码。测试代码如下:

============================= JavaConfig =====================================

package org.springframework.francis.config;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan("org.springframework.francis.bean")
public class JavaConfig {

}

================================ A ==========================================

package org.springframework.francis.bean;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class A {
    @Bean
	public B b() {
		System.out.println("+++++++++++++++++");
		return new B();
	}

	@Bean
	public C c() {
		System.out.println(b());
		return new C();
	}

}

=================================== B =======================================

package org.springframework.francis.bean;

public class B {
}

================================== C ========================================

package org.springframework.francis.bean;

public class C {
}

================================ Test =======================================

package org.springframework.francis;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.francis.bean.A;
import org.springframework.francis.bean.B;
import org.springframework.francis.config.JavaConfig;


public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
        JavaConfig javaConfig = applicationContext.getBean("javaConfig", JavaConfig.class);
        System.out.println(javaConfig);

        A a = applicationContext.getBean("a", A.class);
        System.out.println(a);

        B b = applicationContext.getBean("b", B.class);
        System.out.println(b);
    }

}

当A.class类上面加的注解为@Configuration时,运行结果如下:
在这里插入图片描述
从图中可以看出,A的地址中包含A$$EnhancerBySpringCGLIB,这说明A类是用spring的cglib代理生成的,然后b()中的+++++++只打印一次,且两次打印B的地址都是一样的,说明B是单例的。

然后,当A.class类上面加的注解为@Component时,运行结果如下:
在这里插入图片描述
从图中可以看出,A的地址并没有$$EnhancerBySpringCGLIB字样,b()中的+++++++打印了两次,且两次打印B的地址不一样,这说明这个过程中创建了两个B对象。

从两个截图我们可以很明显的看出他们的区别,而且像

    @Bean
	public C c() {
		System.out.println(b());
		return new C();
	}

这种场景在开发中是比较常见的,比如我们自定义数据源的时候,经常会看到类似的写法:

    @Bean(name = "masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean(name = "masterTransactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        return new DataSourceTransactionManager(masterDataSource());
    }

所以遇到类似场景我们就知道该用哪个注解了,而并不是去靠猜和试。而且也不需要这么写:

    @Bean(name = "masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Autowired
    private DataSource  masterDataSource;
    
    @Bean(name = "masterTransactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        return new DataSourceTransactionManager(masterDataSource);
    }

下面我们从源码角度分析为什么会有这种区别,这时候需要将A.class的注解改为@Configuration,然后debug。

我们先直接断点到ConfigurationClassPostProcessor.java的enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory)方法,第一次走第一个for循环时如图:
在这里插入图片描述
然后,我们一直走for循环,直到beanName为a时,我们再看截图:在这里插入图片描述
接着,我们继续往下走:
在这里插入图片描述
一直执行完第一个for循环,进入第二个for循环,如图:
在这里插入图片描述
从几个图中我们可以看出 configClassAttr=full 的才会存入 configBeanDefs 这个map中,在map中的AbstractBeanDefinition才会进入第二个for循环并执行cglib动态代理。进入enhancer.enhance() 后,会调用到ConfigurationClassEnhancer.java的内部类BeanMethodInterceptor的isMatch方法,具体如下图:
在这里插入图片描述
然后我们再看下图:
在这里插入图片描述
这一段注释说明会处理加了@Bean的注解,具体就不分析了,我们姑且先认为加了@Configuration注解的类中加了@Bean注解的方法只会生成单例对象,当然事实也是如此。

下面我们再看为什么 ConfigurationClassPostProcessor.java的enhanceConfigurationClasses()中A 的 configClassAttr = full,我们在ConfigurationClassUtils.java的checkConfigurationClassCandidate()中断点,下面提供一个调用栈,读者就可以很快知道为什么会在这里断点,如图:
在这里插入图片描述
我们可以看到这个方法中有对加了@Configuration注解的处理,当前beanClass为A时,正好满足下图中 if 的逻辑
在这里插入图片描述
到这里,相信大家应该明白spring在启动过程中是怎么处理加了@Configuration注解以及@Configuration和@Component的区别了。
最近看了一下官方文档,其实官方文档对此也有说明:
在这里插入图片描述
同时,如果不想使用@Configuration,官方 也说明了可以用@Component来代替,但是需要使用构造注入或方法注入,如图:
在这里插入图片描述
示例:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章