-
随着
spingboot
的大火,注解式配置
受到了大家的热烈欢迎,而@Component
和@Configuration
都可以作为配置类,之前一直都没觉得这两个用起来有什么差别,可能有时程序跑的和自己想的有所区别也没注意到。 -
直到看到这篇文章:https://my.oschina.net/guangshan/blog/1807721 。我意识到
@Component和@Configuration
是有区别的,错误的使用可能会导致严重的后果。
请看下面一段代码:
@Getter
@Setter
public class Car {
private Integer id;
private String name;
public Car() {
System.out.println("car构造器被调用了!");
}
}
@Data
public class Driver {
private Integer id;
private String name;
private Car car;
}
/**
* Description:
*
* @author guizy1
* @date 2020/12/7 10:55
*/
@Configuration
public class MyConfig {
@Bean
public Driver driver(){
Driver driver = new Driver();
driver.setId(1);
driver.setName("driver");
driver.setCar(car());
return driver;
}
@Bean
public Car car(){
Car car = new Car();
car.setId(1);
car.setName("car");
return car;
}
}
// 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfig.class)
public class SpringTest {
@Autowired
private Driver driver;
@Autowired
private Car car;
@Test
public void test () {
boolean result = driver.getCar() == car;
System.out.println(result ? "同一个car" : "不同的car");
}
}
- @Configuration打印结果
car构造器被调用了! 同一个car
- 替换为
@Component
后的打印结果:
car构造器被调用了! car构造器被调用了! 不同的car
- 从上面的结果可以发现
使用Configuration
时在driver和spring容器之中的是同一个对象,而使用Component时是不同的对象。 - 造成不同结果的原因在
ConfigurationClassPostProcessor
类之中,通过调用enhanceConfigurationClasses
方法,为被注解@Configuration的类进行CGLIB代理加粗样式,代码如下:
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map configBeanDefs = new LinkedHashMap();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {//判断是否被@Configuration标注
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
// Set enhanced subclass of the user-specified bean class
Class configClass = beanDef.resolveBeanClass(this.beanClassLoader);
Class enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);//生成代理的class
if (configClass != enhancedClass) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
//替换class,将原来的替换为CGLIB代理的class
beanDef.setBeanClass(enhancedClass);
}
}
catch (Throwable ex) {
throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
判断是否为配置类的代码如下:
//是否为配置类
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata));
}
//是否为完整配置类
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
return metadata.isAnnotated(Configuration.class.getName());
}
//是否为精简配置类
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
//精简配置类包含的注解
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
从上面可以看到
- 虽然
@Component
注解也会当做配置类
,但是并不会为其生成CGLIB代理Class,所以在生成Driver对象时和生成Car对象时调用car()方法执行了两次new操作,所以是不同的对象。 - 当时
@Configuration
注解时,生成当前对象的子类Class,并对方法拦截
,第二次调用car()方法时直接从BeanFactory之中获取对象,所以得到的是同一个对象。
相关: 至于产生CGLIB代理
的流程,可以看一下下面链接,其中含有详细介绍:https://my.oschina.net/guangshan/blog/1807721