@Component@Configure都是在SpringBoot中用于配置的常见注解,一个多用于自动化配置,另一个多用于第三方库的Config配置。

一、注解定义

两者定义分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// @Component的定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";

}

// @Configure的定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

@AliasFor(annotation = Component.class)
String value() default "";

boolean proxyBeanMethods() default true;

}

很显然,@Configure注解本质上也是@Component,因此@ComponentScan能扫描到@Configure注解的类。

二、注解使用

在SpringBoot中,对于配置类来讲,大体可以分为两类,一类是LITE模式,另一类是FULL模式@Component注解对应前者,而@Configure注解对应后者。

使用@Component实现配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class AppConfig {
@Bean
public Foo foo() {
System.out.println("foo() invoked...");
Foo foo = new Foo();
System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
return foo;
}

@Bean
public Eoo eoo() {
System.out.println("eoo() invoked...");
Foo foo = foo();
System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
return new Eoo();
}

public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
}
}

执行结果如下:

1
2
3
4
5
6
foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467

我们可以注意到,@Bean注解的foo()方法和eoo()方法执行的foo()方法来自不同的foo对象,但是当我们换成使用@Configure注解时,执行结果如下:

1
2
3
4
foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393

显然foo()方法只执行了一次,foo对象只产生了一个,这就是@Component@Configure的区别现象展示。简单地解释这个现象就是当调用foo()这个方法时,都去容器获取foo这个Bean即可,而要做到这一点可以使用代理,也就是说@Configure注解的类会被代理,更加准确的说是一个类的BeanDefinition的Attribute中有FULL配置属性,那么这个类就会被Spring代理。

三、如何实现FULL配置的代理

重要的一点在于SpringBoot在什么时间将这些配置类转变为FULL模式或者是LITE模式,需要先了解一个关键的类:ConfigurationClassPostProcessor

3.1 ConfigurationClassPostProcessor是什么

这个类的定义如下:

1
2
3
4
5
6
7
public class ConfigurationClassPostProcessor implements 
BeanDefinitionRegistryPostProcessor,
PriorityOrdered,
ResourceLoaderAware,
BeanClassLoaderAware,
EnvironmentAware {
}

由这个类定义可知这个类的类型为BeanDefinitionRegistryPostProcessor,以及实现了众多Spring内置的Aware接口。

3.2 ConfigurationClassPostProcessor在什么时间被实例化

需要先明确一个前提,那就是ConfigurationClassPostProcessor这个类对应的BeanDefinition在什么时间注册到Spring的容器中的,因为Spring的实例化比较特殊,主要是基于BeanDefinition来处理的,那么现在这个问题就可以转变为ConfigurationClassPostProcessor这个类是在什么时间被注册为一个Beandefinition的?这个可以在源代码中找到答案,具体其实就是在初始化这个Spring容器的时候。

1
2
3
4
5
new AnnotationConfigApplicationContext(ConfigClass.class)
-> new AnnotatedBeanDefinitionReader(this);
-> AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
-> new RootBeanDefinition(ConfigurationClassPostProcessor.class);
-> registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName)

从这里可以看出,ConfigurationClassPostProcessor已经被注册为了一个BeanDefinition。

3.3 @Component与@Configuration的实现区别

上面ConfigurationClassPostProcessor已经注册到BeanDefinition注册中心了,说明Spring会在某个时间点将其处理成一个Bean,那么具体的时间点就是在BeanFactory所有的后置处理器的处理过程。

1
2
3
4
AbstractApplicationContext
-> refresh()
-> invokeBeanFactoryPostProcessors(beanFactory);
-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

这个处理BeanFactory的后置处理器简单说来就是主要处理所有实现了BeanFactoryPostProcessor及BeanDefinitionRegistryPostProcessor的类,当然ConfigurationClassPostProcessor就是其中的一个,那么接下来我们看看实现的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);

processConfigBeanDefinitions(registry);
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}

enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

第一个方法主要完成了内部类,@Component@ComponentScan@Bean@Configuration@Import等等注解的处理,processConfigBeanDefinitions()@Configuration类里所有@Bean@Import@ComponentScan等再解析一遍,生成新的BeanDefinition塞回注册表(属性设置为FULL)。然后生成对应的BeanDefinition,另一个方法就是对@Configuration使用CGLIB进行增强。

在第一个方法中的processConfigBeanDefinitions()方法中有一个checkConfigurationClassCandidate()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

// ...

Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// ...
}

根据程序的判断可知,如果一个类被@Configuration标注且代理模式为true,那么这个类对应的BeanDefinition将会被Spring添加一个FULL配置模式的属性,这个”属性“在Spring中有一个特定的接口就是AttributeAccessor,BeanDefinition就是继承了这个接口。

在这里也能看到@Configuration(proxyBeanMethods = false)@Component一样效果,都是LITE模式。

最后则是由enhanceConfigurationClasses()方法对@Configuration注解标注的类的增强,进行CGLIB代理,代码就不展示了,CGLIB的原理可以看我的另一篇文章Java代理模式: JDK代理与CGLIB代理

四、总结

一句话概括就是@Configuration中所有带@Bean注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。