Spring源碼解析----@Configuration洋闽、@SpringBootApplication玄柠、@Bean

這篇文章主要分析一下這幾個注解的原理。

SpringBoot中這幾個注解關(guān)系比較緊密诫舅,少了@SpringBootApplication注解羽利,SpringBoot很多功能都沒法使用,所以文章分析的內(nèi)容涉及了該注解
另外還有個問題也與@SpringBootApplication有關(guān):

  • 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幾個注解的功能(當然也可以不使用該注解,直接把該注解上的其他注解直接放到啟動類上)

在后續(xù)的處理當中褒颈,當判斷一個類上是否有某個注解的時候柒巫,即使一個類是間接依賴的某個注解,那么這種情況也是符合的谷丸,也會將該注解的信息給提取出來堡掏,例如,判斷啟動類是否擁有@Configuration注解刨疼,流程如下:

  1. 首先判斷啟動類上是是否有有@Configuration注解泉唁,結(jié)果沒有
  2. 判斷@SpringBootConfiguration注解上是否有@Configuration注解注解,結(jié)果沒有
  3. 獲取@SpringBootConfiguration注解上的所有注解揩慕,判斷這些注解上是否有@Configuration注解亭畜,重復類似23的流程

由于@SpringBootConfiguration注解上有個@SpringBootConfiguration注解,而該注解上有@Configuration注解迎卤,所以該查找成功

3 解析入口

上文分析了拴鸵,執(zhí)行run方法后,最后會調(diào)用到org.springframework.context.support.AbstractApplicationContext#refresh方法蜗搔,這是Spring核心的方法劲藐,這篇文章主要分析幾個注解的實現(xiàn),所以這里只展示相關(guān)的代碼:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        //這里就是核心邏輯的入口樟凄,主要是對BeanFactoryPostProcessor的處理
        invokeBeanFactoryPostProcessors(beanFactory);
    }

BeanFactoryPostProcessor:類似于BeanPostProcessor聘芜,BeanPostProcessor主要用來后置處理Bean的,而BeanFactoryPostProcessor則是用來在Bean初始化完成之前缝龄,用來操作BeanFactory的厉膀,兩者都是Spring開放的擴展點,用來擴展對應的功能

invokeBeanFactoryPostProcessors內(nèi)將邏輯委托給PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法二拐,該方法內(nèi)部則會對所有的BeanFactoryPostProcessor進行排序并調(diào)用BeanFactoryPostProcessor接口對應的方法服鹅。

BeanFactoryPostProcessor具體處理這里不再詳細展開,只需要知道@Configuration處理邏輯在某個BeanFactoryPostProcessor中百新,這個類就是ConfigurationClassPostProcessor

找到該類企软,發(fā)現(xiàn)其實現(xiàn)的接口是BeanDefinitionRegistryPostProcessor而不是BeanFactoryPostProcessor,而BeanDefinitionRegistryPostProcessor的父接口是BeanFactoryPostProcessor饭望,和BeanFactoryPostProcessor其實差不多仗哨,如果實現(xiàn)了該接口,會先調(diào)用BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法铅辞,再調(diào)用BeanFactoryPostProcessor接口的postProcessBeanFactory方法

4 ConfigurationClassPostProcessor

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法就是核心所在厌漂,其中會調(diào)用到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有關(guān)系的BeanDefinition
  2. 以該BeanDefinition為起點,進行解析操作,得到解析結(jié)果集
  3. 將解析到的結(jié)果集加載到容器中旨椒,即構(gòu)造成一個BeanDefinition放到容器中待初始化

這里還有幾個注意點:

  1. 在上面的第1步的時候晓褪,能取到的數(shù)據(jù)為所有的BeanFactoryPostProcessor以及我們的main方法的類SpringBootDemoApplication。BeanFactoryPostProcessor當然不奇怪综慎,而為什么還有SpringBootDemoApplication呢涣仿?回顧一下上篇文章說的啟動流程,在load方法中把SpringBootDemoApplication已經(jīng)加載進容器了示惊。另外這個時候好港,configCandidates只有一個元素,即SpringBootDemoApplication
  2. 結(jié)合我們的demo米罚,可以猜測媚狰,第2步得到的結(jié)果集中,應該包括MyConfiguration阔拳,可能包括Bean1崭孤,如果這里沒包括的話可能是以別的形式獲取,這樣MyConfiguration和Bean1才能在后面Bean初始化的時候被創(chuàng)建糊肠,而具體怎么解析的辨宠,后續(xù)會分析到。

4.1 判斷類是否與@Configuration有關(guān)

在上面第1步中货裹,有@Configuration注解的會加入到集合當中嗤形,這個判斷是在ConfigurationClassUtils#checkConfigurationClassCandidate當中實現(xiàn)

    public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
        String className = beanDef.getBeanClassName();
        if (className == null || beanDef.getFactoryMethodName() != null) {
            return false;
        }
        //獲取注解元數(shù)據(jù)信息
        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相關(guān)
        // 該方法還會判斷該注解上的注解是否有@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));
    }

參數(shù)為ConfigurationClass漓藕,即配置有@Configuration注解的類對應的ConfigurationClass對象陶珠。

最后將邏輯交由doProcessConfigurationClass處理,該方法會通過配置有ConfigurationClass對象去獲取額外引入的類(也可能沒有引入)享钞。

最后該方法會又會返回SourceClass對象揍诽,直到返回的對象為空才結(jié)束解析。
這里是為了解決有父類的情況栗竖,假設(shè)SpringBootDemoApplication有父類暑脆,那么這里返回的SourceClass為其父類,接著進行解析

解析完成后狐肢,將ConfigurationClass對象放到Map中添吗,表示需要加載到容器中的ConfigurationClass對象集合,后續(xù)會獲取該Map的元素加載到容器中

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

        // 內(nèi)部類的處理
        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());
                // 掃描出來的類碟联,又調(diào)用了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);
                // 返回父類僵腺,讓程序繼續(xù)處理父類的注解
                return sourceClass.getSuperClass();
            }
        }

        // 如果沒有父類鲤孵,那么返回null表示不需要繼續(xù)處理
        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有關(guān)的
            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注解有關(guān)的類

4.2.2 @ComponentScan注解處理

首先會調(diào)用AnnotationConfigUtils.attributesForRepeatable方法獲取@ComponentScan@ComponentScans注解信息(該信息在@SpringBootApplication注解上裤纹,間接獲取到),當獲取到ComponentScan屬性后丧没,會調(diào)用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));
        }
        //如果上面兩個屬性都沒配置漆际,則以參數(shù)declaringClass所在的包作為掃描路徑
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

        //....
        //開始掃描并注冊
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }

這里只列出了包掃描相關(guān)的代碼,可以看到夺饲,獲取包路徑有三種形式奸汇,

  1. 在@ComponentScan的basePackages屬性配置包路徑
  2. 在@ComponentScan的basePackageClasses屬性配置類信息施符,掃描路徑取自該類所在包的路徑
  3. 12都沒填寫,那么取自聲明該注解的類所在包作為掃描路徑

從這里可以看到SpringBoot即使不配置掃描路徑也是可以正常跑的(當然也可以在@SpringBootApplication上特意配置指定信息)擂找,這時候會走到第3步戳吝,將啟動類所在的包作為掃描路徑,而這有個前提就是其他需要掃描的包需要放到啟動類所在的包路徑以下贯涎,否則將掃描不到

4.2.3 @Bean注解處理

以Demo為例听哭,通過@SpringBootApplication的ConfigurationClass為入口,掃描得到自定義的配置有@Configuration注解的MyConfiguration類塘雳,然后又調(diào)用parse方法進行解析陆盘,最后又會調(diào)用到doProcessConfigurationClass方法,只不過參數(shù)ConfigurationClass對象對應的是MyConfiguration败明,這時候運行到retrieveBeanMethodMetadata方法的時候隘马,會獲取MyConfiguration下配置了@Bean注解的方法,然后進行處理妻顶。

        //獲取所有配置了@Bean注解的方法元數(shù)據(jù)信息
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        //將其封裝成BeanMethod酸员,并放入到ConfigurationClass對象的集合中待后續(xù)處理
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

這里只是將配置了@Bean方法的信息收集起來,并沒有做特殊處理

4.3 將ConfigurationClass信息加載到容器中

回到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法讳嘱,當調(diào)用完parse方法之后幔嗦,能得到一批ConfigurationClass集合,但是這時候只是獲取到呢燥,而容器中還沒有對應的注冊信息崭添,那么接下來就是對這批集合進行注冊處理

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

loadBeanDefinitions方法會調(diào)用到loadBeanDefinitionsForConfigurationClass方法

    private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
            TrackedConditionEvaluator trackedConditionEvaluator) {

        //....
        //與@Import注解相關(guān)叛氨,后續(xù)文章分析
        if (configClass.isImported()) {
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        }
        // 對@Bean注解的到的BeanMethod進行處理
        for (BeanMethod beanMethod : configClass.getBeanMethods()) {
            loadBeanDefinitionsForBeanMethod(beanMethod);
        }
         //與@Import注解相關(guān)呼渣,后續(xù)文章分析
        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }

主要看下loadBeanDefinitionsForBeanMethod方法,其他的和@Import注解有關(guān)寞埠,暫不分析

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

        //....
        //獲取@Bean注解的元數(shù)據(jù)信息
        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()));
       //設(shè)置工廠方法
        if (metadata.isStatic()) {
            beanDef.setBeanClassName(configClass.getMetadata().getClassName());
            beanDef.setFactoryMethodName(methodName);
        }
        else {
            beanDef.setFactoryBeanName(configClass.getBeanName());
            beanDef.setUniqueFactoryMethodName(methodName);
        }
        //....

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

上面只列出了核心代碼屁置,主要是構(gòu)造了BeanDefinition,然后注冊進容器仁连,而BeanDefinition的一些屬性則是由注解中獲取蓝角,這部分代碼省略。

另外饭冬,可以看到@Bean的方式構(gòu)造的BeanDefinition的時候使鹅,與普通的不同,這種方式是會設(shè)置工廠方法去初始化昌抠,也就是說患朱,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中配置成靜態(tài)工廠模式

5 總結(jié)

上門介紹了三個注解相關(guān)的處理流程裁厅,流程中涉及的其他注解也會引入更多的ConfigurationClass冰沙,但是涉及篇幅較長,后續(xù)會有其他文章再進行分析执虹。

  1. 處理的入口為BeanFactoryPostProcessor類的實現(xiàn)拓挥,即ConfigurationClassPostProcessor
  2. 通過配置了@SpringBootApplication的啟動類為入口,進行處理
  3. 先獲取所有與@Configuration有關(guān)的類信息袋励,包括@Bean注解的方法信息侥啤,然后再將其轉(zhuǎn)換成BeanDefinition注冊到容器中
  4. 獲取到一個與@Configuration有關(guān)的類的時候,會獲取該類上的注解(例如@ComponentScan插龄、@Import)愿棋,以此引入更多的ConfigurationClass科展,這里涉及遞歸處理
  5. 有@Bean注解的方法在解析的時候作為ConfigurationClass的一個屬性蓝厌,最后還是會轉(zhuǎn)換成BeanDefinition進行處理徽千, 而實例化的時候會作為一個工廠方法進行Bean的創(chuàng)建
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驯杜,更是在濱河造成了極大的恐慌,老刑警劉巖玩般,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爆袍,死亡現(xiàn)場離奇詭異,居然都是意外死亡坞琴,警方通過查閱死者的電腦和手機哨查,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剧辐,“玉大人寒亥,你說我怎么就攤上這事∮兀” “怎么了溉奕?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長忍啤。 經(jīng)常有香客問我加勤,道長,這世上最難降的妖魔是什么同波? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任鳄梅,我火速辦了婚禮,結(jié)果婚禮上未檩,老公的妹妹穿的比我還像新娘戴尸。我一直安慰自己,他們只是感情好讹挎,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布校赤。 她就那樣靜靜地躺著吆玖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪马篮。 梳的紋絲不亂的頭發(fā)上沾乘,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音浑测,去河邊找鬼翅阵。 笑死,一個胖子當著我的面吹牛迁央,可吹牛的內(nèi)容都是我干的掷匠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼岖圈,長吁一口氣:“原來是場噩夢啊……” “哼讹语!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜂科,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顽决,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后导匣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體才菠,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年贡定,在試婚紗的時候發(fā)現(xiàn)自己被綠了赋访。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡缓待,死狀恐怖蚓耽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情命斧,我是刑警寧澤田晚,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站国葬,受9級特大地震影響贤徒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汇四,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一接奈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧通孽,春花似錦序宦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潘明。三九已至,卻和暖如春秕噪,著一層夾襖步出監(jiān)牢的瞬間钳降,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工腌巾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遂填,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓澈蝙,卻偏偏與公主長得像吓坚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子灯荧,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容