Back to Javatutorial

ConfigurationClassPostProcessor(一):处理@ComponentScan注解

docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/ConfigurationClassPostProcessor(一):处理@ComponentScan注解.md

1.0.022.6 KB
Original Source

spring ִ֮ BeanFactoryPostProcessor һУִ BeanFactoryPostProcessor УһҪᱻִе ConfigurationClassPostProcessordzҪᴦ spring ࣬ @Component``@PropertySources``@ComponentScans``@ImportResource ע⣬ϵ½ͨʵԴĽǶȷ spring ⼸עĴ

1. ع BeanFactoryPostProcessor ִ

BeanFactoryPostProcessor ִУ:

AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(java.lang.String...)
 |-AbstractApplicationContext#refresh
  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors
   |-PostProcessorRegistrationDelegate
     #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)

PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List) Уε ConfigurationClassPostProcessor ķ

  • invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry)õ BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
  • invokeBeanFactoryPostProcessors(registryProcessors, beanFactory)õ BeanFactoryPostProcessor#postProcessBeanFactory

ConfigurationClassPostProcessor ͬʱʵ BeanDefinitionRegistryPostProcessor BeanFactoryPostProcessorִе ConfigurationClassPostProcessor

/**
 * ִ postProcessBeanDefinitionRegistry(...)
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(...);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(...);
    }
    this.registriesPostProcessed.add(registryId);
    // ֵһ
    processConfigBeanDefinitions(registry);
}

/**
 * ִ postProcessBeanDefinitionRegistry(...) ִ postProcessBeanFactory(...)
 */
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
        throw new IllegalStateException(
                "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
        //  beanFactory ûбִһ processConfigBeanDefinitions 
        // һ£ﲻᱻִе
        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
    // ǿ
    enhanceConfigurationClasses(beanFactory);
    // Ӵ ImportAware ص BeanPostProcessor뱾ϵ󣬲
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

˵£

  • PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List) ִ߼ִ postProcessBeanDefinitionRegistry(...)ִ postProcessBeanDefinitionRegistry(...)
  • postProcessBeanDefinitionRegistry(...) Ҫǵ processConfigBeanDefinitions(...)
  • postProcessBeanFactory(...) жϵǰ beanFactory Ƿִй processConfigBeanDefinitions(...) ûУִ processConfigBeanDefinitions(...) ֮ enhanceConfigurationClasses(...) ǿ

Ϸյõ processConfigBeanDefinitions(...) enhanceConfigurationClasses(...)ǽص㡣

2. spring δ @ComponentScan עģ

2.1 demo ׼

ڷǰ׼ demo:

׼һ࣬ @ComponentScan ע⣺

@ComponentScan("org.springframework.learn.explore.demo02")
public class BeanConfigs {

}

׼ Bean

@Component
public class BeanObj1 {

    public BeanObj1() {
        System.out.println("beanObj1Ĺ췽");
    }

    @Override
    public String toString() {
        return "BeanObj1{}";
    }
}

@Component
public class BeanObj2 {

    public BeanObj2() {
        System.out.println("beanObj2Ĺ췽");
    }

    @Override
    public String toString() {
        return "BeanObj2{}";
    }
}

public class Demo05Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfigs.class);
        Object obj1 = context.getBean("beanObj1");
        Object obj2 = context.getBean("beanObj2");
        System.out.println("obj1:" + obj1);
        System.out.println("obj2:" + obj2);
        System.out.println(context.getBean("beanConfigs"));
    }
}

ֻ demo Ҫ֣ demo gitee/funcy.

У£

beanObj1Ĺ췽
beanObj2Ĺ췽
obj1:BeanObj1{}
obj2:BeanObj2{}
org.springframework.learn.explore.demo05.BeanConfigs@13eb8acf

demo Ϊһз

2.2 ApplicationContext Ĺ췽AnnotationConfigApplicationContext(Class)

ǽ AnnotationConfigApplicationContext Ĺ췽:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    // spring 
    refresh();
}

/**
 * this() ĵ
 */
public AnnotationConfigApplicationContext() {
    // Աиֵ
    // õ`AnnotationConfigApplicationContext(Class)` Բõ
    // õ`AnnotationConfigApplicationContext(String)` ԲŻõ
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

/**
 * register(...) 
 */
@Override
public void register(Class<?>... componentClasses) {
    Assert.notEmpty(componentClasses, "At least one component class must be specified");
    this.reader.register(componentClasses);
}

AnnotationConfigApplicationContext Ĺ췽£

  • this()޲ι췽ҪǸ reader scanner Աֵ
  • register(componentClasses)ע componentbeanFactory Уõ reader.register(...)
  • refresh()spring Ͳ

ִ register(componentClasses); ǰbeanFactory ڵ BeanDefinitionMap £

ִǰ

ִк

ԿbeanConfigs Ѿעᵽ beanDefinitionNames ˡ

spring ֻǰ beanConfigs עᵽ beanDefinitionNames``BeanConfigs new AnnotationConfigApplicationContext(BeanConfigs.class) ģûɨ @ComponentSacn עָİҲ org.springframework.learn.explore.demo05ôɨеأӦ ConfigurationClassPostProcessor УǼ¿

2.3 ࣺConfigurationClassPostProcessor#processConfigBeanDefinitions

ݿƪķֱӽ ConfigurationClassPostProcessor#processConfigBeanDefinitions £

AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class)
 |-AbstractApplicationContext#refresh
  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors
   |-PostProcessorRegistrationDelegate
      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)
    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions

£

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 1\. ȡBeanDefinition
    String[] candidateNames = registry.getBeanDefinitionNames();
    // 2\. ѭcandidateNames飬ʶFullLite
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        // жϵǰBeanDefinitionѾˣ˾Ͳٴ
        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
            // ֻǴ˸logʡ
            ...
        }
        // жǷΪ࣬
        //  1\.  @Configuration ע proxyBeanMethods != false ࣬spring Ϊ Full 
        //  2\.  @Configuration ע proxyBeanMethods == false,  @Component@ComponentScan
        //     @Import@ImportResource@Bean ֮һע࣬spring Ϊ Lite 
        // FullLiteбʶ
        else if (ConfigurationClassUtils
                 .checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
    // ûֱ࣬ӷ
    if (configCandidates.isEmpty()) {
        return;
    }

    // ʡ޹صĴ
    ...

    // dzҪ @Component@Importע
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    // ṹcandidatesҪ࣬alreadyParsedɽ
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        // 3\. ࣬˺ܶ£
        // 磺@Component@PropertySources@ComponentScans@ImportResourceע
        // עһԽеcandidates
        parser.parse(candidates);
        parser.validate();
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);
        //  readerreaderǰApplicationContextеreaderͬһ
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        // 4\. ν
        // ǰ@Importࡢд@Beanķ@ImportResourceԴתBeanDefinition
        this.reader.loadBeanDefinitions(configClasses);
        // configClasses뵽alreadyParsed
        alreadyParsed.addAll(configClasses);

        // ɺ󣬻candidatesգӵġδFullӵcandidates
        candidates.clear();
        // 5\. ؽӵΪ Full δͰӵcandidatesУ´ѭʱٽ
        // עBeanDefinition  candidateNamesбȽ
        // ڵĻ˵µBeanDefinitionע
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            // ѭalreadyParsed뵽alreadyParsedClasses
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    // ¼ӵΪ࣬δͰӵcandidatesУȴ´ѭ
                    if (ConfigurationClassUtils
                            .checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                            !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }

    // ʡ뱾޹صĴ
    ...
}

ʽϷǰȷ spring ļ

  • @Configuration``@Component``@ComponentScan``@Import``@ImportResource ֮һעࣻ
  • Full@Configuration ע proxyBeanMethods != falsespring Ϊ Full
  • Lite@Configuration ע proxyBeanMethods == false, @Component``@ComponentScan``@Import``@ImportResource ֮һע࣬spring Ϊ Lite

Ϸе㳤ܽ¼£

  1. ȡ BeanDefinition ⲽִɺ󣬽£

  2. ѭ candidateNames 飬ʶΪ Full LiteһĹǶӦ BeanDefinition бʶڱʶʲôãں @Configuration עʱٷbeanConfigs û @Configuration ע⣬ Lite ࡣһõ configCandidates £

  3. @Component``@PropertySources``@ComponentScans``@ImportResource עע࣬dzҪص

  4. ν࣬ @Import ࡢд @Bean ķ@ImportResource Դת BeanDefinitionص BeanDefinitionMap У

  5. ؽӵΪ Full ࣬δͰӵ candidates У´ѭʱٽ

Ϸ̾ˣǾĽҲĵ 3

2.4 ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    // ѭ
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // BeanDefinitionAnnotatedBeanDefinitionʵ
            // ǰõ beanConfigsAnnotatedBeanDefinitionʵifķִ
            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 (...) {
            ...
        }
    }
    this.deferredImportSelectorHandler.process();
}

/**
 * parseн
 */
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    // ConfigurationClassmetadatabeanNameİװ
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

ǰ洫 BeanConfigs ᱻװ AnnotatedGenericBeanDefinition AnnotatedBeanDefinition ʵȻͻ ConfigurationClassParser#parse(String, String)ʵûʲôʵԵĹ processConfigurationClass(...)

/**
 * ж֤಻ظ
 * ʵʸɻ doProcessConfigurationClass(...)
 */
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    // жǷҪ @Conditional ע⣬жǷ
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata() ,
             ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    // жǷͲˣݹϵʡ
    if (existingClass != null) {
        ...
    }
    // SourceClass ͬǰ ConfigurationClass һҲǶmetadatabeanNameİװ
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        // doXxx(...) ɻ
        // صݲΪգٴѭ
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    this.configurationClasses.put(configClass, configClass);
}

жǷִȻ do-while ѭִ doProcessConfigurationClass(...)ѭ doProcessConfigurationClass(...) صݲΪգǼ¿

2.5 ࣺConfigurationClassParser#doProcessConfigurationClass

/**
 * ķ
 */
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, 
        SourceClass sourceClass) throws IOException {
    // 1\.  @Component ע⣬ݹ鴦ڲ࣬IJע
    ...

    // 2\. @PropertySourceע⣬IJע
    ...

    // 3\.  @ComponentScan/@ComponentScans ע
    // 3.1 ȡϵ @ComponentScan/@ComponentScans ע
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    // ûдComponentScan߱@ConditionͲٽif
    if (!componentScans.isEmpty() && !this.conditionEvaluator
            .shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        // ѭcomponentScansҲϵ@ComponentScanע
        for (AnnotationAttributes componentScan : componentScans) {
            // 3.2 componentScanParser.parse(...)componentScanIJ
            // componentScan@ComponentScanϵľݣ
            // sourceClass.getMetadata().getClassName()
            Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser
                    .parse(componentScan, sourceClass.getMetadata().getClassName());
            // 3.3 ѭõ BeanDefinitionӦ࣬ݹparse(...)
            // componentScanб@Beanǵķ@ComponentScanע
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                // жBeanDefinitionӦǷΪ
                if (ConfigurationClassUtils
                        .checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    // Եõ࣬parse(...)ٴνн
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }
    // 4\. @Importע⣬IJע
    ...

    // 5\. @ImportResourceע⣬IJע
    ...

    // 6\. @Beanע⣬IJע
    ...

    // 7\. ĸ࣬ processConfigurationClass(...) һѭʱ
    // sourceClass.getMetadata()
    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();
        }
    }
    return null;
}

/**
 * ٴεprocessConfigurationClass(...)н
 * ĿǣҲпб@BeanǵķComponentScanע
 */
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(...) 
    processConfigurationClass(new ConfigurationClass(reader, beanName));
}

ConfigurationClassParser#doProcessConfigurationClass Ƕ @PropertySource``@ComponentScan``@Import``@ImportResource``@Bean ע⣬ǽע @ComponentScan עĴ£

  1. ȡϵ @ComponentScan/@ComponentScans ע⣻
  2. componentScanParser.parse(...) componentScan IJص
  3. ѭõ BeanDefinitionӦ࣬ݹ parse(...) ɨ赽 @Import``@Bean``@ComponentScan ע⣬ݹ parse(...) ʱᱻ

IJڴѾע͵úˣͲ˵ˣֱ @ComponentScan Ľ.

2.6 ĵطComponentScanAnnotationParser#parse

@ComponentScan Ľ ComponentScanAnnotationParser#parse У£

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    // 1\. һɨɨ
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    // 2\. жǷдĬϵ
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
            BeanUtils.instantiateClass(generatorClass));
    // 3\.  @ComponentScan ע
    // 3.1  @ComponentScan  scopedProxy 
    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));
    }
    // 3.2  @ComponentScan  resourcePattern 
    scanner.setResourcePattern(componentScan.getString("resourcePattern"));

    // 3.3  @ComponentScan  includeFilters 
    // addIncludeFilter addExcludeFilter,List<TypeFilter>
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addIncludeFilter(typeFilter);
        }
    }
    // 3.4  @ComponentScan  excludeFilters 
    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<>();
    // 3.5\. @ComponentScan ָ basePackages ԣԵString
    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);
    }
    // 3.6\. @ComponentScan ָ basePackageClasses ԵClass
    //    ֻҪ⼸ͬģ⼸¼ĶԱɨ赽
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 3.7 ϶ûָĬϻڵİΪɨ·
    if (basePackages.isEmpty()) {
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
    // 3.8 ųͰעųִƥʱ򣬻ų
    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
        @Override
        protected boolean matchClassName(String className) {
            return declaringClass.equals(className);
        }
    });
    // 4\. ſʼɨ @ComponentScan ָİ
    // ɨɺ󣬶Է࣬springὫӵbeanFactoryBeanDefinitionMap
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}

Ϸִ£

  1. һɨɨ
  2. жǷдĬϵ
  3. @ComponentScan עԣɨ 1. @ComponentScan scopedProxy 2. @ComponentScan resourcePattern 3. @ComponentScan includeFilters 4. @ComponentScan excludeFilters 5. @ComponentScan basePackages ԣԵ String 6. @ComponentScan basePackageClasses ԣ Ե Class 7. ûָɨĬϻڵİΪɨ· 8. ųͰעųִƥʱ򣬻ų
  4. ClassPathBeanDefinitionScanner#doScan ɨ

գ ClassPathBeanDefinitionScanner#doScan ɰ spring ֮ɨѾϸˣͲٷˡ

ǻص ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ...
    parser.parse(candidates);
    ....
}

ִǰ

ִк

ԿBeanObj1``BeanObj2 Ѿ BeanFactory BeanDefinitionMap

2.7 ܽ

spring @ComponentScan ̵ͽˣλ ConfigurationClassParser#doProcessConfigurationClass ˽ @ComponentScan @Bean``@Import ע⣬ֻ @ComponentScan Ĵ̣ܽ£

  1. ȡϵ @ComponentScan/@ComponentScans ע
  2. н @ComponentScan IJʱȶһȻ @ComponentScan ԣ䣬Щ֮󣬾Ϳʼаɨ
  3. ɨõ࣬Ϊ࣬ͨ parse(...) һε ConfigurationClassParser#doProcessConfigurationClass нһdzҪͱ֤ɨõе @Bean``@Import``@ComponentScan עõ

ˣľȵˣƪ» ConfigurationClassPostProcessor עĴ


ԭӣhttps://my.oschina.net/funcy/blog/4836178 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע