Spring Master's Road 10 - Unlocking a New Perspective of Spring Component Scanning

  First, we'll explore some of the advanced features of the Springframework , specifically component scanning. Component scanning is an important feature in the framework, which can automatically detect and instantiate classes with specific annotations (such as , , etc.) and register them as in the context . Here, we will clarify these concepts through some detailed examples and show how to use these features in real code.IOCInversion of ControlSpring@Component@Service@ControllerSpringbean

1. Component scan path

@ComponentScanAnnotations are used to specify Springthe package paths that need to be scanned at startup to automatically discover and register components.

We set the component scan path in two ways:

  1. Directly specify the package name : For example , it @ComponentScan("com.example.demo")is equivalent to scanning all classes under the specified package and looking for components with annotations such as , , etc., and then registering these components as containers .@ComponentScan(basePackages = {"com.example.demo"})Spring@Component@Service@RepositorySpringbean

  2. Specify the package containing a specific class : for example @ComponentScan(basePackageClasses = {ExampleService.class}), the package in which the class is located and all its sub-packages Springwill be scanned .ExampleService

Next, a complete example is given of how to use the second method to set the component scan path. This can be @ComponentScan的basePackageClassesachieved by setting properties. For example:

@Configuration
@ComponentScan(basePackageClasses = {
    
    ExampleService.class})
public class BasePackageClassConfiguration {
    
    
}

The above code indicates that the package where the class is located and all its sub-packages Springwill be scanned .ExampleService

The entire code is as follows:

First, we create a ExampleServiceservice class called

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class ExampleService {
    
    
}

Then beancreate it in the directoryDemoDao

package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class DemoDao {
    
    
}

Then set the component scan path in the configuration class

package com.example.demo.configuration;

import com.example.demo.service.ExampleService;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackageClasses = ExampleService.class)
public class BasePackageClassConfiguration {
    
    
}

We will also create a DemoApplicationclass named which will start Springthe context and print out all registered beannames.

package com.example.demo;

import com.example.demo.configuration.BasePackageClassConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext ctx = new
                AnnotationConfigApplicationContext(BasePackageClassConfiguration.class);
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        Arrays.stream(beanDefinitionNames).forEach(System.out::println);
    }
}

Run the method DemoApplicationof the above class mainand you will see all registered names on the console bean, including the one we just created ExampleService.

Insert image description here

Now, if we ExampleServicecreate a new class (for example TestService) under the package where the class is located or any of its sub-packages, then this component class will also be automatically registered as one bean. That's basePackageClasseswhat the attribute does: it tells Springwhich packages and their subpackages are to be scanned.

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class TestService {
    
    
}

If you run DemoApplicationthe class mainmethod again, you will see that TestServiceit is also printed, indicating that it has also been successfully registered as one bean.

Insert image description here

We can see that this DemoDaohas never been scanned. Let’s take a look at @ComponentScanthe source code of the annotation.

Insert image description here

  You can see basePackageClassesthat the attribute is an array type. Some people may wonder. @ComponentScan(basePackageClasses = ExampleService.class)What we just wrote did not use an array. Why can it still run normally here?

  If the array contains only one element, you can omit the curly braces of the array when assigning {}, which is Javaa kind of syntactic sugar. In this case, the compiler will automatically wrap the element into an array.

For example, the following two ways of writing are equivalent:

@ComponentScan(basePackageClasses = {
    
    ExampleService.class})

and

@ComponentScan(basePackageClasses = ExampleService.class)

  In both cases above, ExampleService.classit will be wrapped into an array containing only one element. This is Javaa convenient feature of the syntax that makes code look cleaner when there is only one element.

So in order for DemoDaothe component to be scanned, we can basePackageClassesadd DemoDaothe class to the attribute, so that DemoDaothe package where the component is located and its sub-packages can be scanned.

package com.example.demo.configuration;

import com.example.demo.bean.DemoDao;
import com.example.demo.service.ExampleService;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackageClasses = {
    
    ExampleService.class, DemoDao.class})
public class BasePackageClassConfiguration {
    
    
}

operation result

Insert image description here

@ComponentScanThere is a lot of source code for annotations, we will use them later.


2. Filter components by annotation (included)

  In addition to basic package path scanning, it Springalso provides a filtering function, allowing us to only include or exclude classes with specific annotations by setting filtering rules. This filtering function is very useful for module division in large projects. It can finely control the Springcomponent scanning range and optimize the project startup speed.

SpringAnnotation inclusion filtering can be implemented through the properties in @ComponentScan,   and includeFiltersonly classes with specific annotations will be included.

In the following example, we will create some components with specific annotations and set up a configuration class to scan them.

The entire code is as follows:

Create a new annotation Species:

package com.example.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

Next, we will create three different components, two of which contain Speciesannotations:

package com.example.demo.bean;

import com.example.demo.annotation.Species;
import org.springframework.stereotype.Component;

@Species
public class Elephant {
    
    
}

ElephantThe class is @Speciesmodified, not @Componentmodified.

package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Monkey {
    
    
}

Monkeyonly @Componentmodified

package com.example.demo.bean;

import com.example.demo.annotation.Species;
import org.springframework.stereotype.Component;

@Component
@Species
public class Tiger {
    
    
}

As shown above, Tigerthere are @Componentand @Speciesmodifications.

Next, we need to create a configuration class to scan and include annotated Speciescomponents:

package com.example.demo.configuration;

import com.example.demo.annotation.Species;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = "com.example.demo",
        includeFilters = @Filter(type = FilterType.ANNOTATION, classes = Species.class),
        useDefaultFilters = false)
public class FilterConfiguration {
    
    
}

  In this configuration class, we set the @ComponentScanannotation includeFiltersattribute, FilterType.ANNOTATIONwhich represents filtering by annotation. This is used to scan all Speciescomponents with annotations. Note that useDefaultFilters = falseit means that the default filtering rules are disabled and only Speciescomponents marked with will be included.

Some people may have questions, useDefaultFilters = falseindicating that the default filtering rules are disabled. What are the default filtering rules?

In Spring, when using @ComponentScanannotations for component scanning, Springdefault filtering rules are provided. These default rules include the following types of annotations:

  • @Component
  • @Repository
  • @Service
  • @Controller
  • @RestController
  • @Configuration

useDefaultFiltersWhen the attribute   is not written by default , useDefaultFiltersthe value of the attribute is true. SpringDuring component scanning, components marked with the above annotations will be included by default. If it is useDefaultFiltersset to false, Springonly components with explicitly specified filtering rules will be scanned, and components with the above default rules will no longer be included. components. So, useDefaultFilters = falseit's telling Springus that we only want custom component scan rules.

Finally, we create a main program that instantiates the application context and lists all Beanthe names:

package com.example.demo;

import com.example.demo.configuration.FilterConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(FilterConfiguration.class);
        String[] beanNames = ctx.getBeanDefinitionNames();
        for (String beanName : beanNames) {
    
    
            System.out.println(beanName);
        }
    }
}

  When we run this program, we will see that the output Beannames only include Speciesclasses marked by annotations. In this example you will see Tigerand Elephant, but not Monkeybecause our configuration only contains Speciesclasses annotated with .

operation result:

Insert image description here

If useDefaultFiltersthe property is set to true, then the names output when running the program Beanwill be included Monkey.

  Summary: The above describes how to use Springattributes and attributes @ComponentScanin annotations to filter and scan classes with specific annotations. By customizing annotations and setting relevant properties in configuration classes, you can precisely control which classes are scanned and managed by the container. If set to , only components with explicitly specified filtering rules will be scanned, and components with default rules (such as , etc.) will no longer be included.includeFiltersuseDefaultFiltersSpringuseDefaultFiltersfalseSpring@Component@Service


3. Filter components by annotation (exclude)

  In Springthe framework, we can not only set classes containing specific annotations through the properties @ComponentScanof the annotations , but also exclude classes with specific annotations through properties. This feature is very useful for loading our custom modules. In this way, we can precisely control which components are loaded into the container . Below we will use a specific example to illustrate how to use the attribute to exclude classes with specific annotations.includeFiltersexcludeFiltersSpringIOC@ComponentScanexcludeFilters

The entire code is as follows:

First, create an annotation Exclude:

package com.example.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

Define three classes Elephant, Monkey,Tiger

package com.example.demo.bean;

import com.example.demo.annotation.Exclude;
import org.springframework.stereotype.Component;

@Component
@Exclude
public class Elephant {
    
    
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Monkey {
    
    
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Tiger {
    
    
}

Note that these classes are marked with @Componentannotations Elephanton the classes @Exclude.

Next, we create the configuration class FilterConfiguration, use the annotation in it @ComponentScan, and excludeFiltersexclude all @Excludeclasses marked with via the attribute:

package com.example.demo.configuration;

import com.example.demo.annotation.Exclude;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = "com.example.demo",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Exclude.class))
public class FilterConfiguration {
    
    
}

In this way, in Spring IOCthe container, only Tigerthe and Monkeyclasses will be scanned and instantiated, because Elephantthe class is marked @Excludeby the annotation, and we FilterConfigurationexclude all @Excludeclasses marked by the annotation in the class.

The main program is:

package com.example.demo;

import com.example.demo.configuration.FilterConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(FilterConfiguration.class);
        String[] beanNames = ctx.getBeanDefinitionNames();
        for (String beanName : beanNames) {
    
    
            System.out.println(beanName);
        }
    }
}

operation result:

Insert image description here

  Summary: This section mainly explains how to filter annotations through annotation properties in Springthe framework to accurately control the components loaded into the container. In the example in this section, we first create an annotation named , and then define three classes , , , and , which are all marked as , where there is also an annotation on the class . Next, we created a configuration class using the annotation and excluding all classes marked with via attributes. Therefore, when the program runs, only the and classes in the container will be scanned and instantiated, and because the classes are marked with annotations, they are excluded.@ComponentScanexcludeFiltersSpring IOCExcludeElephantMonkeyTiger@ComponentElephant@ExcludeFilterConfiguration@ComponentScanexcludeFilters@ExcludeSpring IOCTigerMonkeyElephant@Exclude


4. Filter components through regular expressions

  In Springthe framework, in addition to specifying annotations to include and exclude class loading, we can also use regular expressions to filter components. This approach provides a more flexible way to select Spring IOCclasses that need to be managed by the container. Specifically, you can use regular expressions to include or exclude classes whose names match a specific pattern. Below, we will use a concrete example to show how to use regular expression filtering to only include classes whose class names end with a specific string.

The following example will demonstrate how to include only Tigerclasses whose class names end with .

The entire code is as follows:

Define three classes Tiger, Elephant,Monkey

package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Elephant {
    
    
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Monkey {
    
    
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Tiger {
    
    
}

Then we create the configuration class FilterConfiguration, use @ComponentScanthe annotation, and includeFiltersinclude the classes whose class names Tigerend with through the attribute:

package com.example.demo.configuration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = "com.example.demo",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Tiger"))
public class FilterConfiguration {
    
    
}

  In the above example, we use FilterType.REGEXthe filter type and specify the regular expression patterns to include ".*Tiger". As a result, only Tigerclasses will be Spring的IOCscanned and instantiated by the container, because only Tigerthe class name of the class satisfies the regular expression ".*Tiger". Here .represents any single character (except line breaks), *represents the previous character repeated any number of times, and .*combined means matching any number of any characters.

Main program:

package com.example.demo;

import com.example.demo.configuration.FilterConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(FilterConfiguration.class);
        String[] beanNames = ctx.getBeanDefinitionNames();
        for (String beanName : beanNames) {
    
    
            System.out.println(beanName);
        }
    }
}

operation result

Insert image description here

  Summary: This section describes how to Springuse regular expressions to filter components in the framework to select which classes should be Spring IOCmanaged by the container. In the example given, three classes Elephant, Monkeyand , are first defined Tiger. Then I created a configuration class FilterConfiguration, used @ComponentScanannotations, and includeFiltersset a regular expression through properties " .*Tiger"to select "Tiger"classes whose class names end with. So when running the main program, Springthe IOCcontainer will only scan and instantiate Tigerclasses, because only Tigerthe class name of the class satisfies the regular expression " .*Tiger".


5. Assignable type filter component

  " AssignableType filtering" is Springa filtering strategy used by the framework when scanning components. This strategy allows us to specify one or more classes or interfaces, and then Springinclude or exclude all classes that can be assigned to these classes or interfaces. If we specify a specific interface, then all classes that implement this interface will be included (or excluded). Likewise, if a specific class is specified, all classes that inherit from this class will also be included (or excluded).

In the following example, we will use " Assignabletype filtering" to include all classes that implement Animalthe interface.

The entire code is as follows:

First, we define an Animalinterface

package com.example.demo.bean;

public interface Animal {
    
    
}

Then define three classes: Elephant, Monkeyand Tiger, which Tigerdo not implement Animalthe interface.

package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Elephant implements Animal {
    
    
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Monkey implements Animal {
    
    
}
package com.example.demo.bean

import org.springframework.stereotype.Component;

@Component
public class Tiger {
    
    
}

Then, we create a FilterConfigurationclass and use @ComponentScanto scan all classes that implement Animalthe interface.

package com.example.demo.configuration;

import com.example.demo.bean.Animal;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = "com.example.demo",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Animal.class))
public class FilterConfiguration {
    
    
}

This filtering method is used in @ComponentScanannotations FilterType.ASSIGNABLE_TYPE. Here, Springonly all classes that implement Animalthe interface will be scanned and managed.

Finally, we create a main program to test:

package com.example.demo;

import com.example.demo.configuration.FilterConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(FilterConfiguration.class);
        String[] beanNames = ctx.getBeanDefinitionNames();
        for (String beanName : beanNames) {
    
    
            System.out.println(beanName);
        }
    }
}

operation result:

Insert image description here

AnimalYou can also see here that only classes that implement the interface will be scanned and instantiated Springby IoCthe container, and other @Componentclasses that do not implement Animalthe interface beanwill not be scanned and instantiated.

  Summary: This section introduces the " type filtering" Springin the framework , which is a filtering strategy that can specify one or more classes or interfaces for component scanning. Will include or exclude all classes that can be assigned to these classes or interfaces. In the example in this section, an interface is first defined , and then three classes , and , are defined , among which and implements the interface, and does not. Then create a class, use annotations, and specify through attributes and types to scan all classes that implement the interface. Therefore, when running the main program, the container will only scan and instantiate classes that implement the interface , and classes that do not implement the interface will not be scanned and instantiated.AssignableSpringAnimalElephantMonkeyTigerElephantMonkeyAnimalTigerFilterConfiguration@ComponentScanincludeFiltersFilterType.ASSIGNABLE_TYPEAnimalSpringIOCAnimalElephantMonkeyAnimalTiger


6. Custom component filters

  SpringAlso allows us to define our own filters to decide which components will be Spring IoCscanned by the container. To do this, we need to implement TypeFilterthe interface and override match()the methods. In match()the method, we can customize which components need to be included or excluded.

The entire code is as follows:

Add an interfaceAnimal

package com.example.demo.bean;

public interface Animal {
    
    
    String getType();
}

Define several classes and implement Animalinterfaces

package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Elephant implements Animal {
    
    
    public String getType() {
    
    
        return "This is a Elephant.";
    }
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Monkey implements Animal {
    
    
    public String getType() {
    
    
        return "This is an Monkey.";
    }
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Tiger implements Animal {
    
    
    public String getType() {
    
    
        return "This is a Tiger.";
    }
}
package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Tiger2 {
    
    
    public String getType() {
    
    
        return "This is a Tiger2.";
    }
}

Tiger2The interface is not implemented Animaland will be used for comparison later.

Next, we first customize a filter CustomFilter, which implements TypeFilterthe interface. This filter will include all classes that implement Animalthe interface and whose class names "T"begin with:

package com.example.demo.filter;

import com.example.demo.bean.Animal;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.Arrays;

public class CustomFilter implements TypeFilter {
    
    

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
    
    
        ClassMetadata classMetadata = metadataReader.getClassMetadata();

        // 如果全限定类名以 "T" 开头并且实现了 "Animal" 接口
        return classMetadata.getClassName().startsWith("com.example.demo.bean.T") &&
                Arrays.asList(classMetadata.getInterfaceNames()).contains(Animal.class.getName());
    }
}

  If matchthe method returns true, then Springthis class will be regarded as a candidate component, and other conditions must be met before it can be created bean. If this class does not use @Component, @Serviceetc. annotations, then even if the filter finds this class, Springit will not be registered bean. Because Springit is still necessary to identify the metadata of the class (such as: @Component, @Serviceand other annotations) to determine how to create and manage it bean. Otherwise, if matchthe method returns false, Springthe class will be ignored.

in matchmethod

  • metadataReader.getClassMetadata()Returns an ClassMetadataobject, which contains some metadata information about the current class, such as class name, whether it is an interface, parent class name, etc.

  • classMetadata.getClassName()Returns the fully qualified class name of the current class, that is, the class name including the package name.

  In matchthe method, we checked whether the fully qualified name of the current class starts "com.example.demo.bean.T"with and whether the current class implements "Animal"the interface. If these two conditions are met, matchthe method returns trueand Springthe class is considered a candidate component. If either of these two conditions is not met, matchthe method returns falseand Springthe class is ignored and will not be considered a candidate component.

Then, in our FilterConfiguration, use FilterType.CUSTOMtypes and specify CustomFilterthe class we just created:

package com.example.demo.configuration;

import com.example.demo.filter.CustomFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(basePackages = "com.example.demo",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = CustomFilter.class))
public class FilterConfiguration {
    
    
}

In this way, when Spring IoCthe container scans, only components whose class names "T"begin with and implement Animalthe interface will be included. In our example, only Tigerclasses will be included, and classes Tiger2, , Elephantand Monkeywill be excluded.

Main program:

package com.example.demo;

import com.example.demo.configuration.FilterConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(FilterConfiguration.class);
        String[] beanNames = ctx.getBeanDefinitionNames();
        for (String beanName : beanNames) {
    
    
            System.out.println(beanName);
        }
    }
}

operation result:

Insert image description here

  Debugging will reveal that matchthe method is constantly being called back. In fact, matchthe number of method calls is related to the number of definitions Springin the application context . When we use to perform package scanning, all classes under the specified package (and its sub-packages) will be traversed, and each class will be analyzed to determine whether it needs to be created. corresponding .Bean@ComponentScanSpringBean

  When we use to @ComponentScan.Filterdefine a custom filter, Springthe filter method will be called for each traversed class matchto determine whether this class needs to be ignored. Therefore, the number of matchtimes a method is called is equal to Springthe number of classes scanned, including not only Beanclasses that were ultimately created as , but also classes that were ignored by the filter.

  This behavior may be affected by some other configuration. For example, if Springlazy loading ( ) is used in the configuration @Lazy, matchthe invocation of the method may be delayed until it Beanis first requested.

  Summary: This section describes how to Springcreate and use custom filters in the framework to decide which components will be Spring IoCconsidered candidates by the container. By implementing TypeFilterthe interface and overriding its match()methods, you can decide which classes will be included in the list of candidate components based on custom conditions. In this example, we created a custom filter so that only classes "T"that start with and implement Animalthe interface will be marked as candidate components. When Springperforming a package scan, all classes will be traversed and the filter match()method will be called on each class. The number of calls to this method is equal to Springthe number of scanned classes. Then, only those classes that both meet the filter conditions and are Springidentified as components (for example, using @Componentthe or @Serviceand other annotations) will be instantiated Beanand Spring IoCmanaged by the container. If lazy loading is configured, Beaninstantiation may be delayed until Beanthe first time it is requested.


7. Other features of component scanning

SpringThe component scanning mechanism provides some powerful features, let's explain them one by one.

7.1 Combined use of component scanning

  SpringAnnotations are provided @ComponentScans, allowing us to combine multiple @ComponentScanuses, which allows us to complete multiple package scans in one operation.

  @ComponentScansThe main usage scenarios are when you need Springmore granular control over the component scanning behavior, you can scan two completely independent packages in the same application, or you can apply multiple independent filters to exclude or include specific components.

You can see @ComponentScansthat the annotation receives an ComponentScanarray, which means a bunch of annotations are combined at once @ComponentScan.

Let's look at an example of how to use @ComponentScansto combine multiple @ComponentScan.

The entire code is as follows:

First, we define two simple classes, in the com.example.demo.bean1and com.example.demo.bean2packages:

package com.example.demo.bean1;

import org.springframework.stereotype.Component;

@Component
public class BeanA {
    
    
}
package com.example.demo.bean2;

import org.springframework.stereotype.Component;

@Component
public class BeanB {
    
    
}

Then, we use in the configuration class @ComponentScansto scan these two packages at once:

package com.example.demo.configuration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScans({
    
    
        @ComponentScan("com.example.demo.bean1"),
        @ComponentScan("com.example.demo.bean2")
})
public class AppConfig {
    
    
}

Finally, we can test whether these two classes were successfully scanned:

package com.example.demo;

import com.example.demo.bean1.BeanA;
import com.example.demo.bean2.BeanB;
import com.example.demo.configuration.AppConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class DemoApplication {
    
    

    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        BeanA beanA = ctx.getBean(BeanA.class);
        BeanB beanB = ctx.getBean(BeanB.class);
        System.out.println("beanA = " + beanA);
        System.out.println("beanB = " + beanB);
    }
}

By running the above mainmethod, BeanAand BeanBare successfully scanned and injected Springinto ApplicationContext.

operation result:

Insert image description here

  Summary: This section introduces Springan important feature of the package scanning mechanism, which is the ability to use @ComponentScansannotations for combined package scanning. This feature allows multiple packet scans to be completed in a single operation, enabling Springfine-grained control over component scanning behavior. For example, two completely independent packages can be scanned simultaneously, or multiple independent filters can be applied to exclude or include specific components. In the example in this section, two packages , and , @ComponentScanswere scanned at once , and the and were successfully scanned and injected into .com.example.demo.bean1com.example.demo.bean2BeanABeanBSpringApplicationContext


8. Component name generation for component scanning

  When we Springuse annotations beanfor definition and management, we usually use annotations such as @Component, @Service, @Repositoryand so on. @ControllerWhen defining using these annotations bean, if we do not explicitly specify beana name, a default name Springwill be generated for us according to certain rules .bean

  This default name is usually the first letter of the class name in lowercase. For example, for a class named , if we use annotations MyServicelike this :@Service

@Service
public class MyService {
    
    
}

  Then a default name Springwill be generated for ours . We can refer to this by this name elsewhere in the application . For example, we can inject this via an annotation and this name among others :beanmyServicebeanbean@Autowiredbean

@Autowired
private MyService myService;

  This default name is generated by BeanNameGeneratorthe implementation class of the interface . It will check whether our class has an explicitly specified name. If not, it will generate a default name according to the rules of lowercase first letter of the class name.AnnotationBeanNameGeneratorAnnotationBeanNameGeneratorbean

8.1 How Spring generates default bean names (source code analysis)

To explain this process, let's take a look at AnnotationBeanNameGeneratorthe source code of the class. The corresponding Springversion of the source code below is 5.3.7.

The source code picture is given first, and the source code analysis is given later.

Insert image description here

The code block is brought up for analysis:

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    
    
    if (definition instanceof AnnotatedBeanDefinition) {
    
     // 该行检查BeanDefinition是否为AnnotatedBeanDefinition
        String beanName = this.determineBeanNameFromAnnotation((AnnotatedBeanDefinition)definition); // 该行调用方法来从注解获取bean名称
        if (StringUtils.hasText(beanName)) {
    
     // 检查是否获取到了有效的bean名称
            return beanName; // 如果有,返回这个名称
        }
    }

    return this.buildDefaultBeanName(definition, registry); // 如果没有从注解中获取到有效的名称,调用方法生成默认的bean名称
}

determineBeanNameFromAnnotationLet’s look at the method again

Insert image description here

This code is very long, let’s analyze the code block directly:

@Nullable
protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
    
    
    // 1. 获取bean定义的元数据,包括所有注解信息
    AnnotationMetadata amd = annotatedDef.getMetadata();
    
    // 2. 获取所有注解类型
    Set<String> types = amd.getAnnotationTypes();
    
    // 3. 初始化bean名称为null
    String beanName = null;
    
    // 4. 遍历所有注解类型
    Iterator var5 = types.iterator();
    while(var5.hasNext()) {
    
    
        // 4.1 获取当前注解类型
        String type = (String)var5.next();
        
        // 4.2 获取当前注解的所有属性
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
        
        // 4.3 只有当前注解的属性不为null时,才会执行以下代码
        if (attributes != null) {
    
    
            Set<String> metaTypes = (Set)this.metaAnnotationTypesCache.computeIfAbsent(type, (key) -> {
    
    
                Set<String> result = amd.getMetaAnnotationTypes(key);
                return result.isEmpty() ? Collections.emptySet() : result;
            });
            
            // 4.4 检查当前注解是否为带有名称的元注解
            if (this.isStereotypeWithNameValue(type, metaTypes, attributes)) {
    
    
                // 4.5 尝试从注解的"value"属性中获取bean名称
                Object value = attributes.get("value");
                if (value instanceof String) {
    
    
                    String strVal = (String)value;
                    
                    // 4.6 检查获取到的名称是否为有效字符串
                    if (StringUtils.hasLength(strVal)) {
    
    
                        // 4.7 如果已经存在bean名称并且与当前获取到的名称不一致,则抛出异常
                        if (beanName != null && !strVal.equals(beanName)) {
    
    
                            throw new IllegalStateException("Stereotype annotations suggest inconsistent component names: '" + beanName + "' versus '" + strVal + "'");
                        }

                        // 4.8 设置bean名称为获取到的名称
                        beanName = strVal;
                    }
                }
            }
        }
    }
    
    // 5. 返回获取到的bean名称,如果没有找到有效名称,则返回null
    return beanName;
}

Finally , let ’s take a look at how buildDefaultBeanNamethe method generates the default name.Springbean

Insert image description here

Break it into code blocks and analyze:

protected String buildDefaultBeanName(BeanDefinition definition) {
    
    
    // 1. 从bean定义中获取bean的类名
    String beanClassName = definition.getBeanClassName();

    // 2. 确保bean类名已设置,否则会抛出异常
    Assert.state(beanClassName != null, "No bean class name set");

    // 3. 使用Spring的ClassUtils获取类的简单名称,即不带包名的类名
    String shortClassName = ClassUtils.getShortName(beanClassName);

    // 4. 使用Java内省工具(Introspector)将类名首字母转换为小写
    // 这就是Spring的默认bean命名策略,如果用户没有通过@Component等注解显式指定bean名,
    // 则会使用该类的非限定类名(即不带包名的类名),并将首字母转换为小写作为bean名。
    return Introspector.decapitalize(shortClassName);
}

8.2 Special cases of generating default bean names

  Everyone must know that the UserServicedefault beanname is userService, but if the class name is MService, beanthe name will still be MService, the first letter will not be lowercase. Let’s analyze the specific reasons.

  When we analyzed buildDefaultBeanNamethe method above to generate the default beanname, we found that it decapitalizereturned after calling the method. Let's take a look at decapitalizethe method.

Insert image description here

Come up with a code block and analyze it

/**
 * 将字符串转换为正常的 Java 变量名规则的形式。
 * 这通常意味着将第一个字符从大写转换为小写,
 * 但在(不常见的)特殊情况下,当有多个字符并且第一个和第二个字符都是大写时,我们将保持原样。
 * 因此,“FooBah”变为“fooBah”,“X”变为“x”,但“URL”保持为“URL”。
 * 这是 Java 内省机制的一部分,因为它涉及 Java 对类名和变量名的默认命名规则。
 * 根据这个规则,我们可以从类名自动生成默认的变量名。
 *
 * @param name 要小写的字符串。
 * @return 小写版本的字符串。
 */
public static String decapitalize(String name) {
    
    
    if (name == null || name.length() == 0) {
    
    
        return name;
    }
    // 如果字符串的前两个字符都是大写,那么保持原样
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) {
    
    
        return name;
    }
    char chars[] = name.toCharArray();
    // 将第一个字符转为小写
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

  According to Javathe naming rules of , the first letter of the class name should be capitalized, while the first letter of the variable name should be lowercase, which tells the introspection mechanism how to generate the default variable name (or name) from the class name bean.

  As you can see here, decapitalizethe method receives a string parameter and then converts the first letter of the string to lowercase, unless the first two characters of the string are both uppercase, in which case the string remains unchanged.

  Therefore Java, in the introspection mechanism, if the first two letters of the class name are uppercase, then when the first letter is converted to lowercase, it will remain unchanged. That is, for this case, beanthe name and class name are the same.

  This is designed to adhere to Javathe naming convention in , which states that when a word begins a class name and is in all caps (e.g. URL, HTTP), it should remain in all caps.


9. Application of Java’s introspection mechanism in generating default bean names

  JavaThe introspection mechanism ( Introspection) is Javathe language Bean's ability to self-examine classes, and it is Javaan important supplement to reflection. It allows the program to obtain the type information of the class as well as the information about its properties and methods Javaat runtime . NOTE: "Introspection" is pronounced .BeanBean"nèi xǐng"

The purpose of the introspection mechanism is to provide a unified set of APIinformation that can dynamically obtain various types of information at runtime, mainly covering the following aspects:

  1. Obtain the type information of a class : You can obtain the class, interface, parent class, modifier and other information to which any object belongs at runtime Bean.

  2. Attribute information : You can obtain Beanvarious information about the attributes of the class, such as type, modifier, etc.

  3. Obtain method information : You can obtain Beanmethod information of the class, such as return value type, parameter type, modifiers, etc.

  4. Calling methods : You can call methods of any object at runtime Bean.

  5. Modify property valuesBean : Property values ​​that can be modified at runtime .

Through these reflections API, we can operate any object in a unified way without hard-coding the specific class of the object.

  In terms of naming rules, when we get an attribute name, if the first letter of the remaining part of the Beancorresponding getteror settermethod name is uppercase after removing the prefix, then when converting it into an attribute name, this letter will become "get"/"set"lower case. If the first two letters of the remainder are uppercase, the property name remains unchanged and they are not converted to lowercase.

  This rule is mainly to deal with the situation where some class names or method names use uppercase abbreviations. For example, for a getURLmethod named " ", we will get " URL" as the property name, not " uRL".

Although we may not use Javathe introspection mechanism directly and frequently in daily development, in some specific scenarios and tools, the introspection mechanism plays an important role:

  • IDE and debugging tools : These tools need to use the introspection mechanism to obtain class information, such as class hierarchy, method and attribute information, etc., in order to provide code completion, code inspection and other functions.

  • Test framework : For example, JUnitsuch a test framework needs to use the introspection mechanism to instantiate test classes and obtain test methods and other information to run tests.

  • Dependency injection framework : For example, Springdependency injection frameworks need to use the introspection mechanism to scan classes, obtain dependency definitions in classes, and automatically assemble them bean.

  • Serialization/deserialization : Serialization needs to obtain the type information and attribute information of the object to achieve persistence of the object state; deserialization needs to use type information to restore the object.

  • Logging framework : Many logging frameworks can automatically obtain contextual information such as the class and method name of the logging method through the introspection mechanism.

  • Access permission judgment : Some security-related frameworks need to determine whether a member's access permission is legal through introspection.

  • Interface-oriented programming : The introspection mechanism makes it possible to do not need the implementation class name of the interface when programming for interfaces hardcode, but to locate it at runtime.

  In short, the purpose of the introspection mechanism is to achieve cross-class dynamic operations and information access and improve runtime flexibility. This also allows the framework to perform some useful operations without knowing the specific class.



Welcome to the one-click triple connection~

If you have any questions, please leave a message, let's discuss and learn together

----------------------Talk is cheap, show me the code----- ------------------

Guess you like

Origin blog.csdn.net/qq_34115899/article/details/131751729