Cao worker said Spring Boot source (28) - Spring's component-scan mechanism to allow yourself to be simple to achieve, how do

EDITORIAL words

Background and resources:

Cao worker said Spring Boot source (1) - Bean Definition in the end what is, with spring Mind Map Share

Cao worker said Spring Boot source (2) - Bean Definition in the end what is, let's facing interfaces, one by one method to explain

Cao worker said Spring Boot source (3) - Manual registration Bean Definition gaming fun than you, let's try

Cao worker said Spring Boot source (4) - I is how the custom ApplicationContext, read bean definition from the json file?

Cao worker said Spring Boot source (5) - how to read from the bean properties file

Cao worker said Spring Boot source (6) - Spring bean from how to resolve the xml file

Cao worker said Spring Boot source (7) - Spring parse xml file, which in the end got what (a)

Cao worker said Spring Boot source (8) - Spring parse xml file, which in the end got what (util namespace)

Cao worker said Spring Boot source (9) - Spring parse xml file, which in the end got what (context name space)

Cao worker said Spring Boot source (10) - Spring parse xml file, which in the end got what (context: annotation-config parsing)

Cao worker said Spring Boot source (11) - context: component-scan, you really will use it (this is it's clever but useless)

Cao worker said Spring Boot source (12) - Spring parse xml file, which in the end got what (context: component-scan fully resolved)

Spring Boot Cao said source station (13) - weaving (Load-Time-Weaving) AspectJ runtime, the basic content is clear (with source)

Cao worker said Spring Boot source (14) - AspectJ's Load-Time-Weaving of two implementations careful explanations, and how and Spring Instrumentation Integration

Cao worker said Spring Boot source (15) - Spring in the end got what from xml file (context: load-time-weaver fully resolved)

Cao worker said Spring Boot source (16) - Spring in the end got what from xml file (aop: config fully resolved [on])

Cao worker said Spring Boot source (17) - Spring in the end got what from xml file (aop: config fully resolved [in])

Cao worker said Spring Boot source (18) - Spring AOP source code analysis trilogy, finally finished fast (aop: config fully resolved [next])

Cao worker said Spring Boot source (19) - Spring gives us the tools weapon, create a proxy need not worry (ProxyFactory)

Cao worker said Spring Boot source (20) - code network has long arms, how to record each operation log Spring RedisTemplate

Cao worker said Spring Boot source (21) - In order for us to understand Spring Aop weapon ProxyFactory, I have to fight

Cao worker said Spring Boot source (22) - You say I Spring Aop rely AspectJ, I rely on it what the

Cao worker said Spring Boot source (23) - ASM and meritorious, Spring turned out to be such a recursive meta-annotation to obtain comment

Cao worker said Spring Boot source (24) - Spring annotation scanning Swiss Army knife, asm combat technique (on)

Cao worker said Spring Boot source (25) - Spring annotation scanning Swiss Army knife, ASM + Java Instrumentation, incidentally mention Jar package crack

Cao worker said Spring Boot source (26) - Learn byte code is also too difficult, really can not stand it, wrote a little bytecode execution engine

Spring Boot Cao worker said source (27) - Spring a component-scan, various light configurations include-filter properties, enough to play a half

Project code address Mind Mapping address

Engineering Schematic:

Overview

This lecture is relatively independent, we do not love bullshit, but to say this lecture Zuosa.

As we all know, @ Component-scan annotation in the annotation of the times, the most important use is to specify the name of a package, and then spring will be to package the following corresponding scanning annotated @ Controller, @ Service, @ Repository annotation and other categories, then registered as a bean.

spring boot times, this comment is to stand behind the scenes, as follows:

  @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 {
}

In short, this annotation we too familiar. We talk about this, the core goal is to let everyone know better spring, way to do that is to allow ourselves to achieve the following objectives:

  1. Defined main class

    @MyConfiguration
    @MyComponentScan(value = "org.springframework.test")
    public class BootStrap {
        public static void main(String[] args) {
             ...
    }
    

    The above defines two custom annotations have added a prefix "My". Following is a brief look.

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyConfiguration {
    	
    }
    

    This effect is similar to @configuration, means that we are a configuration class, usually have a bunch of other notes on configuration classes, to introduce other bean definition.

    Then MyComponentScan notes:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyComponentScan {
    
        /*
         * 要扫描的包名
         */
        String value();
    
    }
    
  2. Under our target package, we need to have a scan bean, as follows:

    package org.springframework.test;
    
    @MyComponent
    @MyComponentScan(value = "org.springframework.test1")
    public class PersonService {
        private String personname1;
    }
    

    As used herein MyComponent annotation, this annotation, annotation we used to scan for those who like bean. For example, here, we hope PersonService this class are scanned as bean.

    Secondly, we have defined a @MyComponentScan(value = "org.springframework.test1"), this is mainly: We want to be able to support recursive processing.

    Of course, you can ignore the first time being when it does not exist.

  3. The final test results are as follows:

    import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.RootBeanDefinition;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.custom.MyConfigurationClassPostProcessor;
    import org.springframework.custom.annotation.MyComponentScan;
    import org.springframework.custom.annotation.MyConfiguration;
    import org.springframework.test1.AnotherPersonService;
    
    @MyConfiguration
    @MyComponentScan(value = "org.springframework.test")
    public class BootStrap {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
            AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(BootStrap.class);
            context.registerBeanDefinition(BootStrap.class.getName(), beanDefinition);
    
            /**
             * 注册一个beanFactoryPostProcessor,用来处理MyComponentScan注解
             */
            RootBeanDefinition def = new RootBeanDefinition(MyConfigurationClassPostProcessor.class);
            def.setSource(null);
            context.registerBeanDefinition(MyConfigurationClassPostProcessor.class.getName(),
                    def);
    
            context.refresh();
    
            PersonService bean = context.getBean(PersonService.class);
            System.out.println(bean);
        }
    }
    

    Output is as follows:

    12:39:27.259 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personService'
    org.springframework.test.PersonService@71ba5790

You can see, our goal is more than that. To achieve this goal, in fact, we modeled the realization spring he ctrl c / v one, which a lot of simplification.

Realization of ideas

The basic idea is equivalent to the realization of the spring, because the purpose of this series is to let everyone know better Spring, so no need to open a new path.

Notes a starting point for class configuration with @MyConfiguration

Specifies a @MyConfiguration annotated class, a start will be registered as a bean definition, similar to the spring boot the startup class, you know, spring boot, start the class is also an indirect annotated @SpringBootConfiguration, but it @SpringBootConfiguration , we look at:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

The comment is a comment @configuration.

In fact, manually specify a configuration class, this class configuration, there seems to source code is called the startUp config, as a starting point, through this starting point, we can find in this kind of starting point, more yuan notes, for example, in our company. real project, start class configuration on a bunch of things:

@SpringBootApplication
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.xxx.cad.mapper")
@ComponentScan("com.xxx")
@EnableFeignClients
//@Slf4j
@Controller
@EnableScheduling
public class CadWebService {

This class is our starting point here is class. The starting point of the class, then registered as a bean.

A register BeanDefinitionRegistryPostProcessor, @MyComponentScan for processing the configuration class, to find more bean

We all know that @configuration configuration class, how is it being treated? It is through org.springframework.context.annotation.ConfigurationClassPostProcessor.

This class does, implement the following interfaces:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean definition registry after its
	 * standard initialization. All regular bean definitions will have been loaded,
	 * but no beans will have been instantiated yet. This allows for adding further
	 * bean definitions before the next post-processing phase kicks in.
	 * @param registry the bean definition registry used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

This method is called as soon as the interface is in: Up to now, the configuration (whether xml, or as the first step above this, to manually register the bean definition) way to find the bean definition have been found; the original the next step is to find out which of singleton bean eager-init, and to initialize.

But then, before that, we have a step, it can be considered an extension point that allows us to modify the current definition of a collection of bean.

For example, if popular, something like this, for example, a company, organization, everyone went out to play, we volunteered, began to assume a reported 10 people scheduled Saturday departure; Until then, let us confirm the company , we can

  1. Increase, with families, with male and female friends;
  2. Delete, they do not go up
  3. Change, I would also like to write like Saturday bug, but I have another colleague lacks something, he can go

To achieve these, as long as you realize that the interface methods we mentioned earlier can be, we can then observe this method:

	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

The parameters are registry, which is the current registration form, registration form gave you, what you have is not doing?

public interface BeanDefinitionRegistry extends AliasRegistry {
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;
	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	
	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	...
}

Take the above example that several methods:

  1. registerBeanDefinition, this is the person added to the list
  2. removeBeanDefinition, this is their own not to go
  3. BeanDefinition getBeanDefinition (String beanName), this can be found in their own name registration information, change of name, no problem, right?

@Configuration annotation process is dependent on the realization of a BeanDefinitionRegistryclass interface, in this class, it did a lot of things:

If the current list, only the start of our class, right, marked with a lump of notes, what various Enable, what Component-scan it, there. But we label these, such as component-scan, not them the play, is to go to the corresponding package, to help us scan the bean; this is equivalent to saying that, to add people to the list.

The logic can be understood as substantially:

  1. Get the initial list, here it is the bean definition of the start classes, as well as some of the other hand to get into the bean definition

  2. By implementing the BeanDefinitionRegistryof ConfigurationClassPostProcessor, take a look at the first step of the initial list, there is no comment @ Component-scan, if not comment directly return; if the annotation, to the third step;

  3. Get the package name @ component-scan in the configuration to be scanned, and then get all the class in this package, then look at these class full condition (for example, recognize only notes the controller, service, etc. annotations)

  4. The third step out of the screen, to meet the conditions of the class, they qualified themselves, can be used as a bean; and then see if they have no qualifications as a configuration class, I get the following example:

    @Component
    @ComponentScan(value = "xxxx.xxxx")
    public class PersonService {
        private String personname1;
    }
    

    Itself, the above example, PersonService because of the credit component-scan, has been closed for the bean, but this is not the end, because it also notes above the @ComponentScan own annotations, and this, we need to recursive processing.

    Some students might feel a little extreme, maybe, but, following the example of the extreme right:

    @Component
    @Import({MainClassForTestAnnotationConfig.class})
    public class PersonService {
        private String personname1;
    
    

    If you feel @Import extreme, then @ImportResource to import xml file in the bean, this scene, sometimes still encounter it, for example, be compatible with the old program when.

    And I want to say is that in ConfigurationClassPostProcessorthe process of handling @configuration notes, if found to have the following behavior on the class, will be processed recursively:

    1. Inner classes to parse
    2. PropertySource annotation
    3. ComponentScanannotation
    4. Import annotation
    5. ImportResourceannotation
    6. There Beanannotated method
    7. Processing superClass

    Overall, this class is quite difficult, and will be processed recursively.

Our demo today, in order to focus, and to achieve a simple, deal only with the first recursive @ component-scan itself.

Next, take a look at implementation.

Implementation

Test class, the main logic flow of the overall drive


@MyConfiguration
@MyComponentScan(value = "org.springframework.test")
public class BootStrap {
    public static void main(String[] args) {
        // 1
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(BootStrap.class);
        context.registerBeanDefinition(BootStrap.class.getName(), beanDefinition);

        /**
         * 2 注册一个beanFactoryPostProcessor
         */
        RootBeanDefinition def = new RootBeanDefinition(MyConfigurationClassPostProcessor.class);
        def.setSource(null);
        context.registerBeanDefinition(MyConfigurationClassPostProcessor.class.getName(),
                def);
		// 3 
        context.refresh();
		// 4
        PersonService bean = context.getBean(PersonService.class);
        System.out.println(bean);
        
        AnotherPersonService anotherPersonService = context.getBean(AnotherPersonService.class);
        System.out.println(anotherPersonService);
    }
}
  • At 1, the use of the default spring driven context annotations, provided: config starting class for the current class, registered to spring containers
  • At 2, registered a MyConfigurationClassPostProcessor to spring container, similar to this and speaking in front of ConfigurationClassPostProcessor effect, used to resolve our own @MyComponentScan
  • At 3, load context
  • 4, the bean acquisition, detection.

MyConfigurationClassPostProcessor,解析@MyComponentScan

@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("postProcessBeanDefinitionRegistry...");
        
        /**
         * 1: 找到标注了{@link org.springframework.custom.annotation.MyConfiguration}注解的类
         * 这些类就是我们的配置类
         * 我们通过这些类,可以发现更多的bean definition
         */
        Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<BeanDefinitionHolder>();
        for (String beanName : registry.getBeanDefinitionNames()) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (MyConfigurationUtils.checkConfigurationClassCandidate(beanDef)) {
                beanDefinitionHolders.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
		// 2
        if (CollectionUtils.isEmpty(beanDefinitionHolders)) {
            return;
        }
		// 3
        MyConfigurationClassParser parser = new MyConfigurationClassParser(environment,registry);
        parser.parse(beanDefinitionHolders);
    }
  • At 1, find the current, marked notes of all the bean definition MyConfiguration
  • At 2, if there is, return
  • At 3, the first step in the collection found, for further processing

Specific analytical work, who fell on the third step, specifically, is MyConfigurationClassParser class body.

MyConfigurationClassParser specific executor

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            String className = bd.getBeanClassName();

            try {
                processConfigurationClass(className);
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
            }
        }
    }

Here is the class for each configuration found for processing. For example, demo here we find is the startup class.

Then call the following method:


    protected void processConfigurationClass(String className) throws IOException {
        MetadataReader metadataReader = MyConfigurationUtils.getMetadataReader(className);
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

        /**
         * 1. 判断该类上,是否有标注{@link MyComponentScan}
         */
        Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(MyComponentScan.class.getName(), true);
        AnnotationAttributes componentScan = AnnotationAttributes.fromMap(annotationAttributes);

        /**
         * 2. 如果类上有这个{@link MyComponentScan},则需要进行处理
         */
        if (componentScan != null) {
            /**
             * 3. 马上扫描这个base package路径下的bean,在里面,会注册beanDefinition到bean registry
             */
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, annotationMetadata.getClassName());

            /**
             * 4. 如果扫描回来的bean definition不为空,递归处理
             */
            if (!CollectionUtils.isEmpty(scannedBeanDefinitions)) {
                this.parse(scannedBeanDefinitions);
            }
        }

    }
  • At 1, such a determination, whether there is marked @MyComponentScan
  • At 2, if the @MyComponentScan the class, then need to be processed
  • At 3, immediately bean scanning path under the base package, on the inside, is registered to the bean Registry beanDefinition
  • 4, the third step of scanning, bean, recursive processing, find more bean

The third step is the focus here, to a place called the componentScanParser to deal with this componentScanParser in this class initialization time of assignment:

    public MyConfigurationClassParser(Environment environment, BeanDefinitionRegistry registry) 	{
        this.environment = environment;
        this.registry = registry;
        this.componentScanParser = new MyComponentScanParser(componentScanBeanNameGenerator,
                environment,registry);
    }

MyComponentScanParser of the process

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String className) {
        // 1
        String basePackage = componentScan.getString("value");
        // 2
        includeFilters.add(new AnnotationTypeFilter(MyComponent.class));
        // 3
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();

        /**
         * 4 获取包下的全部bean definition
         */
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        /**
         * 5 对扫描回来的bean,进行一定的处理,然后注册到bean registry
         */
        for (BeanDefinition candidate : candidates) {
            String generateBeanName = componentScanBeanNameGenerator.generateBeanName(candidate, registry);

            if (candidate instanceof AbstractBeanDefinition) {
                ((AbstractBeanDefinition)candidate).applyDefaults(this.beanDefinitionDefaults);
            }

            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }

            boolean b = checkCandidate(generateBeanName, candidate);
            if (b) {
                // 6
                beanDefinitions.add(new BeanDefinitionHolder(candidate,generateBeanName));
            }
        }

        /**
         * 7 注册到bean definition registry
         */
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
            registry.registerBeanDefinition(beanDefinitionHolder.getBeanName(),beanDefinitionHolder.getBeanDefinition());
        }

        return beanDefinitions;
    }
  • At 1, get MyComponentScan annotation value information indicating the package to be scanned
  • At 2, set up to identify the bean rule, here is the comment of the @MyComponent, considered to be one of us
  • At 3, the definition of variables, for storing returned results
  • 4, under conditions satisfying all scan package, bean definition
  • At 5, the processing step 4 to get the definition set bean
  • At 6, is added to the result set to be returned
  • At 7, registered with the spring container

Above, at only 4 needed Again, others are relatively simple.

Get to meet the conditions of the bean definition process

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        // 1
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + "/" + this.resourcePattern;
        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
        // 2
        for (Resource resource : resources) {
            log.info("Scanning " + resource);

            if (!resource.isReadable()) {
                continue;
            }
			// 3
            MetadataReader metadataReader = MyConfigurationUtils.getMetadataReader(resource);
            // 4
            if (isCandidateComponent(metadataReader)) {
                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                sbd.setResource(resource);
                sbd.setSource(resource);
                // 5
                candidates.add(sbd);
            }
            else {
                log.info("Ignored because not matching any filter: " + resource);
            }
        }
    }
    ...

    return candidates;
}
  • 1 at the entire class, the scan package
  • At 2, traversing the class
  • At 3, acquire information on the class of notes
  • 4, the use of annotation information, determines whether or not one of us (annotated @MyComponent)
  • At 5, one of us, ready to take away

Wherein, at 4, to achieve the following:

    
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, MyConfigurationUtils.getMetadataReaderFactory())) {
                return true;
            }
        }
        return false;
    }

You are to use includeFilters to match, in front of you remember, we set the bar:

includeFilters.add(new AnnotationTypeFilter(MyComponent.class));

General process is the case.

The realization dubbo, superficial analysis

I found, and like I said above, the difference could not be too much, I have not used dubbo, did not achieve the reference dubbo.

For example, it defines a class that implements a BeanDefinitionRegistryPostProcessor, called:

public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {

Its implementation is as follows:

@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        registerBeans(registry, DubboBootstrapApplicationListener.class);
		// 1
        Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            // 2
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
            }
        }

    }
  • At 1, found to be scanned package
  • At 2, scans the specified package

At 2 above, to achieve the following:

private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        DubboClassPathBeanDefinitionScanner scanner =
                new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
		
        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);

        scanner.setBeanNameGenerator(beanNameGenerator);
		// 1
        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
		// 2
        scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
		// 3
        for (String packageToScan : packagesToScan) {

            // 4 Registers @Service Bean first
            scanner.scan(packageToScan);
			// 5
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
			// 6
            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                    // 7
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }

            }

        }

    }
  • 1, set includeFilters, annotation types, notes the org.apache.dubbo.config.annotation.Servicetype even if
  • 2, or set includeFilters, only for previous compatible
  • 3, traverse to scan package
  • 4, scan the specified package
  • 5, scan package, satisfying the condition set acquired
  • 6, the fifth step is returned not empty, then start at step 7 below to register to spring
  • 7, registered to the spring

to sum up

After the previous explanation, we should immediately more clearly now, if still a little ignorant, it is best to try the demo pulled down.

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-annotation-custom-component-scan

If you think a little help, help point a praise it.

Guess you like

Origin www.cnblogs.com/grey-wolf/p/12632419.html