0
点赞
收藏
分享

微信扫一扫

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?

杰克逊爱学习 2022-06-27 阅读 44

例如下面定义的bean:

@EnableAspectJAutoProxy
@Configuration
@PropertySource(value = "classpath:user.properties", encoding = "UTF-8")
@ComponentScan(value = "fengge.dao")
@ImportResource("classpath:demo.xml")
@Import({A.class, B.class})
public class AopOneConfig implements DemoInterface{

@Bean
public AopOne aopOne() {
return new AopOne();
}

@Bean
@ConditionalOnBean(name = "aopOne") // 如果aopOne存在才实例化AopOneAspect
public AopOneAspect oneAspect() {
return new AopOneAspect();
}

@Override
public String selectData() {
return "ok";
}

@Component
class InnerClass{

}
}

这里的aopOne这个bean是怎么注入spring的?我们去看源码是怎么处理的。

一、@Configuration注解是怎么解析的?

要想搞清楚@Bean注解修饰的aopOne是怎么注入spring容器的,那就先要知道@Configuration修饰的AopOneConfig是怎么被spring解析处理的。这个很好理解,要是AopOneConfig 都没被spring解析到,那怎么可能识别aopOne呢?

@Configuration修饰的类是怎么被解析的呢?Spring提供了一个 ​​ConfigurationClassParser​​​ 类,它的 ​​parse​​ 方法用来解析每一个@Configuration修饰的类。

为很好的说明,我们准备了下面的测试代码,方便打断点及查看:

@EnableAspectJAutoProxy
@Configuration
public class AopOneConfig {
@Bean
public AopOne aopOne() {
return new AopOne();
}

@Bean
@ConditionalOnBean(name = "aopOne") // 如果aopOne存在才实例化AopOneAspect
public AopOneAspect oneAspect() {
return new AopOneAspect();
}
}
@Test
public void test2() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopOneConfig.class);
AopOne bean = applicationContext.getBean(AopOne.class);
bean.compute(10, 1);
}

1.1 调用链

​AnnotationConfigApplicationContext​​​ # ​​refresh​​​ # ​​invokeBeanFactoryPostProcessors​​​ # ​​invokeBeanFactoryPostProcessors​​​ # ​​invokeBeanDefinitionRegistryPostProcessors​​​ # ​​BeanDefinitionRegistryPostProcessor​​​ # ​​postProcessBeanDefinitionRegistry​​​ # ​​processConfigBeanDefinitions​​​ # ​​parse​​​ # ​​parse​​​ # ​​processConfigurationClass​​​ # ​​doProcessConfigurationClass​

调用链有点长,前面的在 【一篇搞懂】Sping 容器启动过程详解​ 已经说过这个链路上的很多方法,这里从 ​​invokeBeanDefinitionRegistryPostProcessors​​ 说起。

1.2 整体流程源码

1.2.1 invokeBeanDefinitionRegistryPostProcessors

/**
* Invoke the given BeanDefinitionRegistryPostProcessor beans.
*/
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}

上面的代码主要BeanDefinitionRegistry后置处理器进行bean定义的扫描,这里传过的BeanDefinitionRegistry后置处理器是由上一步循环获取到的,如下:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed
我们打个断点看下这里到底有哪些BeanDefinitionRegistry后置处理器:@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_02
可以看到这里只传入了一个 ​​​ConfigurationClassPostProcessor​​​,那从上面的源码知道后面调用的就是它的 ​​postProcessBeanDefinitionRegistry​​ 方法,接下来看下这个方法。

1.2.2 postProcessBeanDefinitionRegistry

@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);
}

这个方法是 ​​ConfigurationClassPostProcessor​​ 类里面的方法,前面主要是为传入的registry获取它的对应HashCode-- Id,registriesPostProcessed 和 factoriesPostProcessed 都是一个Set集合,确保这个Id是唯一的。

private final Set<Integer> registriesPostProcessed = new HashSet<>();
private final Set<Integer> factoriesPostProcessed = new HashSet<>();

最核心的方法就是 ​​processConfigBeanDefinitions​​。

1.2.3 processConfigBeanDefinitions

/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();

for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}

// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}

// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});

// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}

if (this.environment == null) {
this.environment = new StandardEnvironment();
}

// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}

这个很长也很重要,主要是对 ​​@Configuration​​​ 注解修饰的类进行校验、解析、处理。具体的逻辑说半天不如打个断点看下:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_03
candidateNames存储的是已经注册的所有BeanDefinitionName:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_spring_04
接下来说说ConfigurationClassUtils类中的几个重要方法:
方法:​​​isFullConfigurationClass​​​ 作用:判断是否是full configuration,带有@Configuration注解的类叫做full configuration,需要注意的是,该方法中并不直接判断是否带有Configuration注解,而是判断bean.getAttribute是否标记为FULL。
方法:​​isLiteConfigurationClass​​ 作用:判断是否是lite configuration,带@Component,@ComponentScan,@Import,@ImportResource,@Bean 5个注解中的任一个,那么这个类叫做lite configuration。
方法:​​checkConfigurationClassCandidate​​ 作用:判断是否是configuration class,如果isFullConfigurationClass或者isLiteConfigurationClass,那么就是configuration class。
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_05
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_06

public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
metadata = new StandardAnnotationMetadata(beanClass, true);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// It's a full or lite configuration candidate... Let's determine the order value, if any.
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}

上面的断点可以看到:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_07
configCandidates只会有aopOneConfig这一个元素。就像源码里注释的一样, configCandidates若为空,说明没有 @Configuration 修饰的类被找到。
接下来就是为 configCandidates 里面的元素进行排序,会考虑 ​​​@Order​​​ 注解标注的顺序(如果有)。
再接下来就是检测封闭的应用程序上下文提供的任何自定义bean名称生成策略,看看有无自定义bean名称生成器。
再接下来就是重点了!已经确定了​​​@Configuration​​​ 注解修饰的类,也进行了校验,那 ​​ConfigurationClassParser​​​ 就要对这个类进行解析了。
后面会详细介绍 ​​​ConfigurationClassParser​​​ 解析的过程,这里先把 ​​processConfigBeanDefinitions​​​ 的流程梳理完。
打个断点看看解析出来的结果是啥样的:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_08
可以看到,configClasses已经是aopOneConfig了,这时虽然aopOneConfig这个bean的信息(源码里注释所说的model)已经被解析了,但是还不能以此信息直接创建bean,因为beanFactory是根据BeanDefinition去创建的。那就要根据这些解析出来的信息来创建aopOneConfig的bean definitions。
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_09
创建读取完全填充好的ConfigurationClass实例的读取器 ​​​this.reader​​​,其loadBeanDefinitions方法将完全填充好的ConfigurationClass实例转化成BeanDefinition注册入IOC容器。
其实不止aopOneConfig,它里面的aopOne、oneAspect的bean definitions也是在loadBeanDefinitions这个方法被注册到容器中(这个方法也是比较复杂,本文后面会详细说明)。
所以在完成上面的步骤后,registry里注册的BeanDefinition数量就比candidateNames里元素数量多了,于是有了下面的判断及处理:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_10
用了newCandidateNames去保存此时容器中所有的beanName,然后在for循环中对于在newCandidateNames而不在oldCandidateNames里的元素,创建它们的BeanDefinition。
到这里基本走完了 ​​​@Configuration​​ 修饰的类是怎么被解析的流程。

1.3 核心源码

上面为了不影响对整体流程的说明,对于部分核心源代码并未展开说明,比如 ​​ConfigurationClassParser​​​ 的 ​​parse​​ 方法,接下来详细说明下。

1.3.1 ConfigurationClassParser#parser(candidates)

public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}

这个 ​​ConfigurationClassParser#parser(candidates)​​​ 里面又包含了三个 ​​parse​​ 方法,是传参不同的重载方法。

protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}

protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

以上可以看到,三个方法最后都是调用 ​​processConfigurationClass​​ 这个方法:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}

上面这个方法中,开始的 ​​this.conditionEvaluator.shouldSkip​​​ 主要用于完成条件注解 ​​@Conditional​​​ 的解析和判断。 ​​@Conditional​​​ 注解使得只有在特定条件满足时才启用一些配置,shouldSkip就是用于判断@Conditional下的而配置是否被启用。若返回true,则说明 ​​@Conditional​​​ 注解生效,不会接着创建这个bean。若没有加​​@Conditional​​ 注解,这个方法一般返回 false,会继续下面的流程。

接着检查这个 ​​@Configuration​​​ 注解的类(configClass)是否已经解析过,解析过的configClass都会在这个 ​​parse​​​ 方法的最后put到​​this.configurationClasses​​​ 这个Map里面,若Map里有则说明已经解析过了。那就会将 ​​existingClass​​ 里面之前解析的和现在的进行合并。

接下来 ​​asSourceClass(configClass)​​​ 递归处理这个configClass及其超类的层次结构:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_11
最后还有个重要的 ​​​doProcessConfigurationClass​​ 方法,从这个方法名就知道这个是要真正处理 ConfigurationClass 了:

/**
* Apply processing and build a complete {@link ConfigurationClass} by reading the
* annotations, members and methods from the source class. This method can be called
* multiple times as relevant sources are discovered.
* @param configClass the configuration class being build
* @param sourceClass a source class
* @return the superclass, or {@code null} if none found or previously processed
*/
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {

if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}

// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}

// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);

// 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);
}
}

// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

// Process default methods on interfaces
processInterfaces(configClass, sourceClass);

// 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;
}

上面的源代码中注意下注释,可以看到一些熟悉的注解:

  • ​@PropertySource​​​ 加载指定的配置文件。参考:如何引用 application.yml 或者 application.properties 之外的其它配置文件?
  • ​@ComponentScan​​​ 在配置类上添加该注解,默认会扫描该类所在的包下所有的配置类,参考:@ComponentScan配置老扫描不到Bean,这下彻底搞懂
  • ​@Import​​​ 向spring容器中注入指定的bean,参考:向spring容器中注入bean有哪些方式?大总结!
  • ​@ImportResource​​​ 导入Spring的配置文件,让配置文件里面的内容生效。参考:如何引用 application.yml 或者 application.properties 之外的其它配置文件?
  • ​@Bean​​​ 容器中添加组件,参考:向spring容器中注入bean有哪些方式?大总结!

所以大概的流程大家都能猜到,就是去看下有无这些注解,并给与相应的处理。一点一点去看:

(1) processMemberClasses

首先,查看这个configClass是否有内部类,有的话通过 ​​processMemberClasses​​ 处理:

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
processConfigurationClass(candidate.asConfigClass(configClass));
}
finally {
this.importStack.pop();
}
}
}
}
}

我们的测试代码 AopOneConfig 里有个 内部类 InnerClass,打个断点看下:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_spring_12
可以看到就是在这里处理的内部类,对这个内部类的处理和对其外部类处理是一样的,因为你可以看到这个方法最后又回头调用了 ​​​processConfigurationClass​​ 方法。

(2) processPropertySource

接着就是处理 ​​@PropertySource​​ 注解配置:

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
private void addPropertySource(PropertySource<?> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();

if (this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource<?> existing = propertySources.get(name);
if (existing != null) {
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}

if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}

老规矩打个断点看下:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_13
​​​user.properties​​​ 我们自己定义的配置文件已经通过 ​​@PropertySource​​ 注解读取到spring中。

(3) this.componentScanParser.parse

接下来就是处理 ​​@ComponentScan​​​ 或者 ​​ComponentScans​​​ 注解, 直接断点看下:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_14
我们的测试类里注明的路径就是’fengge.dao’,可以看到这个路径下的bean都已经被扫描到。扫描到后就开始了为这些bean生成对应的BeanDefinition,最后每个BeanDefinition作为参数又回头调用parse。怎么扫描到的?通过 ​​​this.componentScanParser.parse​​ :

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));

ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}

scanner.setResourcePattern(componentScan.getString("resourcePattern"));

for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}

boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}

Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}

scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

​doScan​​​ 方法中会根据指定的 basePackage 找到所有的候选bean:
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_15
​​​findCandidateComponents​​ 方法可以跟进去看下:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_ide_16
当然,​​​getResourcePatternResolver().getResources(packageSearchPath);​​​ 根据路径去解析resource的过程参考:@ComponentScan配置老扫描不到Bean,这下彻底搞懂

(4) processImports

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_17
处理Import注解,可以通过Import导入相应的类,我们在测试类中添加的是 @Import({A.class, B.class}) ,即导入的是A、B 两个类,先看它的第三个传参:

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {

if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_18上面的方法,就是看 sourceClass(就是我们的测试配置类aopOneConfig)是否包含 @Import注解,且是递归实现,即看这个注解包含的注解是否也有 @Import注解。若有则把@Import注解注入的bean放到一个Set集合里面。这里可以看到这里通过 @Import注解 注入了三个bean,即A、B、AspectJAutoProxyRegistrar(@EnableAspectJAutoProxy 注解中包含的@Import注解导入的)。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

if (importCandidates.isEmpty()) {
return;
}

if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(
configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}

上面就是对拿到所有 @Import注解 导入的类后进行处理,生成相应的 BeanDefinition 并注册到容器。

(5) processImports

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_19

public void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
this.importedResources.put(importedResource, readerClass);
}

(6) retrieveBeanMethodMetadata

接着处理@Bean注解:

// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_sed_20
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_spring_21
上面可以看到我们测试类里面的两个@Bean注解的bean已经被找到。怎么解析找到的呢?
通过retrieveBeanMethodMetadata()方法:

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
AnnotationMetadata original = sourceClass.getMetadata();
Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
// Try reading the class file via ASM for deterministic declaration order...
// Unfortunately, the JVM's standard reflection returns methods in arbitrary
// order, even between different runs of the same application on the same JVM.
try {
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
break;
}
}
}
if (selectedMethods.size() == beanMethods.size()) {
// All reflection-detected methods found in ASM method set -> proceed
beanMethods = selectedMethods;
}
}
}
catch (IOException ex) {
logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
// No worries, let's continue with the reflection metadata we started with...
}
}
return beanMethods;
}

ok,到这里已经@Configuration 和 @Bean 注解修饰的bean怎么被扫描解析注入spring的过程已经说差不多了。当然这里只是创建它们的BeanDefinition,后面还有它们的实例化初始化等流程。

(7)processInterfaces

虽然@Configuration 和 @Bean 注解修饰的bean怎么被扫描解析注入spring的过程已经说差不多了,但是为了完整性,接着把 ​​doProcessConfigurationClass​​​ 这个方法的流程说完。
​​​processInterfaces​​ 是来处理 configClass 实现的接口的,我们的测试代码里面让 AopOneConfig实现了 我们自定义的DemoInterface接口:

public interface DemoInterface {
String selectData();
}

@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_spring_22

(7)getSuperClass

处理父类。
@Bean注解修饰的bean是怎么注入spring容器的,@Configuration呢?_spring_23


举报

相关推荐

Spring注入Bean

0 条评论