Spring源码解析----@Configuration、@SpringBootApplication、@Bean

这篇文章主要分析一下这几个注解的原理。

SpringBoot中这几个注解关系比较紧密,少了@SpringBootApplication注解,SpringBoot很多功能都没法使用,所以文章分析的内容涉及了该注解
另外还有个问题也与@SpringBootApplication有关:

  • SpringBoot为什么不需要配置包扫描,Spring是如何知道要扫描哪些路径下的类?

1 Demo

在上一篇文章上,加上两个类,Bean1和MyConfiguration,代码如下:

@Configuration
public class MyConfiguration {
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }
}
public class Bean1 {
    public Bean1(){
        System.out.println("bean init");
    }
}

上面代码的作用是实例化一个Bean1对象(作用类似在Bean1类上加上@Component注解)。

这时候工程目录如下:


image.png

下面就以上面代码为例子,分析@Configuration和@Bean注解的原理。

2 @SpringBootApplication

在分析之前,需要先介绍一下启动类的@SpringBootApplication注解,先看下其定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
}

@SpringBootApplication注解上很多个注解,其中就包括@ComponentScan注解,所以这里查询@ComponentScan注解是能成功的,因为这里及上文中查询@Configuration的方式都是:查找当前注解,如果注解没有则查找注解上的注解有没,所以一个@SpringBootApplication就包含了@ComponentScan@SpringBootConfiguration(即@Configuration)、@EnableAutoConfiguration几个注解的功能(当然也可以不使用该注解,直接把该注解上的其他注解直接放到启动类上)

在后续的处理当中,当判断一个类上是否有某个注解的时候,即使一个类是间接依赖的某个注解,那么这种情况也是符合的,也会将该注解的信息给提取出来,例如,判断启动类是否拥有@Configuration注解,流程如下:

  1. 首先判断启动类上是是否有有@Configuration注解,结果没有
  2. 判断@SpringBootConfiguration注解上是否有@Configuration注解注解,结果没有
  3. 获取@SpringBootConfiguration注解上的所有注解,判断这些注解上是否有@Configuration注解,重复类似23的流程

由于@SpringBootConfiguration注解上有个@SpringBootConfiguration注解,而该注解上有@Configuration注解,所以该查找成功

3 解析入口

上文分析了,执行run方法后,最后会调用到org.springframework.context.support.AbstractApplicationContext#refresh方法,这是Spring核心的方法,这篇文章主要分析几个注解的实现,所以这里只展示相关的代码:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        //这里就是核心逻辑的入口,主要是对BeanFactoryPostProcessor的处理
        invokeBeanFactoryPostProcessors(beanFactory);
    }

BeanFactoryPostProcessor:类似于BeanPostProcessor,BeanPostProcessor主要用来后置处理Bean的,而BeanFactoryPostProcessor则是用来在Bean初始化完成之前,用来操作BeanFactory的,两者都是Spring开放的扩展点,用来扩展对应的功能

invokeBeanFactoryPostProcessors内将逻辑委托给PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法,该方法内部则会对所有的BeanFactoryPostProcessor进行排序并调用BeanFactoryPostProcessor接口对应的方法。

BeanFactoryPostProcessor具体处理这里不再详细展开,只需要知道@Configuration处理逻辑在某个BeanFactoryPostProcessor中,这个类就是ConfigurationClassPostProcessor

找到该类,发现其实现的接口是BeanDefinitionRegistryPostProcessor而不是BeanFactoryPostProcessor,而BeanDefinitionRegistryPostProcessor的父接口是BeanFactoryPostProcessor,和BeanFactoryPostProcessor其实差不多,如果实现了该接口,会先调用BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法,再调用BeanFactoryPostProcessor接口的postProcessBeanFactory方法

4 ConfigurationClassPostProcessor

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法就是核心所在,其中会调用到processConfigBeanDefinitions方法,直接看下这个方法的逻辑(省略一些非核心流程代码):

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        // 获取当前注册到容器的BeanDefinition的名称集合
        String[] candidateNames = registry.getBeanDefinitionNames();
        // 筛选具有@Configuration注解信息的BeanDefinition
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            //判断BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE属性是否为full
            //判断BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE属性是否为lite
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                    //log忽略
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                // 该BeanDefinition对应的类是否有@Configuration注解
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        // 如果当前没有,那么就不需要进行@Configuration的处理
        if (configCandidates.isEmpty()) {
            return;
        }
        //用来解析各种注解的解析器
        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);

            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();
        while (!candidates.isEmpty());

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

processConfigBeanDefinitions整个方法可以大体划分为三个阶段:

  1. 从容器中获取和Configuration有关系的BeanDefinition
  2. 以该BeanDefinition为起点,进行解析操作,得到解析结果集
  3. 将解析到的结果集加载到容器中,即构造成一个BeanDefinition放到容器中待初始化

这里还有几个注意点:

  1. 在上面的第1步的时候,能取到的数据为所有的BeanFactoryPostProcessor以及我们的main方法的类SpringBootDemoApplication。BeanFactoryPostProcessor当然不奇怪,而为什么还有SpringBootDemoApplication呢?回顾一下上篇文章说的启动流程,在load方法中把SpringBootDemoApplication已经加载进容器了。另外这个时候,configCandidates只有一个元素,即SpringBootDemoApplication
  2. 结合我们的demo,可以猜测,第2步得到的结果集中,应该包括MyConfiguration,可能包括Bean1,如果这里没包括的话可能是以别的形式获取,这样MyConfiguration和Bean1才能在后面Bean初始化的时候被创建,而具体怎么解析的,后续会分析到。

4.1 判断类是否与@Configuration有关

在上面第1步中,有@Configuration注解的会加入到集合当中,这个判断是在ConfigurationClassUtils#checkConfigurationClassCandidate当中实现

    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())) {
            metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        }
        else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
            Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
            metadata = new StandardAnnotationMetadata(beanClass, true);
        }
        else {
            try {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
                metadata = metadataReader.getAnnotationMetadata();
            }
            catch (IOException ex) {
                return false;
            }
        }
        // 查找当前注解是否是与@Configuration相关
        // 该方法还会判断该注解上的注解是否有@Configuration,一直往上寻找
        // 因为有的注解为复合注解
        if (isFullConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        // 查找当前注解上是否有ComponentScan、Component、Import、ImportResource注解
        //如果没有则查找Bean注解,同上,一直往上查找
        else if (isLiteConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
            return false;
        }
        return true;
    }

从这里可以猜测,我们使用如下的代码,程序应该也可以跑起来

//@SpringBootApplication
//@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@Component
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}

上面说了@SpringBootApplication等价于@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan注解,而这时,将@SpringBootConfiguration替换成@Component之后,checkConfigurationClassCandidate方法仍然返回true,所以程序应该也可以跑起来。

试着跑了一下,代码没问题,Bean1仍然被初始化,而把MyConfiguration类上的注解换成@Component也是正常没有问题,但是其实两者还是有差别的,但是不在这篇文章的讨论范围,故不详细分析。

4.2 注解解析

解析工作交由ConfigurationClassParser处理

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        this.deferredImportSelectors = new LinkedList<>();

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

        processDeferredImportSelectors();
    }

上面说了,这个时候configCandidates只有一个元素,即SpringBootDemoApplication,他属于AnnotatedBeanDefinition,会走到第一个分支,其实无论哪个分支,最后都会走到如下方法

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        //....

        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

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

参数为ConfigurationClass,即配置有@Configuration注解的类对应的ConfigurationClass对象。

最后将逻辑交由doProcessConfigurationClass处理,该方法会通过配置有ConfigurationClass对象去获取额外引入的类(也可能没有引入)。

最后该方法会又会返回SourceClass对象,直到返回的对象为空才结束解析。
这里是为了解决有父类的情况,假设SpringBootDemoApplication有父类,那么这里返回的SourceClass为其父类,接着进行解析

解析完成后,将ConfigurationClass对象放到Map中,表示需要加载到容器中的ConfigurationClass对象集合,后续会获取该Map的元素加载到容器中

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {

        // 内部类的处理
        processMemberClasses(configClass, sourceClass);

        // @PropertySource注解处理
        //....

        // @ComponentScan注解处理
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            //ComponentScan属性可能有多个,因为可能配置了ComponentScans注解
            for (AnnotationAttributes componentScan : componentScans) {
                // 通过配置的@ComponentScan注解的信息进行包扫描
                // 扫描后的类型将注册成BeanDefinitionHolder并返回
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // 扫描出来的类,又调用了parse,进行递归处理
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(
                            holder.getBeanDefinition(), this.metadataReaderFactory)) {
                        parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        // 处理@Import注解
        //....

        // @ImportResource注解处理
        //....

        // @Bean注解处理
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // 父类处理
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") &&
                    !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // 返回父类,让程序继续处理父类的注解
                return sourceClass.getSuperClass();
            }
        }

        // 如果没有父类,那么返回null表示不需要继续处理
        return null;
    }

4.2.1 成员类处理

    private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
        //获取成员类的SourceClass对象
        Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
        if (!memberClasses.isEmpty()) {
            List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
            //获取与@Configuration有关的
            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) {
                //....
                processConfigurationClass(candidate.asConfigClass(configClass));
                //....
            }
        }
    }

代码逻辑比较简单,核心的逻辑还是processConfigurationClass,该方法只是找到和@Conguration注解有关的类

4.2.2 @ComponentScan注解处理

首先会调用AnnotationConfigUtils.attributesForRepeatable方法获取@ComponentScan@ComponentScans注解信息(该信息在@SpringBootApplication注解上,间接获取到),当获取到ComponentScan属性后,会调用ComponentScanAnnotationParser#parse方法进行查找,主要看下大概的逻辑

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
        // ....
        // 获取basePackages属性,即进行包扫描的根路径
        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);
            basePackages.addAll(Arrays.asList(tokenized));
        }
        //获取basePackageClasses属性,以该类所在的包作为扫描路径
        //ClassUtils.getPackageName为获取当前类所在的包路径
        for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        //如果上面两个属性都没配置,则以参数declaringClass所在的包作为扫描路径
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

        //....
        //开始扫描并注册
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }

这里只列出了包扫描相关的代码,可以看到,获取包路径有三种形式,

  1. 在@ComponentScan的basePackages属性配置包路径
  2. 在@ComponentScan的basePackageClasses属性配置类信息,扫描路径取自该类所在包的路径
  3. 12都没填写,那么取自声明该注解的类所在包作为扫描路径

从这里可以看到SpringBoot即使不配置扫描路径也是可以正常跑的(当然也可以在@SpringBootApplication上特意配置指定信息),这时候会走到第3步,将启动类所在的包作为扫描路径,而这有个前提就是其他需要扫描的包需要放到启动类所在的包路径以下,否则将扫描不到

4.2.3 @Bean注解处理

以Demo为例,通过@SpringBootApplication的ConfigurationClass为入口,扫描得到自定义的配置有@Configuration注解的MyConfiguration类,然后又调用parse方法进行解析,最后又会调用到doProcessConfigurationClass方法,只不过参数ConfigurationClass对象对应的是MyConfiguration,这时候运行到retrieveBeanMethodMetadata方法的时候,会获取MyConfiguration下配置了@Bean注解的方法,然后进行处理。

        //获取所有配置了@Bean注解的方法元数据信息
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        //将其封装成BeanMethod,并放入到ConfigurationClass对象的集合中待后续处理
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

这里只是将配置了@Bean方法的信息收集起来,并没有做特殊处理

4.3 将ConfigurationClass信息加载到容器中

回到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法,当调用完parse方法之后,能得到一批ConfigurationClass集合,但是这时候只是获取到,而容器中还没有对应的注册信息,那么接下来就是对这批集合进行注册处理

  //上面分析的解析流程,主要是获取一批ConfigurationClass集合
  parser.parse(candidates);
  //解析后得到的一批集合
  Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
  //加载到容器中
  this.reader.loadBeanDefinitions(configClasses);

loadBeanDefinitions方法会调用到loadBeanDefinitionsForConfigurationClass方法

    private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
            TrackedConditionEvaluator trackedConditionEvaluator) {

        //....
        //与@Import注解相关,后续文章分析
        if (configClass.isImported()) {
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        }
        // 对@Bean注解的到的BeanMethod进行处理
        for (BeanMethod beanMethod : configClass.getBeanMethods()) {
            loadBeanDefinitionsForBeanMethod(beanMethod);
        }
         //与@Import注解相关,后续文章分析
        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }

主要看下loadBeanDefinitionsForBeanMethod方法,其他的和@Import注解有关,暂不分析

    private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
        ConfigurationClass configClass = beanMethod.getConfigurationClass();
        MethodMetadata metadata = beanMethod.getMetadata();
        String methodName = metadata.getMethodName();

        //....
        //获取@Bean注解的元数据信息
        AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
        Assert.state(bean != null, "No @Bean annotation attributes");
        //....
        ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
        beanDef.setResource(configClass.getResource());
        beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
       //设置工厂方法
        if (metadata.isStatic()) {
            beanDef.setBeanClassName(configClass.getMetadata().getClassName());
            beanDef.setFactoryMethodName(methodName);
        }
        else {
            beanDef.setFactoryBeanName(configClass.getBeanName());
            beanDef.setUniqueFactoryMethodName(methodName);
        }
        //....

        this.registry.registerBeanDefinition(beanName, beanDefToRegister);
    }

上面只列出了核心代码,主要是构造了BeanDefinition,然后注册进容器,而BeanDefinition的一些属性则是由注解中获取,这部分代码省略。

另外,可以看到@Bean的方式构造的BeanDefinition的时候,与普通的不同,这种方式是会设置工厂方法去初始化,也就是说,MyConfiguration类下的bean1方法被Spring当成一个工厂方法,也就是说这种方式与下列的初始化方式原理类似:

    <bean id="myConfiguration" 
          class="com.example.springboot.springbootdemo.bean.MyConfiguration"/>
 
    <bean id="bean1" factory-bean="myConfiguration" factory-method="bean1">
    </bean>

如果demo中的bean1方法加上static修饰,就类似xml中配置成静态工厂模式

5 总结

上门介绍了三个注解相关的处理流程,流程中涉及的其他注解也会引入更多的ConfigurationClass,但是涉及篇幅较长,后续会有其他文章再进行分析。

  1. 处理的入口为BeanFactoryPostProcessor类的实现,即ConfigurationClassPostProcessor
  2. 通过配置了@SpringBootApplication的启动类为入口,进行处理
  3. 先获取所有与@Configuration有关的类信息,包括@Bean注解的方法信息,然后再将其转换成BeanDefinition注册到容器中
  4. 获取到一个与@Configuration有关的类的时候,会获取该类上的注解(例如@ComponentScan、@Import),以此引入更多的ConfigurationClass,这里涉及递归处理
  5. 有@Bean注解的方法在解析的时候作为ConfigurationClass的一个属性,最后还是会转换成BeanDefinition进行处理, 而实例化的时候会作为一个工厂方法进行Bean的创建
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容