@Configuration 使用与原理
@Configuration 基本使用
@Configuration 源码的注解非常长,十分详细地阐述了 @Configuration 的使用方法并给出了示例代码,我这里就贴出一小部分:
- 搭配 @Bean 注入到 Spring 容器 Indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.
- 搭配 @ComponentScan 扫描 Bean @Configuration classes may not only be bootstrapped using component scanning, but may also themselves configure component scanning using the @ComponentScan annotation.
- ...
而 @Configuration 有一个属性 proxyBeanMethods
往往被人忽视,我们重点来看一下:
@Configuration 的两种模式
java
// @Configuration 本身是个复合注解,包含了一个 @Component
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
为什么配置类也会有代理?其实这个属性值对应了两种模式:Full 与 Lite,对应 Spring 容器在处理 @Bean 时的两种不同行为。
java
// 默认true即为Full模式;false为Lite模式,可以指定配置类为Lite模式
@Configuration(proxyBeanMethods = false)
1、Full模式:代理配置类
Full 模式会使用 CGLib 代理这个配置类。
Full 模式下,一个方法调用另外一个 @Bean 方法,动态代理方法会先去容器中检查是否存在该 Bean,如果存在,则直接使用容器中的 Bean,否则才会去创建新的对象。
java
@Configuration
public class xxxConfig {
// 一个方法getUser1调用另外一个 @Bean方法 getUser2
@Bean
public User getUser1(){
return getUser2();
}
@Bean
public User getUser2(){
return new User();
}
}
此时的输出结果:
java
applicationContext.getBean(User.class)
// expected single matching bean but found 2: getUser1,getUser2
发现只有一个名为 getUser2 的 Bean。
2、Lite模式
显示指定 proxyBeanMethods = false,此时为 Lite 模式。
仍然是上面的代码,只修改了proxyBeanMethods的值。
此时直接编译报错,提示为:
java
// Method annotated with @Bean is called directly in a @Configuration where proxyBeanMethods set to false. Set proxyBeanMethods to true or use dependency injection.
即:Spring不允许在Lite模式下的一个类内方法调用另外一个 @Bean 方法,因为这可能导致对象被重复创建,而Full模式通过动态代理杜绝了这个可能。
直接把 @Configuration 替换为 @Component,同样报错,提示为:
java
// Method annotated with @Bean is called directly. Use dependency injection instead.
总之,Spring建议我们通过依赖注入或者Full模式解决这个问题。
Full vs Lite
可能大多数情况下都采用了默认的 Full 模式,但 Full 模式也并非万金油。
因为 Full 用到了动态代理,因此速度上比 Lite 慢,并且继承了CGLIB的局限性:final修饰的方法无法被代理,因此可能报错。
但Lite的缺点是:一个@Bean 方法无法调用另外一个 @Bean 方法。
@Configuration 源码分析
源码的注释中提到了一个关键类:ConfigurationClassPostProcessor。
xml<beans> <context:annotation-config/> <bean class="com.acme.AppConfig"/> </beans>
In the example above, is required in order to enable ConfigurationClassPostProcessor and other annotation-related post processors that facilitate handling @Configuration classes.
但是,该从哪下手呢?我尝试对 @ComponentScan 使用 ALT+F7 并打上断点,发现调用栈进入的是 refresh 方法中的 invokeBeanFactoryPostProcessors(beanFactory)
。
java
// refresh 方法部分源码 ...
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
于是又将断点打在 invokeBeanFactoryPostProcessors(beanFactory)
方法上,此时已经有若干 BeanDefinition:
其中这个 internalConfigurationAnnotationProcessor 就是 ConfigurationClassPostProcessor 类。
进到这个方法里简单看一下:
java
/**
* Invoke the given BeanDefinitionRegistryPostProcessor beans.
*/
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
.tag("postProcessor", postProcessor::toString);
// 执行这个方法时 BeanDefinationMap 还只有 7 个
postProcessor.postProcessBeanDefinitionRegistry(registry);
// 执行 end 方法时 BeanDefinationMap 已经加载了很多 Bean 了
postProcessBeanDefRegistry.end();
}
}
我们发现当断点停在postProcessor.postProcessBeanDefinitionRegistry(registry);
时,BeanDefinationMap
还只有 7 个,下一处就有几百个了。因此 postProcessBeanDefinitionRegistry
这个方法就是核心,而它又调用了 processConfigBeanDefinitions
。
源码上的注解是这样的,又印证了我们的想法:
Build and validate a configuration model based on the registry of
Configuration
classes.
这个方法比较长,我只贴出部分核心代码,省略了如去重的部分:
java
// 创建 ConfigurationClassParser 并解析 @Configuration class
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(...);
// candidates 是待解析的 @Configuration 集合
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
do {
StartupStep processConfig = this.applicationStartup.start("...");
// 核心方法 parse 解析 @Configuration
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
this.reader.loadBeanDefinitions(configClasses);
candidates.clear();
// 加载了新的 BeanDefinition
if (registry.getBeanDefinitionCount() > candidateNames.length) {
// 新加载的(可能是扫描到了新的) Configuration 加入 candidates 循环处理
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) )) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
关键问题是,parser.parse(candidates);
具体是如何解析的?
我们发现 parse 方法,会调用到 processConfigurationClass 方法
java
// 递归解析
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
真正处理 @Configuration:doProcessConfigurationClass
而这个 doProcessConfigurationClass 就是最核心的方法:
java
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// 1. Process any @PropertySource annotations
// ... 省略判断
processPropertySource(propertySource);
// 2. Process any @ComponentScan annotations
// 用 componentScanParser 解析 @ComponentScan 的路径
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName())
// 可能扫描到新的 @Configuration ,parse
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
// 3. Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 4. Process any @ImportResource annotations
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 5. Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 6. Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// 7. Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
这里也对不同的注解做了不同的处理,因为比较多,就看一下是如何处理 @Bean 的,其它方法就不一一分析了。
@Bean 的处理方式
java
// Process individual @Bean methods
// 首先 Retrieve the metadata for all @Bean methods.
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
ConfigurationClass 有这么一个属性:
java
private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
也就是把 beanMethod 放入到一个 LinkedHashSet,仅此而已。
而 BeanMethod 的注释很明了:
Represents a @Configuration class method annotated with @Bean.
那么解析 @Bean 又在何时呢?在 beanMethods 属性的 get 方法上打上断点,发现进入了 processConfigBeanDefinitions
方法的 this.reader.loadBeanDefinitions(configClasses);
中。
java
// loadBeanDefinitionsForConfigurationClass 方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
这里就为 @Bean 方法加载 BeanDefinitions,有我们熟悉的别名,默认 bean 名称,initMethod,autowire 等
java
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
// Consider name and any aliases
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
// bean 名称 默认方法名
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
// 别名 Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) {
// static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
}
else {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
beanDef.setUniqueFactoryMethodName(methodName);
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
if (metadata instanceof StandardMethodMetadata) {
beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
}
beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
Autowire autowire = bean.getEnum("autowire");
if (autowire.isAutowire()) {
beanDef.setAutowireMode(autowire.value());
}
boolean autowireCandidate = bean.getBoolean("autowireCandidate");
if (!autowireCandidate) {
beanDef.setAutowireCandidate(false);
}
String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
}
String destroyMethodName = bean.getString("destroyMethod");
beanDef.setDestroyMethodName(destroyMethodName);
// Consider scoping
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
beanDef.setScope(attributes.getString("value"));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
}
}
// Replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry,
proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = new ConfigurationClassBeanDefinition(
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName);
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
小结
本文主要介绍了 @Configuration 的两种模式:Full 与 Lite。Full 模式会采取 CGLIB 动态代理实现,保证在调用一个 @Bean 方时法,会先去容器中检查是否存在该 Bean,如果存在,则直接使用容器中的 Bean,否则才会去创建新的对象。而 Lite 模式为了避免对象被重复创建,禁止类内方法调用另外一个 @Bean 方法。
然后对 @Configuration 注解进行源码分析。在 refresh 方法中调用invokeBeanFactoryPostProcessors(beanFactory)
时,ConfigurationClassPostProcessor 会负责解析 @Configuration 类,依次处理 @PropertySource、@ComponentScan,最后是@Bean。并且 @Bean 仅仅是将方法封装并放入 LinkedHashSet 中,在后续 loadBeanDefinitionsForConfigurationClass 时再解析。