Cao Gong said that Spring Boot source code (28) - Spring's component scan mechanism allows you to carry out simple implementation by yourself, what to do

Words written in the front

Relevant background and resources:

Cao Gong said Spring Boot source code (1) -- what is Bean Definition? Share with spring mind map

Cao Gong said that Spring Boot source code (2) -- what is Bean Definition? Let's explain it to the interface one by one

Cao Gong said that Spring Boot source code (3) -- is it not more fun to register Bean Definition manually than to play games? Let's try it

Cao Gong said that Spring Boot source code (4) -- how do I customize ApplicationContext and read bean definition from json file?

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

Cao Gong said Spring Boot source code (6) -- how Spring parses bean s from xml files

Cao Gong said Spring Boot source code (7) -- what does Spring get from parsing xml files (Part 1)

Cao Gong said Spring Boot source code (8) -- what does Spring get from parsing xml files (util namespace)

Cao Gong said Spring Boot source code (9) -- what does Spring get from parsing xml files (on context namespace)

Cao Gong said Spring Boot source code (10) -- what does Spring get from parsing xml files (context: annotation config parsing)

Cao Gong said that Spring Boot source code (11) -- context: component scan, do you really use it (this time, it's a strange and obscene skill)

Cao Gong said that Spring Boot source code (12) -- what does Spring get from parsing xml files (context: component scan full parsing)

Cao Gong said Spring Boot source code (13) -- load time weaving of AspectJ. The basic content is to make it clear (with source code)

Cao Gong said Spring Boot source code (14) -- two implementation methods of AspectJ's load time weaving and how to integrate with Spring Instrumentation

Cao Gong said that Spring Boot source code (15) -- what does Spring get from the xml file (context: full parsing of load time Weaver)

Cao Gong said that Spring Boot source code (16) -- what does Spring get from the xml file (aop: config full parsing [up])

Cao Gong said that Spring Boot source code (17) -- what does Spring get from the xml file (aop: config full parsing [in])

Cao Gong said that the Spring Boot source code (18) - the Spring AOP source code analysis trilogy is almost finished (aop: config complete analysis [next])

Cao Gong said that Spring Boot source code (19) - Spring brings us tools and sharp tools. It's easy to create an agent (ProxyFactory)

Cao Gong said that the Spring Boot source code (20) - code network location, carelessness, how to record the Spring RedisTemplate every operation log

Cao Gong said that Spring Boot source code (21) -- in order to let you understand the ProxyFactory, a powerful Spring Aop tool, I have spelled

Cao Gong said Spring Boot source code (22) -- you said that Spring Aop depends on AspectJ, what do I depend on it

Cao Gong said that Spring Boot source code (23) -- ASM has made great contributions again. Spring used to recursively obtain meta annotations of annotations

Cao Gong said Spring Boot source code (24) -- Spring annotation scanning Swiss Army knife, asm Technology Practice (I)

Cao Gong said Spring Boot source code (25) -- Swiss Army knife for Spring annotation scanning, ASM + Java Instrumentation, and Jar package cracking by the way

Cao Gong said that Spring Boot source code (26) -- learning bytecode is too hard to bear. He wrote a small bytecode execution engine

Cao Gong said that the Spring Boot source code (27) - Spring component scan, just the various configuration methods of the include filter attribute, is enough to play for half a day

Project code address Mind map address

Engineering structure drawing:

outline

This talk is relatively independent, and we don't like to talk nonsense. Let's just say what we are going to do.

As you know, @ component scan annotation, in the era of annotation, the most important use is to specify a package name, and then spring will go to the corresponding package, scan the classes annotated with @ Controller, @ Service, @ Repository and so on, and register as bean s.

In the spring boot era, this annotation stands 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 a word, we are all familiar with this annotation. In this lecture, the core goal is to let you know more about spring. The way is to let us achieve the following goals by ourselves:

  1. Define main class

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

    Two custom annotations are defined above, all prefixed with "My". Let's take a look.

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

    The effect of this is similar to @ configuration, which means that we are a configuration class. Generally, there will be a bunch of other annotations on the configuration class to introduce other bean definition s.

    Then the MyComponentScan annotation:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyComponentScan {
    
        /*
         * Package name to scan
         */
        String value();
    
    }
    
  2. Under our target package, there is a bean that we need to scan, as follows:

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

    Here we use the MyComponent annotation, which is used to annotate the classes we want to scan as beans. For example, here, we want the PersonService class to be scanned as a bean.

    Secondly, we also define a @ MyComponentScan(value = "org.springframework.test1"), which is mainly: we need to be able to support recursive processing.

    Now, of course, it's time to ignore it when it doesn't 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);
    
            /**
             * Register a beanFactoryPostProcessor to process MyComponentScan annotations
             */
            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);
        }
    }
    

    The 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

As you can see, this is our goal. In order to achieve this goal, in fact, we are imitating the implementation of spring, and we have done a lot of simplification in our own ctrl c/v.

Implementation ideas

The idea is basically the same as the implementation of spring, because the purpose of this series of tutorials is to let you know more about spring, so there is no need to find another way.

Annotate a starting point configuration class with @ MyConfiguration

Specify a class annotated with @ MyConfiguration, which will be registered as bean definition at the beginning, similar to the startup class in spring boot. As you know, in spring boot, the startup class also indirectly annotates @ SpringBootConfiguration. For @ SpringBootConfiguration, take a look:

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

}

This annotation annotates @ configuration.

In fact, it is to manually specify a configuration class, which seems to be called startUp config in the source code. As a starting point, we can find more meta annotations on this starting point class. For example, in our company's real projects, the startup class configures 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 the starting class here. This starting class is then registered as a bean.

Register a BeanDefinitionRegistryPostProcessor to process @ MyComponentScan on the configuration class to discover more beans

You know how the @ configuration configuration class is handled? Through org.springframework.context.annotation.ConfigurationClassPostProcessor.

This class implements 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;

}

The time to call this method of this interface is: up to now, all the bean definitions that can be found have been found through configuration (whether it's xml or manually registering the bean definition as in the first step above); originally, the next step is to find out the single bean to be eager init and initialize it.

However, before that, we have another step, which is also an extension point. Let's modify the current bean definition collection.

If you take a popular example, it's probably like this. For example, a company organizes everyone to go out and play, and everyone signs up voluntarily. At the beginning, assume that 10 people are registered and scheduled to leave on Saturday. Before that, the company will let you confirm that you can

  1. Zeng, with family members, with male and female friends;
  2. Delete, don't go
  3. No, I seem to write bug s on Saturday, but my other colleague is OK. He can go

To realize these, you only need to implement the method of the interface we mentioned earlier. You can observe this method again:

	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

The enrollment is registry, that is, the current application form and application form are given to you. What else can you do?

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 methods for example:

  1. Register bean definition, this is to add people to the list
  2. Remove bean definition, this is not to go
  3. BeanDefinition getBeanDefinition(String beanName). You can use your own name to find the registration information and change your name, OK?

@The processing of Configuration annotation depends on a class that implements the BeanDefinitionRegistry interface. In this class, it does many things:

If there is only our startup class in the current list, right? There are a bunch of annotations on it, including all kinds of Enable and component scan. But we annotate these, such as component scan, not to play, but to go to the corresponding package to help us scan bean s; this is equivalent to adding people to the list.

The general logic can be understood as follows:

  1. Get the initial list. Here is the bean definition of the startup class, as well as some other bean definitions manually obtained

  2. By implementing the configuration class postprocessor of BeanDefinitionRegistry, we can see whether there is an annotation @ component scan in the initial list of step 1. If there is no annotation, we will directly return it. If there is an annotation, we will go to step 3;

  3. Get the name of the package to scan configured in @ component scan, then get all the classes under the package, and then check whether these classes meet the conditions (for example, only those annotated with controller, service, etc.)

  4. Step 3: the classes that meet the conditions are qualified and can be used as bean s. Then, see if they are qualified as configuration classes. Let me take the following example:

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

    In the above example, because of the component scan, the PersonService has been accepted as a bean, but this is not the end, because it also annotates the @ ComponentScan annotation, which requires recursive processing.

    Some students think it's a little extreme, may, but is the following example extreme:

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

    If you think @ Import is extreme, then @ ImportResource will Import the bean s in the xml file. In this scenario, you may encounter it sometimes, for example, when you want to be compatible with old programs.

    What I want to say is that in the process of processing @ configuration annotation by ConfigurationClassPostProcessor, if the following behaviors are found on this class, it will be processed recursively:

    1. Inner class parsing first
    2. PropertySource annotation
    3. ComponentScan annotation
    4. Import annotation
    5. ImportResource annotation
    6. Method with Bean annotation
    7. Process superClass

    In general, this class is relatively difficult, and it will be processed recursively.

In our demo today, in order to focus and realize simplicity, we only deal with the recursion of @ component scan itself.

Next, let's look at the implementation.

Concrete realization

Test class, main logic, driving the overall process


@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 Register a 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);
    }
}
  • 1. Use the default annotation driven context of spring to set the start class of config as the current class and register it to the spring container
  • 2, register a MyConfigurationClassPostProcessor to the spring container. This is similar to the previous ConfigurationClassPostProcessor, which is used to parse our own @ MyComponentScan
  • 3 places, load context
  • 4, get the bean and check the effect.

MyConfigurationClassPostProcessor, parsing @ MyComponentScan

@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("postProcessBeanDefinitionRegistry...");
        
        /**
         * 1: Find the class annotated with {@ link org.springframework.custom.annotation.MyConfiguration}
         * These are our configuration classes
         * Through these classes, we can find more bean definition s
         */
        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);
    }
  • 1. Find the current bean definition with MyConfiguration annotation
  • 2 places, if not, return
  • 3 places, for the set found in the first step, proceed to the next step

The specific parsing work falls on the third step, specifically the MyConfigurationClassParser class.

Specific executor of MyConfigurationClassParser

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 processing of each found configuration class. For example, what we find in demo here is the startup class.

Then the following method is called:


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

        /**
         * 1. Determine whether there is a label {@ link MyComponentScan} on this class
         */
        Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(MyComponentScan.class.getName(), true);
        AnnotationAttributes componentScan = AnnotationAttributes.fromMap(annotationAttributes);

        /**
         * 2. If there is {@ link MyComponentScan} on the class, it needs to be processed
         */
        if (componentScan != null) {
            /**
             * 3. Scan the bean under the base package path immediately. In it, beanDefinition will be registered to bean registry
             */
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, annotationMetadata.getClassName());

            /**
             * 4. If the scanned bean definition is not empty, recursive processing is performed
             */
            if (!CollectionUtils.isEmpty(scannedBeanDefinitions)) {
                this.parse(scannedBeanDefinitions);
            }
        }

    }
  • 1 place, judge whether there is annotation @ MyComponentScan on this class
  • If @ MyComponentScan exists on the class, it needs to be processed
  • 3 places, scan the bean under the base package path immediately, in which beanDefinition will be registered to bean registry
  • 4 places, scan the third step, get the bean, recursively process, find more beans

The third step here is to give a component scanparser to handle. This component scanparser is assigned during initialization of this class:

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

Process of MyComponentScanParser

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 Get all bean definition s under the package
         */
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        /**
         * 5 Process the scanned bean and register it in 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 Register to bean definition registry
         */
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
            registry.registerBeanDefinition(beanDefinitionHolder.getBeanName(),beanDefinitionHolder.getBeanDefinition());
        }

        return beanDefinitions;
    }
  • 1 place, get the value information in MyComponentScan annotation, indicating the package to be scanned
  • 2. Set the rules for identifying bean s. Here, annotate @ MyComponent as your own person
  • 3 places, define variables to store the returned results
  • There are 4 places. If all the conditions under the scan package are met, the bean definition
  • 5 places, process the bean definition set obtained in step 4
  • 6 places, added to the result set to be returned
  • 7, register to spring container

Above, only the fourth part needs to be explained again, others are relatively simple.

The process of getting the qualified bean definition

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 place, scan all class es under the package
  • 2 places, traverse class
  • 3 places, get the annotation information of the class
  • 4 places, use the annotation information to judge whether it is your own person (annotated @ MyComponent)
  • Five, my own, ready to go

Among them, the fourth part is realized as follows:

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

It is to use includeFilters to match. Remember, we set it up:

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

This is the general process.

Implementation in dubbo, shallow analysis

I found that, as I said above, it's not much worse. I haven't used dubbo, and I really haven't referred to the implementation of dubbo.

For example, it also defines an implementation class of BeanDefinitionRegistryPostProcessor, which is called:

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

It 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!");
            }
        }

    }
  • 1 place, find the package to scan
  • 2, scan the specified package

The above two parts are realized as follows:

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 type, and org.apache.dubbo.config.annotation.Service type
  • 2. Still set includeFilters to be compatible with the previous ones
  • 3. Traverse the package to be scanned
  • 4. Scan the specified package
  • 5. Scan the package to get the set meeting the conditions
  • 6. Step 5 return is not empty, then start to register to spring in step 7 below
  • 7. Register to spring

summary

After the previous explanation, you should know better immediately. If you are still a little confused, you'd better pull down the demo to try.

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

If you think it's a little bit helpful, please give me a compliment.

Tags: Spring xml Dubbo JSON

Posted on Sun, 05 Apr 2020 08:08:48 -0700 by pido