环境:Spring6.1.2
1. 简介
@Configuration与@Component在Spring框架中各自具有不同的作用和使用场景。
@Component是Spring中的一个通用注解,用于标识一个类作为组件。它告诉Spring这是一个普通的组件类,应该被Spring容器管理。当Spring启动时,它会扫描应用程序的类路径,并自动检测带有@Component注解的类。这些类随后会被Spring容器实例化并注册为Bean。
@Configuration用于标识配置类,这些类不同于普通的组件类,它主要包含了Bean的定义。在配置类中,可以使用@Bean注解来定义Bean的创建方法。Spring容器在启动时,会调用这些带有@Bean注解的方法,并将返回的对象注册为Bean。通过@Configuration,可以更加灵活和细粒度地控制Bean的创建和配置,从而实现更复杂的依赖注入和组件装配。
2. 两者区别
2.1 环境准备
public class PersonDAO {
}
public class PersonService{
}
public class AppConfig {
@Bean
public PersonDAO personDAO() {
return new PersonDAO() ;
}
@Bean
public PersonService personService() {
return new PersonService() ;
}
}
以上会基于上面的几个类进行测试
2.2 区别1
AppConfig使用@Configuration注解
@Configuration
public class AppConfig {
// ...
}
// 测试代码
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println(context.getBean(AppConfig.class)) ;
System.out.println(context.getBean(PersonDAO.class)) ;
}
输出结果
com.pack.AppConfig$$SpringCGLIB$$0@17f7cd29
com.pack.PersonDAO@7d8704ef
AppConfig是代理类。
AppConfig使用@Component注解
@Component
static class AppConfig {
// ...
}
输出结果
com.pack.AppConfig@659a969b
com.pack.PersonDAO@76908cc0
总结:@Configuration与@Component注解的类中都可以使用@Bean定义方法bean。但使用@Configuration注解的这个类本身会被Spring容器创建为代理类。
2.3 区别2
我们在AppConfig配置类中@Bean方法互相调用。
AppConfig使用@Configuration注解
@Configuration
static class AppConfig {
// 在personDAO方法中调用PersonService()方法
@Bean
PersonDAO personDAO() {
PersonService ps = personService() ;
System.out.printf("在@Bean方法中获取PersonService: %s%n", ps) ;
return new PersonDAO() ;
}
@Bean
PersonService personService() {
return new PersonService() ;
}
}
// 测试代码
AnnotationClassApplicationContext context = ...
System.out.printf("获取PersonService: %s%n", context.getBean(PersonService.class)) ;
输出结果
在@Bean方法中获取PersonService: com.pack.PersonService@2d1ef81a
获取PersonService: com.pack.PersonService@2d1ef81a
获取的同一个PersonService对象,这保证了容器中PersonService始终是同一个对象。
AppConfig使用@Component注解
将上面示例AppConfig注解改为@Component后
输出结果
在@Bean方法中获取PersonService: com.pack.PersonService@3901d134
获取PersonService: com.pack.PersonService@4c9f8c13
获取的是不同的对象,这就造成了问题,Spring容器中存在多个不同的实例,如果这里的调用的是DataSource数据源Bean的方法,那么就出大问题了,很可能你的事务都将不会生效等一些问题。
修改@Configuration注解属性
@Configuration(proxyBeanMethods = false)
static class AppConfig {}
将proxyBeanMethods属性设置为false,再次运行上面的代码,输出结果
在@Bean方法中获取PersonService: com.pack.PersonService@7b227d8d
获取PersonService: com.pack.PersonService@3ba9ad43
PersonService是不同的对象与@Component没有区别。
以上是@Configuration与@Component的区别,接下来通过源码讲解@Configuration到底做了什么。
3. @Configuration原理
首先,我们的知道该注解的处理器是ConfigurationClassPostProcessor这是一个Bean工厂处理器。在该处理器中会对@Configuration注解进行处理。
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
// 1. 第一步,在容器初始化时,该方法会先执行
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
// 遍历当前容器中注册的所有bean
for (String beanName : candidateNames) {
// 初始肯定没有值(除非你手动注册BeanDefinition对象设置了该属性)
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
}
// 判断当前的bean,返回为true后添加到集合中
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
//
return true ;
}
}
// 2. 第二步,对BeanDefinition进行增强配置
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// ...
// 增强配置类
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
// 增强配置类
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
// 遍历所有的BeanDefinition对象
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
// 这里获取当前BeanDefinition对象在第一步中设置的full还是lite
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
// ...
// 判断属性值是否为full
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
// 添加到集合中
configBeanDefs.put(beanName, abd);
}
}
// 遍历找到的所有配置类属性为full的
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// 获取原始class对象
Class<?> configClass = beanDef.getBeanClass();
// 创建代理类
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
// 将原始的class修改为代理类
beanDef.setBeanClass(enhancedClass);
}
}
}
}
ConfigurationClassUtils工具类
public abstract class ConfigurationClassUtils {
static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
// 这里简化了源代码
AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata() ;
// ...
// 获取@Configuration注解的所有属性
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
// 判断proxyBeanMethods属性是否为true,默认值为true
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
// 那么为当前beandefinition配置该属性;设置为full
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 否则设置为lite
else if (config != null || Boolean.TRUE.equals(beanDef.getAttribute(CANDIDATE_ATTRIBUTE)) ||
isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
}
}
在上面通过ConfigurationClassEnhancer类创建代理类,既然是代理,那么就一定有相应的拦截器
class ConfigurationClassEnhancer {
// 这里我们就看下该静态属性即可明白
static final Callback[] CALLBACKS = new Callback[] {
// @Bean方法的拦截
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
}
BeanMethodInterceptor拦截器,会在每次你调用方法的时候都会拦截从BeanFactory中获取你要Bean对象(根据方法名,及方法返回值)。
到此,我们已经分析完了@Configuration注解的类为什么在内部方法调用时始终能保证是同一个对象的原因。