spring注解:@ComponentScan,@Bean,@Import,@Component

Posted Small leaf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring注解:@ComponentScan,@Bean,@Import,@Component相关的知识,希望对你有一定的参考价值。

我们知道我们要扫描某些包下的注解类,或者某些注解类,可以用@ComponentScan去扫描。我们需要类能被扫描到,可能会用到@Configuration,@Service,@Repository,这三个都使用了@Component注解。实际上扫描的是Component注解。我们也可以用@Bean来定义一个bean,也可以用Import来导入一个bean或者一个资源文件。

那么这些注解spring是如何扫描并且注册到spring容器的呢?

@Configuration
@Import(ImportConfig.class)
public class JavaConfig 



  @Test
    public void test()
        ApplicationContext app = new AnnotationConfigApplicationContext(JavaConfig.class);
        Student student = (Student) app.getBean("student");
        student.say();
    

我们从AnnotationConfigApplicationContext出发

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry 

来看AnnotationConfigApplicationContext的构造函数

    public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) 
        super(beanFactory);
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) 
        this();
        register(annotatedClasses);
        refresh();


    public AnnotationConfigApplicationContext(String... basePackages) 
        this();
        scan(basePackages);
        refresh();
    

进入this.reader = new AnnotatedBeanDefinitionReader(this);

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) 
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, @Nullable Object source) 

        DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
        if (beanFactory != null) 
            if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) 
                beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
            
            if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) 
                beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
            
        

        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(4);

        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        

        if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        

        if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        

        // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
        if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
        

        // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
        if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition();
            try 
                def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                        AnnotationConfigUtils.class.getClassLoader()));
            
            catch (ClassNotFoundException ex) 
                throw new IllegalStateException(
                        "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
            
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
        

        if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
        
        if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
        

        return beanDefs;
    
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) 
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        

以上面的代码查看,ConfigurationClassPostProcessor被加入到了spring容器当中了。

同理AutowiredAnnotationBeanPostProcessor,RequiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor等都被加入了spring容器当中了。

而他们都实现了BeanPostProcessor,从前面两篇spring生命周期来看,在spring生命周期中会执行相应的方法。

spring生命周期

CommonAnnotationBeanPostProcessor讲过@PostConstruct会使用这个类来进行初始化方法。

重点讲解ConfigurationClassPostProcessor类,它会去读取@Component,@Bean,@Import等等。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
        PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor

ConfigurationClassPostProcessor他继承了BeanDefinitionRegistryPostProcessor



/**
 * Extension to the standard @link BeanFactoryPostProcessor SPI, allowing for
 * the registration of further bean definitions <i>before</i> regular
 * BeanFactoryPostProcessor detection kicks in. In particular,
 * BeanDefinitionRegistryPostProcessor may register further bean definitions
 * which in turn define BeanFactoryPostProcessor instances.
 *
 * @author Juergen Hoeller
 * @since 3.0.1
 * @see org.springframework.context.annotation.ConfigurationClassPostProcessor
 */
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor 

我们前面讲过实现BeanDefinitionRegistryPostProcessor,在spring生命周期中执行顺序为

  1. 构造函数
  2. BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
  3. BeanFactoryPostProcessor.postProcessBeanFactory

ConfigurationClassPostProcessor的构造函数是默认的。

ConfigurationClassPostProcessor.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);
    

processConfigBeanDefinitions主要看

//处理相关注册的类
parser.parse(candidates);
//注册这些类到spring容器当中
this.reader.loadBeanDefinitions(configClasses);
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();
    

进入parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    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);
                for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) 
                    if (configClass.equals(it.next())) 
                        it.remove();
                    
                
            
        

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

先来看

if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) 
            return;
        
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) 
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) 
            return false;
        

        if (phase == null) 
            if (metadata instanceof AnnotationMetadata &&
                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) 
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        

        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) 
            for (String conditionClass : conditionClasses) 
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            
        

        AnnotationAwareOrderComparator.sort(conditions);

        for (Condition condition : conditions) 
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) 
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            
            if (requiredPhase == null || requiredPhase == phase) 
                if (!condition.matches(this.context, metadata)) 
                    return true;
                
            
        

        return false;
    

看我们的类有没有使用@Conditional,有使用就进行判断,如果相关条件不通过则丢弃该类,也就是该类不会被注入到spring容器当中。这里就涉及到springboot中很多的条件注解。
关于这个注解以及相关的的条件注解如何实现放在下次讲。

进入sourceClass = doProcessConfigurationClass(configClass, sourceClass);

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

        // 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.warn("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) 
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(
                            holder.getBeanDefinition(), this.metadataReaderFactory)) 
                        parse(holder.getBeanDefinition().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,@ComponentScan,@Import,@ImportResource,@Bean。

这里我着重讲解@Import,其他的看代码就很容器理解,也可以按照我这样一步步分析。

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

        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 (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) 
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(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(ImportConfig.class)

public class ImportConfig 


    @Bean
    public Student student()
        return new Student();
    

ImportConfig只是一个普通的类。

processImports会先判断import中的是ImportSelector还是ImportBeanDefinitionRegistrar,做相应的处理。
关于这两个接口,我想单独拿出来讲解,不放在这里,以免篇幅过长。
读者也可以自己思考。

我们看

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

看注释该类不是ImportSelector或者ImportBeanDefinitionRegistrar我们把它当做@Configuration类来处理。
再执行
processConfigurationClass(candidate.asConfigClass(configClass));
又回到了我们最开始。

我们解析JavaConfig,解析到@Import中的ImportConfig,发现ImportConfig不是ImportSelector和ImportBeanDefinitionRegistrar,我们把它当做Configuration做同样的处理,如此递归下去,直接解析完所有的类。

到这里我们有所的Configuration,Import配置类等等都解析出来了。

来看

this.reader.loadBeanDefinitions(configClasses);

    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) 
        TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
        for (ConfigurationClass configClass : configurationModel) 
            loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        
    

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
            TrackedConditionEvaluator trackedConditionEvaluator) 

        if (trackedConditionEvaluator.shouldSkip(configClass)) 
            String beanName = configClass.getBeanName();
            if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) 
                this.registry.removeBeanDefinition(beanName);
            
            this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
            return;
        

        if (configClass.isImported()) 
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        
        for (BeanMethod beanMethod : configClass.getBeanMethods()) 
            loadBeanDefinitionsForBeanMethod(beanMethod);
        
        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    

这里就把我们的配置类注册到spring容器当中了。

回顾一下:
ConfigurationClassPostProcessor使我们实现注册的核心类,在我们项目启动的时候,该类就被加载进来,在spring生命周期中,进行我们配置类的检测,将相关的类注册到spring容器当中。

ConfigurationClassPostProcessor先去扫描@Configuration,然后处理它@PropertySource,@ComponentScan,@Import,@ImportResource,@Bean。递归下去直到扫描完毕。这些类被处理后都放入spring容器当中。

@PropertySource:读取配置文件
@ComponentScan:扫描相应包下的注解类,或者扫描指定的类
@ImportResource:导入资源文件
@Bean:实例化一个bean放入spring容器当中。
@Import:实例化一个指定的类,当这个类没有实现ImportSelector或者ImportBeanDefinitionRegistrar,会把它当中一个普通的@Configuration来处理。

讲到这里相信大家对spring如何加载java注解配置文件已经基本了解。那么关于@Import中的ImportSelector和ImportBeanDefinitionRegistrar他们是用来干嘛的呢?

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default ;

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default ;

springboot中EnableAutoConfiguration自动配置,使用了EnableAutoConfigurationImportSelector

public class EnableAutoConfigurationImportSelector
        extends AutoConfigurationImportSelector 


public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
        BeanFactoryAware, EnvironmentAware, Ordered 

public interface DeferredImportSelector extends ImportSelector

究竟是怎样实现自动配置。

下回分解吧。。。

以上是关于spring注解:@ComponentScan,@Bean,@Import,@Component的主要内容,如果未能解决你的问题,请参考以下文章

Spring注解驱动开发第4讲——自定义TypeFilter指定@ComponentScan注解的过滤规则

spring注解:@ComponentScan,@Bean,@Import,@Component

注解@componentscan

Spring注解——使用@ComponentScan自动扫描组件

@componentscan注解的用法和作用

spring注解第02课 包扫描@ComponentScan@ComponentScans