Article directory
- 1. Component scan path
- 2. Filter components by annotation (included)
- 3. Filter components by annotation (exclude)
- 4. Filter components through regular expressions
- 5. Assignable type filter component
- 6. Custom component filters
- 7. Other features of component scanning
- 8. Component name generation for component scanning
- 9. Application of Java’s introspection mechanism in generating default bean names
First, we'll explore some of the advanced features of the Spring
framework , 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.IOC
Inversion of Control
Spring
@Component
@Service
@Controller
Spring
bean
1. Component scan path
@ComponentScan
Annotations are used to specify Spring
the package paths that need to be scanned at startup to automatically discover and register components.
We set the component scan path in two ways:
-
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
@Repository
Spring
bean
-
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-packagesSpring
will 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的basePackageClasses
achieved 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 Spring
will be scanned .ExampleService
The entire code is as follows:
First, we create a ExampleService
service class called
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class ExampleService {
}
Then bean
create 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 DemoApplication
class named which will start Spring
the context and print out all registered bean
names.
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 DemoApplication
of the above class main
and you will see all registered names on the console bean
, including the one we just created ExampleService
.
Now, if we ExampleService
create 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 basePackageClasses
what the attribute does: it tells Spring
which 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 DemoApplication
the class main
method again, you will see that TestService
it is also printed, indicating that it has also been successfully registered as one bean
.
We can see that this DemoDao
has never been scanned. Let’s take a look at @ComponentScan
the source code of the annotation.
You can see basePackageClasses
that 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 Java
a 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.class
it will be wrapped into an array containing only one element. This is Java
a convenient feature of the syntax that makes code look cleaner when there is only one element.
So in order for DemoDao
the component to be scanned, we can basePackageClasses
add DemoDao
the class to the attribute, so that DemoDao
the 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
@ComponentScan
There 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 Spring
also 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 Spring
component scanning range and optimize the project startup speed.
Spring
Annotation inclusion filtering can be implemented through the properties in @ComponentScan
, and includeFilters
only 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 Species
annotations:
package com.example.demo.bean;
import com.example.demo.annotation.Species;
import org.springframework.stereotype.Component;
@Species
public class Elephant {
}
Elephant
The class is @Species
modified, not @Component
modified.
package com.example.demo.bean;
import org.springframework.stereotype.Component;
@Component
public class Monkey {
}
Monkey
only @Component
modified
package com.example.demo.bean;
import com.example.demo.annotation.Species;
import org.springframework.stereotype.Component;
@Component
@Species
public class Tiger {
}
As shown above, Tiger
there are @Component
and @Species
modifications.
Next, we need to create a configuration class to scan and include annotated Species
components:
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 @ComponentScan
annotation includeFilters
attribute, FilterType.ANNOTATION
which represents filtering by annotation. This is used to scan all Species
components with annotations. Note that useDefaultFilters = false
it means that the default filtering rules are disabled and only Species
components marked with will be included.
Some people may have questions, useDefaultFilters = false
indicating that the default filtering rules are disabled. What are the default filtering rules?
In Spring
, when using @ComponentScan
annotations for component scanning, Spring
default filtering rules are provided. These default rules include the following types of annotations:
- @Component
- @Repository
- @Service
- @Controller
- @RestController
- @Configuration
useDefaultFilters
When the attribute is not written by default , useDefaultFilters
the value of the attribute is true
. Spring
During component scanning, components marked with the above annotations will be included by default. If it is useDefaultFilters
set to false
, Spring
only components with explicitly specified filtering rules will be scanned, and components with the above default rules will no longer be included. components. So, useDefaultFilters = false
it's telling Spring
us that we only want custom component scan rules.
Finally, we create a main program that instantiates the application context and lists all Bean
the 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 Bean
names only include Species
classes marked by annotations. In this example you will see Tiger
and Elephant
, but not Monkey
because our configuration only contains Species
classes annotated with .
operation result:
If useDefaultFilters
the property is set to true
, then the names output when running the program Bean
will be included Monkey
.
Summary: The above describes how to use Spring
attributes and attributes @ComponentScan
in 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.includeFilters
useDefaultFilters
Spring
useDefaultFilters
false
Spring
@Component
@Service
3. Filter components by annotation (exclude)
In Spring
the framework, we can not only set classes containing specific annotations through the properties @ComponentScan
of 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.includeFilters
excludeFilters
Spring
IOC
@ComponentScan
excludeFilters
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 @Component
annotations Elephant
on the classes @Exclude
.
Next, we create the configuration class FilterConfiguration
, use the annotation in it @ComponentScan
, and excludeFilters
exclude all @Exclude
classes 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 IOC
the container, only Tiger
the and Monkey
classes will be scanned and instantiated, because Elephant
the class is marked @Exclude
by the annotation, and we FilterConfiguration
exclude all @Exclude
classes 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:
Summary: This section mainly explains how to filter annotations through annotation properties in Spring
the 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.@ComponentScan
excludeFilters
Spring IOC
Exclude
Elephant
Monkey
Tiger
@Component
Elephant
@Exclude
FilterConfiguration
@ComponentScan
excludeFilters
@Exclude
Spring IOC
Tiger
Monkey
Elephant
@Exclude
4. Filter components through regular expressions
In Spring
the 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 IOC
classes 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 Tiger
classes 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 @ComponentScan
the annotation, and includeFilters
include the classes whose class names Tiger
end 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.REGEX
the filter type and specify the regular expression patterns to include ".*Tiger"
. As a result, only Tiger
classes will be Spring的IOC
scanned and instantiated by the container, because only Tiger
the 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
Summary: This section describes how to Spring
use regular expressions to filter components in the framework to select which classes should be Spring IOC
managed by the container. In the example given, three classes Elephant
, Monkey
and , are first defined Tiger
. Then I created a configuration class FilterConfiguration
, used @ComponentScan
annotations, and includeFilters
set a regular expression through properties " .*Tiger"
to select "Tiger"
classes whose class names end with. So when running the main program, Spring
the IOC
container will only scan and instantiate Tiger
classes, because only Tiger
the class name of the class satisfies the regular expression " .*Tiger"
.
5. Assignable type filter component
" Assignable
Type filtering" is Spring
a filtering strategy used by the framework when scanning components. This strategy allows us to specify one or more classes or interfaces, and then Spring
include 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 " Assignable
type filtering" to include all classes that implement Animal
the interface.
The entire code is as follows:
First, we define an Animal
interface
package com.example.demo.bean;
public interface Animal {
}
Then define three classes: Elephant
, Monkey
and Tiger
, which Tiger
do not implement Animal
the 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 FilterConfiguration
class and use @ComponentScan
to scan all classes that implement Animal
the 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 @ComponentScan
annotations FilterType.ASSIGNABLE_TYPE
. Here, Spring
only all classes that implement Animal
the 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:
Animal
You can also see here that only classes that implement the interface will be scanned and instantiated Spring
by IoC
the container, and other @Component
classes that do not implement Animal
the interface bean
will not be scanned and instantiated.
Summary: This section introduces the " type filtering" Spring
in 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.Assignable
Spring
Animal
Elephant
Monkey
Tiger
Elephant
Monkey
Animal
Tiger
FilterConfiguration
@ComponentScan
includeFilters
FilterType.ASSIGNABLE_TYPE
Animal
Spring
IOC
Animal
Elephant
Monkey
Animal
Tiger
6. Custom component filters
Spring
Also allows us to define our own filters to decide which components will be Spring IoC
scanned by the container. To do this, we need to implement TypeFilter
the 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 Animal
interfaces
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.";
}
}
Tiger2
The interface is not implemented Animal
and will be used for comparison later.
Next, we first customize a filter CustomFilter
, which implements TypeFilter
the interface. This filter will include all classes that implement Animal
the 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 match
the method returns true
, then Spring
this 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
, @Service
etc. annotations, then even if the filter finds this class, Spring
it will not be registered bean
. Because Spring
it is still necessary to identify the metadata of the class (such as: @Component
, @Service
and other annotations) to determine how to create and manage it bean
. Otherwise, if match
the method returns false
, Spring
the class will be ignored.
in match
method
-
metadataReader.getClassMetadata()
Returns anClassMetadata
object, 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 match
the 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, match
the method returns true
and Spring
the class is considered a candidate component. If either of these two conditions is not met, match
the method returns false
and Spring
the class is ignored and will not be considered a candidate component.
Then, in our FilterConfiguration
, use FilterType.CUSTOM
types and specify CustomFilter
the 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 IoC
the container scans, only components whose class names "T"
begin with and implement Animal
the interface will be included. In our example, only Tiger
classes will be included, and classes Tiger2
, , Elephant
and Monkey
will 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:
Debugging will reveal that match
the method is constantly being called back. In fact, match
the number of method calls is related to the number of definitions Spring
in 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
@ComponentScan
Spring
Bean
When we use to @ComponentScan.Filter
define a custom filter, Spring
the filter method will be called for each traversed class match
to determine whether this class needs to be ignored. Therefore, the number of match
times a method is called is equal to Spring
the number of classes scanned, including not only Bean
classes 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 Spring
lazy loading ( ) is used in the configuration @Lazy
, match
the invocation of the method may be delayed until it Bean
is first requested.
Summary: This section describes how to Spring
create and use custom filters in the framework to decide which components will be Spring IoC
considered candidates by the container. By implementing TypeFilter
the 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 Animal
the interface will be marked as candidate components. When Spring
performing 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 Spring
the number of scanned classes. Then, only those classes that both meet the filter conditions and are Spring
identified as components (for example, using @Component
the or @Service
and other annotations) will be instantiated Bean
and Spring IoC
managed by the container. If lazy loading is configured, Bean
instantiation may be delayed until Bean
the first time it is requested.
7. Other features of component scanning
Spring
The component scanning mechanism provides some powerful features, let's explain them one by one.
7.1 Combined use of component scanning
Spring
Annotations are provided @ComponentScans
, allowing us to combine multiple @ComponentScan
uses, which allows us to complete multiple package scans in one operation.
@ComponentScans
The main usage scenarios are when you need Spring
more 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 @ComponentScans
that the annotation receives an ComponentScan
array, which means a bunch of annotations are combined at once @ComponentScan
.
Let's look at an example of how to use @ComponentScans
to combine multiple @ComponentScan
.
The entire code is as follows:
First, we define two simple classes, in the com.example.demo.bean1
and com.example.demo.bean2
packages:
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 @ComponentScans
to 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 main
method, BeanA
and BeanB
are successfully scanned and injected Spring
into ApplicationContext
.
operation result:
Summary: This section introduces Spring
an important feature of the package scanning mechanism, which is the ability to use @ComponentScans
annotations for combined package scanning. This feature allows multiple packet scans to be completed in a single operation, enabling Spring
fine-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 , @ComponentScans
were scanned at once , and the and were successfully scanned and injected into .com.example.demo.bean1
com.example.demo.bean2
BeanA
BeanB
Spring
ApplicationContext
8. Component name generation for component scanning
When we Spring
use annotations bean
for definition and management, we usually use annotations such as @Component
, @Service
, @Repository
and so on. @Controller
When defining using these annotations bean
, if we do not explicitly specify bean
a name, a default name Spring
will 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 MyService
like this :@Service
@Service
public class MyService {
}
Then a default name Spring
will 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 :bean
myService
bean
bean
@Autowired
bean
@Autowired
private MyService myService;
This default name is generated by BeanNameGenerator
the 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.AnnotationBeanNameGenerator
AnnotationBeanNameGenerator
bean
8.1 How Spring generates default bean names (source code analysis)
To explain this process, let's take a look at AnnotationBeanNameGenerator
the source code of the class. The corresponding Spring
version of the source code below is 5.3.7
.
The source code picture is given first, and the source code analysis is given later.
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名称
}
determineBeanNameFromAnnotation
Let’s look at the method again
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 buildDefaultBeanName
the method generates the default name.Spring
bean
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 UserService
default bean
name is userService
, but if the class name is MService
, bean
the name will still be MService
, the first letter will not be lowercase. Let’s analyze the specific reasons.
When we analyzed buildDefaultBeanName
the method above to generate the default bean
name, we found that it decapitalize
returned after calling the method. Let's take a look at decapitalize
the method.
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 Java
the 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, decapitalize
the 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, bean
the name and class name are the same.
This is designed to adhere to Java
the 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
Java
The introspection mechanism ( Introspection
) is Java
the language Bean
's ability to self-examine classes, and it is Java
an 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 Java
at runtime . NOTE: "Introspection" is pronounced .Bean
Bean
"nèi xǐng"
The purpose of the introspection mechanism is to provide a unified set of API
information that can dynamically obtain various types of information at runtime, mainly covering the following aspects:
-
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
. -
Attribute information : You can obtain
Bean
various information about the attributes of the class, such as type, modifier, etc. -
Obtain method information : You can obtain
Bean
method information of the class, such as return value type, parameter type, modifiers, etc. -
Calling methods : You can call methods of any object at runtime
Bean
. -
Modify property values
Bean
: 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 Bean
corresponding getter
or setter
method 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 getURL
method named " ", we will get " URL
" as the property name, not " uRL
".
Although we may not use Java
the 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,
JUnit
such 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,
Spring
dependency injection frameworks need to use the introspection mechanism to scan classes, obtain dependency definitions in classes, and automatically assemble thembean
. -
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----- ------------------