Soul torture
- @What does the ComponentScan annotation do?
- What is the difference between basePackages and basepackagecalasses? Which do you suggest? Why?
- What's the use of useDefaultFilters?
- What are the common types of filters? Tell me a few things you know
- @In which class is ComponentScan processed? Tell me about the general analysis process?
If all these questions are ok, congratulations. It's excellent. I don't know. It doesn't matter. Let's have a look.
Background introduction
So far, two methods of registering bean s have been introduced:
- How bean elements are in xml
- @Bean annotation annotation method
Usually, most classes in the project need to be managed by spring. According to the above two methods, the amount of code is still very large.
In order to facilitate bean registration, spring provides a batch method to register beans to facilitate batch registration of a large number of beans. This is what @ ComponentScan in spring does.
@ComponentScan
@ComponentScan is used to batch register bean s.
This annotation will let spring scan all classes in some packages and their sub packages, and then register the classes that meet certain conditions as bean s in the spring container.
Which packages need to be scanned? And what conditions the classes in these packages meet to be registered in the container, which can be dynamically configured through the parameters in this annotation.
Let's take a look at the definition of this annotation:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class) //@1 public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default "**/*.class"; boolean useDefaultFilters() default true; Filter[] includeFilters() default {}; Filter[] excludeFilters() default {}; boolean lazyInit() default false; }
It can be seen from the definition that this annotation can be used on any type, but we usually use it on classes.
Common parameters:
value: specify the package to be scanned, such as: com javacode2018
basePackages: same as value; Value and basePackages cannot be set at the same time. You can choose either
Basepackagecclasses: specify some classes, and the spring container will scan the package in which these classes are located and the classes in their sub packages
nameGenerator: Custom bean name generator
resourcePattern: those resources in the package need to be scanned. The default is: * * / * Class, all class files in the specified package will be scanned
useDefaultFilters: whether to enable the default filter for the scanned class. The default value is true
includeFilters: filters: used to configure that the scanned classes will be registered in the container as components
excludeFilters: filters, which have the opposite effect to includeFilters, are used to exclude scanned classes. Excluded classes will not be registered in the container
lazyInit: whether to delay the initialization of the registered bean
@1: @ Repeatable(ComponentScans.class), this annotation can use multiple at the same time.
@The working process of ComponentScan:
- Spring will scan the specified package and recurse the following sub packages to get an array of classes
- Then these classes will pass through the above filters, and finally the remaining classes will be registered in the container
Therefore, when playing this annotation, we mainly focus on two issues:
First: which packages need to be scanned? It is controlled by three parameters: value, backPackages and basepackageclass
Second: what are the filters? The filter is controlled by three parameters: useDefaultFilters, includeFilters and excludeFilters
When these two problems are clarified, we can determine which classes will be registered in the container.
By default, when no parameters are set, the package of the class modified by @ ComponentScan will be used as the scanning package; By default, useDefaultFilters is true. When this is true, the default filter will be used inside the spring container. The rule is: if there is any one of @ Repository, @ Service, @ Controller and @ Component annotations on the class, the class will be registered in the spring container as a bean, so by default, Just add any of these annotations to the classes, and these classes will be automatically handed over to the spring container for management.
@Component,@Repository,@Service,@Controller
These annotations are provided by spring.
First, let's talk about the annotation @ Component and see its definition:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface Component { String value() default ""; }
As you can see from the definition, this annotation can be used on any type.
Usually, this annotation is used on the class to mark the class as a component. By default, it will be registered in the container as a bean when it is scanned.
value parameter: used to specify the name of the bean when it is registered as a bean. If it is not specified, it defaults to the lowercase initial of the class name. For example, the beanname corresponding to class userservice is userservice
Let's look at the @ Repository source code as follows:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { @AliasFor(annotation = Component.class) String value() default ""; }
There is a @ Component annotation on the Repository.
There is @ AliasFor(annotation = Component.class) above the value parameter. Setting the value parameter is also equivalent to setting the value in the @ Component annotation.
The other two annotations @ Service, @ Controller source code and @ Repository source code are similar.
These four annotations are essentially no different. They can be used on a class to indicate that when the class is scanned by the spring container, it can be registered in the spring container as a bean component.
The parsing of these four annotations in the spring container is not distinguished. The @ Component annotation is used for parsing, so these annotations can be replaced with each other.
spring provides these four annotations to make the system clearer. Generally, the system is hierarchical. Most systems are generally divided into controller layer, service layer and dao layer.
@Controller is usually used to mark the components of controller layer, @ service annotation marks the components of service layer and @ Repository marks the components of dao layer, which can make the structure of the whole system clearer. When you see these annotations, you will know which layer they belong to. For spring, replacing these three annotations with @ Component annotation has no impact on the system, The effect is the same.
Let's experience various uses of @ ComponentScan through examples.
Case 1: no parameters are set
UserController
package com.javacode2018.lesson001.demo22.test1.controller; import org.springframework.stereotype.Controller; @Controller public class UserController { }
UserService
package com.javacode2018.lesson001.demo22.test1.service; import org.springframework.stereotype.Service; @Service public class UserService { }
UserDao
package com.javacode2018.lesson001.demo22.test1.dao; import org.springframework.stereotype.Repository; @Repository public class UserDao { }
UserModel
package com.javacode2018.lesson001.demo22.test1; import org.springframework.stereotype.Component; @Component public class UserModel { }
In the above classes, four annotations are used respectively.
@Componentscan decorated class
package com.javacode2018.lesson001.demo22.test1; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class ScanBean1 { }
Structure diagram of the above classes
test case
package com.javacode2018.lesson001.demo22; import com.javacode2018.lesson001.demo22.test1.ScanBean1; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class ComponentScanTest { @Test public void test1() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean1.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName + "->" + context.getBean(beanName)); } } }
@1: Use AnnotationConfigApplicationContext as ioc container and pass ScanBean as parameter.
By default, all classes in the package where the ScanBean class is located will be scanned, and any annotated @ Component, @ Repository, @ Service, @ Controller on the class will be registered in the container
Run output
Some outputs are as follows:
userModel->com.javacode2018.lesson001.demo22.test1.UserModel@595b007d userController->com.javacode2018.lesson001.demo22.test1.controller.UserController@72d1ad2e userDao->com.javacode2018.lesson001.demo22.test1.dao.UserDao@2d7275fc userService->com.javacode2018.lesson001.demo22.test1.service.UserService@399f45b1
Note that the bean s in the last four lines have been registered successfully.
Case 2: specify the package to scan
Specify which packages need to be scanned. You can configure them through value or basePackage. If you choose one of them, an error will be reported when running. Let's configure them through value.
ScanBean2
package com.javacode2018.lesson001.demo22.test2; import org.springframework.context.annotation.ComponentScan; @ComponentScan({ "com.javacode2018.lesson001.demo22.test1.controller", "com.javacode2018.lesson001.demo22.test1.service" }) public class ScanBean2 { }
The above specifies 2 packages to be scanned. There are 2 classes in these two packages.
test case
New method in ComponentScanTest
@Test public void test2() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean2.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName + "->" + context.getBean(beanName)); } }
Run output
The key lines are intercepted as follows:
userController->com.javacode2018.lesson001.demo22.test1.controller.UserController@dd8ba08 userService->com.javacode2018.lesson001.demo22.test1.service.UserService@245b4bdc
It can be seen that only two classes in the controller package and the service package are registered as bean s.
be careful
There is a hidden danger in scanning by specifying the package name. If the package name is duplicated, the scanning will become invalid. Generally, we use basepackagecalasses to specify the package to be scanned. This parameter can specify some types. By default, all classes in the package and its sub packages of these classes will be scanned. This method can effectively avoid this problem.
Let's take a look at how basepackageclass works.
Case: basepackageclass specifies the scan range
We can define a marked interface or class in the package that needs to be scanned. Their only function is to serve as the value of basepackageclassies, which has no other purpose.
Let's define such an interface
package com.javacode2018.lesson001.demo22.test6.beans; public interface ScanClass { }
Define two more classes and mark them with @ Component annotation
package com.javacode2018.lesson001.demo22.test6.beans; import org.springframework.stereotype.Component; @Component public class Service1 { }
package com.javacode2018.lesson001.demo22.test6.beans; import org.springframework.stereotype.Component; @Component public class Service2 { }
A class marked with @ composentscan
package com.javacode2018.lesson001.demo22.test6; import com.javacode2018.lesson001.demo22.test6.beans.ScanClass; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackageClasses = ScanClass.class) public class ScanBean6 { }
test case
New method in ComponentScanTest
@Test public void test6() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean6.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName + "->" + context.getBean(beanName)); } }
Run output
service1->com.javacode2018.lesson001.demo22.test6.beans.Service1@79924b service2->com.javacode2018.lesson001.demo22.test6.beans.Service2@7b9a4292
Use of includeFilters
usage
Let's take another look at the definition of the parameter includeFilters:
Filter[] includeFilters() default {};
It is an array of Filter type. Multiple filters are or relationships, that is, any one can be satisfied. Take a look at the Filter code:
@Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter { FilterType type() default FilterType.ANNOTATION; @AliasFor("classes") Class<?>[] value() default {}; @AliasFor("value") Class<?>[] classes() default {}; String[] pattern() default {}; }
It can be seen that Filter is also an annotation. Parameters:
Type: the type of filter. It is an enumeration type. There are five types
ANNOTATION: filter candidates by ANNOTATION, that is, judge whether the candidates have specified annotations
ASSIGNABLE_TYPE: filter candidates by the specified type, that is, judge whether the candidate is the specified type
ASPECTJ: ASPECTJ expression method, that is, judge whether the candidate matches the ASPECTJ expression
REGEX: regular expression method, that is, judge whether the complete name of the candidate matches the regular expression
CUSTOM: the user can customize the filter to filter the candidates, and the filtering of the candidates is left to the user to judge
value: the same as the effect of the parameter classes, choose one from the other
classes: the three cases are as follows
When type = filtertype During annotation, you can specify some annotations through the classes parameter to judge whether there are annotations specified by the classes parameter on the scanned class
When type = filtertype ASSIGNABLE_ Type, you can specify some types through the classes parameter to judge whether the scanned class is the type specified by the classes parameter
When type = filtertype When custom, it means that the filter is user-defined. The classes parameter is used to specify the user-defined filter. The user-defined filter needs to implement org springframework. core. type. filter. Typefilter interface
pattern: the two cases are as follows
When type = filtertype When ASPECTJ, specify the value of ASPECTJ expression to be matched through pattern
When type = filtertype When regex, the value from the regular expression through pattern
Case: scanning classes with annotations
demand
We customize an annotation to automatically register the class marked with these annotations into the container
code implementation
The following codes are on COM javacode2018. lesson001. demo22. Test3 package.
Define an annotation
package com.javacode2018.lesson001.demo22.test3; import java.lang.annotation.*; @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyBean { }
Create a class and annotate it with this annotation
package com.javacode2018.lesson001.demo22.test3; @MyBean public class Service1 { }
Another class, using the @ composite annotation in spring
package com.javacode2018.lesson001.demo22.test3; import org.springframework.stereotype.Component; @Component public class Service2 { }
Another class, annotated with @ composentscan
package com.javacode2018.lesson001.demo22.test3; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; @ComponentScan(includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyBean.class) }) public class ScanBean3 { }
The type of Filter specified above is the annotation type. As long as there is @ MyBean annotation on the class, it will be registered in the container as a bean.
test case
Add a test case in ComponentScanTest
@Test public void test3() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean3.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName + "->" + context.getBean(beanName)); } }
Run the output and intercept the main lines
service1->com.javacode2018.lesson001.demo22.test3.Service1@6b81ce95 service2->com.javacode2018.lesson001.demo22.test3.Service2@2a798d51
Service1 is marked with @ MyBean annotation and registered in the container, but Service2 is not marked with @ MyBean. How can it be registered in the container?
Reason: Service2 is annotated with @ Component annotation, and useDefaultFilters in @ componentscan annotation is true by default, which means that the default filter will also be enabled, and the default filter will also register classes annotated with @ component, @ Repository, @ Service and @ Controller into the container
If we only want to register the beans annotated with @ MyBean annotation to the container, we need to turn off the default filter, that is: useDefaultFilters=false. Let's modify the code of ScanBean3 as follows:
@ComponentScan( useDefaultFilters = false, //Do not enable default filters includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyBean.class) }) public class ScanBean3 { }
Run test3 output again:
service1->com.javacode2018.lesson001.demo22.test3.Service1@294425a7
Extension: Custom annotations support defining bean names
The above customized @ MyBean annotation cannot specify the bean name. You can modify this annotation and add a value parameter to specify the bean name, as follows:
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component //@1 public @interface MyBean { @AliasFor(annotation = Component.class) //@2 String value() default ""; //@3 }
Focus on the codes in @ 1 and @ 2. You can indirectly set the value in the @ Component annotation through the above parameters.
The @ AliasFor annotation is used here. If you don't know about this, you can take a look: java annotation explanation and spring annotation enhancement
Modify the code of Service1:
@MyBean("service1Bean") public class Service1 { }
Run test3 case output:
service1Bean->com.javacode2018.lesson001.demo22.test3.Service1@222545dc
At this point, the bean name becomes service1Bean.
Case: class containing the specified type
The following codes are located on COM javacode2018. lesson001. demo22. Test4 package.
Let's have an interface
package com.javacode2018.lesson001.demo22.test4; public interface IService { }
Let spring scan, and register IService types in the container.
Two implementation classes
package com.javacode2018.lesson001.demo22.test4; public class Service1 implements IService { }
package com.javacode2018.lesson001.demo22.test4; public class Service2 implements IService { }
A @ composentscan annotated class
package com.javacode2018.lesson001.demo22.test4; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; @ComponentScan( useDefaultFilters = false, //Do not enable default filters includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class) //@1 }) public class ScanBean4 { }
@1: The scanned class meets iservice class. Isassignablefrom (scanned class) conditions are registered in the spring container
Let's have a test case
Add a test case in ComponentScanTest
@Test public void test4() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean4.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName + "->" + context.getBean(beanName)); } }
Run output
service1->com.javacode2018.lesson001.demo22.test4.Service1@6379eb service2->com.javacode2018.lesson001.demo22.test4.Service2@294425a7
Custom Filter
usage
Sometimes we need to use custom filters. The steps to use custom filters are as follows:
1.set up@Filter in type The type of is: FilterType.CUSTOM 2.To customize the filter class, you need to implement the interface: org.springframework.core.type.filter.TypeFilter 3.set up@Filter Medium classses Filter type for customization
Let's take a look at the definition of TypeFilter interface:
@FunctionalInterface public interface TypeFilter { boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException; }
It is a functional interface, including a match method. The method returns boolean type. It has two parameters, both of which are interface types. The following describes these two interfaces.
MetadataReader interface
The class metadata reader can read any information on a class, such as the annotation information on the class, the disk path information of the class, and various information of the class object of the class. spring encapsulates it and provides various convenient methods.
Take a look at the definition of this interface:
public interface MetadataReader { /** * Returns the resource reference of the class file */ Resource getResource(); /** * Return a ClassMetadata object, through which you can read some metadata information of the class, such as the class object of the class, whether it is an interface, whether there are annotations, whether it is an abstract class, parent class name, interface name, internal list, etc. you can go to the source code */ ClassMetadata getClassMetadata(); /** * Get all annotation information on the class */ AnnotationMetadata getAnnotationMetadata(); }
MetadataReaderFactory interface
Class metadata reader factory. You can obtain the metadata reader object of any class through this class.
Source code:
public interface MetadataReaderFactory { /** * Returns the MetadataReader object for the given class name */ MetadataReader getMetadataReader(String className) throws IOException; /** * Returns the MetadataReader object for the specified resource */ MetadataReader getMetadataReader(Resource resource) throws IOException; }
Custom Filter case
demand
Let's use a custom Filter to judge whether the scanned class is of IService interface type and register it in the container.
code implementation
Create a custom TypeFilter class:
package com.javacode2018.lesson001.demo22.test5; import com.javacode2018.lesson001.demo22.test4.IService; 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; public class MyFilter implements TypeFilter { /** * @param metadataReader * @param metadataReaderFactory * @return * @throws IOException */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { Class curClass = null; try { //Currently scanned class curClass = Class.forName(metadataReader.getClassMetadata().getClassName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } //Judge whether curClass is IService type boolean result = IService.class.isAssignableFrom(curClass); return result; } }
A @ composentscan annotated class
package com.javacode2018.lesson001.demo22.test5; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; @ComponentScan( basePackages = {"com.javacode2018.lesson001.demo22.test4"}, useDefaultFilters = false, //Do not enable default filters includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyFilter.class) //@1 }) public class ScanBean5 { }
@1: type is filtertype Custom indicates that the Filter is user-defined and the classes are user-defined filters
Another test case
Add a test case in ComponentScanTest
@Test public void test5() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean5.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName + "->" + context.getBean(beanName)); } }
Run output
service1->com.javacode2018.lesson001.demo22.test4.Service1@4cc451f2 service2->com.javacode2018.lesson001.demo22.test4.Service2@6379eb
excludeFilters
Configure the excluded filters. The classes that meet these filters will not be registered in the container. The usage above is the same as that of includeFilters. I won't demonstrate this and can play by myself
@ComponentScan reuse
From the definition of this annotation, it can be seen that multiple annotations can be used at the same time, such as:
@ComponentScan(basePackageClasses = ScanClass.class) @ComponentScan( useDefaultFilters = false, //Do not enable default filters includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class) }) public class ScanBean7 { }
There is another way to write @ ComponentScans:
@ComponentScans({ @ComponentScan(basePackageClasses = ScanClass.class), @ComponentScan( useDefaultFilters = false, //Do not enable default filters includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class) })}) public class ScanBean7 { }
Spring source code
@The componentscan annotation is handled by the following class
org.springframework.context.annotation.ConfigurationClassPostProcessor
This class is very important. It is mainly used to register user beans. The @ configuration and @ bean annotations described earlier are also handled by this class.
There are also the following notes:
@PropertySource @Import @ImportResource @Compontent
The above annotations are processed by the ConfigurationClassPostProcessor class. These annotations will be processed recursively internally to complete bean registration.
Take @ componentscan as an example. After the first scan, you will get a batch of classes that need to be registered. Then, you will traverse these classes to determine whether there is any of the above annotations. If so, you will hand over this class to ConfigurationClassPostProcessor to continue processing until the registration of all bean s is completed recursively.
If you want to be a master, you must see this class.
summary
- @ComponentScan is used to batch register bean s. Spring will recursively scan all classes in the specified package according to the configuration of this annotation, and batch register the qualified classes into the spring container
- The scanning range of packages can be configured through the parameters of value, basePackages and basepackageclass
- You can configure the filter of the class through the parameters useDefaultFilters, includeFilters and excludeFilters. After being processed by the filter, the remaining classes will be registered in the container
- There is a hidden danger in configuring the scanning range by specifying the package name. Renaming the package name will lead to the scanning implementation. Therefore, generally, we can create a marked interface or class in the package to be scanned as the value of basepackagecalasses to control the scanning range of the package
- @The CompontScan annotation will be recursively processed by the ConfigurationClassPostProcessor class, and finally all classes that need to be registered will be obtained.
Case source code
Link: https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ Extraction code: zr99