preface
SpringBoot provides many annotations starting with Enable. These annotations are used to dynamically Enable some functions, and its underlying principle is to use the @ Import annotation to Import some configuration classes, such as realizing the dynamic loading of beans. This sentence sounds confused, so let's think about a question: can the SpringBoot project directly obtain the beans defined in the jar package? With questions, let's analyze the source code of @ Enable * annotation of SpringBoot automatic configuration.
verification
1. First, take a brief look at the startup annotation @ SpringBootApplication
Click in the source code as follows:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { ... }
Explanation:
@Target({ElementType.TYPE}) --------- declare that the scope of annotation is class;
@Retention(RetentionPolicy.RUNTIME) - declares that the annotation action time is runtime;
@Documented -------------------------------------- generate javaDoc document by declaration;
@SpringBootConfiguration ----------------- click in and the source code is as follows. It shows that @ SpringBootConfiguration is a composite annotation. In fact, it is a @ Configuration, that is, a tag of a Configuration class. This is the fundamental reason why beans can be created in the main Configuration class of SpringBoot:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration //The core of this annotation @Indexed public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
@EnableAutoConfiguration ----------------- Click to see the source code and find that it is also a composite annotation with an @ Import annotation
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) //This annotation is the core public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
Question: can the SpringBoot project directly obtain the beans defined in the jar package?
1. Create two module presentations
This paper creates two modules: springBoot enable and springBoot embedded. The former represents our springBoot project, and the latter simulates a three-party jar package
Write springboot embedded
1. Define Bean class User.java
public class User { }
2. Define the configuration class UserConfig.java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class UserConfig { @Bean public User user(){ return new User(); } }
3. Write springboot enable
1. Introducing the coordinate dependency of springboot embedded into pom.xml
<dependency> <groupId>com.test</groupId> <artifactId>springboot-embedded</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
2. Startup class
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class SpringbootEnableApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args); User user = (User)context.getBean("user"); System.out.println(user); } }
After startup, an error is reported
nosuch bean
It shows that the SpringBoot project cannot directly obtain the beans defined in the jar package, so the problem comes again. Why can we directly use an object named redisTemplate after introducing Redis coordinates in pom.xml? The root cause is the @ Import annotation
The SpringBoot implementation uses beans defined in third-party jar packages
It has been verified that the SpringBoot project cannot directly obtain the beans defined in the jar package because @ ComponentScan, one of the internal combinations of the @ SpringBootApplication annotation on the startup class, is a package scanning scope annotation. The default package scanning scope is to scan the peer packages and sub packages of the startup class annotated by @ SpringBootApplication. We found that the package of the startup class is com.itlean, The package of UserConfig.java is com.embedded.config, which has no peer or inclusion relationship. If you want to scan UserConfig.java during startup, there are three solutions, as follows:
1. The first method (scanning range)
Modify the startup class, add the annotation of package scanning range, and manually scan the package of UserConfig.java:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan("com.test.config") //UserConfig.java manually scans and joins IOC container public class SpringbootEnableApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args); User user = (User)context.getBean("user"); System.out.println(user); } }
But is this method too extensive? I use an object. After that, I have to know the package in which this object is located. Then I need to use redisTemplate object of Redis. I have to find out which package this thing is in, and then add package scanning in @ ComponentScan annotation of startup class. It must be unrealistic
2. The second method is @ Import annotation
First look at the source code and find that the input parameter is Class<?>[] value();That is, an array of classes. One more mouth, ha, in SpringBoot Used in@Import These classes introduced by the annotation are loaded into the IOC In the container.
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { Class<?>[] value(); }
As mentioned above, let's modify the startup class annotation again as follows:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; @SpringBootApplication @Import(UserConfig.class)//Directly introduce the User's configuration class into public class SpringbootEnableApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args); User user = (User)context.getBean("user"); System.out.println(user); } }
This method is much more indirect than method 1. You don't need to know the package path of the used Bean, but you still need to remember the name of the configuration class. I use an object and need to know the name of the configuration class corresponding to this object. Obviously, it's not friendly enough. The next method 3 is the ultimate optimization of @ Import usage.
3. The third method is @ Import annotation encapsulation
In a third party jar Create a new annotation class in@Import Encapsulate with annotations:
import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(UserConfig.class)// If UserConfig.class is directly introduced into the third party, the caller does not need to know the package path and configuration class name public @interface EnableUser { }
The caller startup class is modified as follows:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication @EnableUser //This is already very simple to use public class SpringbootEnableApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args); User user = (User)context.getBean("user"); System.out.println(user); } }
In actual development, the last method is used.
@ Enable * annotation in SpringBoot
The third way is SpringBoot The principle of bottom automatic assembly, so@Enable*This kind of annotation is the annotation that enables some functions, and the underlying is the use of@Import Way to achieve Bean Dynamic loading, not used@EnableUser I can't use this annotation User This object. Let's go back to the annotation of the startup class@SpringBootAppliction Annotation, there is one in the internal annotation combination@EnableAutoConfiguration Notes, as follows:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { ... }
@The EnableAutoConfiguration annotation uses the @ Import annotation internally, as follows:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }