@SpringBootApplication 之 @EnableAutoConfiguration
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/14917975.html
原理
@SpringBootApplication 是一个复合注解,包括
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
这3个注解的作用就是把项目工程中定义的Bean 注入到 Spring IoC 容器中。
@EnableAutoConfiguration 源码
Note:
@Import 作用就是将指定的类实例注入到Spring IoC 容器中
@Import 分析
Project Directory
Maven Dependency
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> </parent> <groupId>org.fool</groupId> <artifactId>hellospring</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
SRC
MockUserBean.java
package org.fool.spring.imports; public class MockUserBean { }
MockUserService.java
package org.fool.spring.imports; import org.springframework.context.annotation.Import; import org.springframework.stereotype.Component; @Component @Import({MockUserBean.class}) public class MockUserService { }
Note:
@Import({MockUserBean.class})
MainTest.java
package org.fool.spring.imports; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MockUserService.class); MockUserBean mockUserBean = context.getBean(MockUserBean.class); MockUserService mockUserService = context.getBean(MockUserService.class); System.out.println(mockUserBean); System.out.println(mockUserService); } }
Run
反之,如果将@Import 注释掉
再次运行MainTest.java,会抛出异常
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.fool.spring.imports.MockUserBean' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127) at org.fool.spring.imports.MainTest.main(MainTest.java:9)
AutoConfigurationImportSelector 分析
Note: 重点关注 DeferredImportSelector
Note: 可以看到DeferredImportSelector 继承了 ImportSelector 接口
Note: ImportSelector 接口定义了一个selectImports 方法,英文注释写的很明白,选择哪些classes 需要被注册到Spring IoC 容器中,一般会和 @Import 注解一起使用。
自定义实现类似 @EnableAutoConfiguration 的注解
Project Directory
SRC
MockRoleBean.java
package org.fool.spring.selector; public class MockRoleBean { }
MockUserBean.java
package org.fool.spring.selector; public class MockUserBean { }
MockImportSelector.java
package org.fool.spring.selector; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; public class MockImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"org.fool.spring.selector.MockUserBean", "org.fool.spring.selector.MockRoleBean"}; } }
Note: MockImportSelector.java 实现了 ImportSelector 接口
EnableMockConfig.java
package org.fool.spring.selector; import org.springframework.context.annotation.Import; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(MockImportSelector.class) public @interface EnableMockConfig { }
Note:@Import 注解和 MockImportSelector 一起使用
MockConfig.java
package org.fool.spring.selector; @EnableMockConfig public class MockConfig { }
Note:MockConfig 使用了 @EnableMockConfig 注解
MainTest.java
package org.fool.spring.selector; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainTest { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MockConfig.class); MockUserBean mockUserBean = context.getBean(MockUserBean.class); MockRoleBean mockRoleBean = context.getBean(MockRoleBean.class); System.out.println(mockUserBean); System.out.println(mockRoleBean); } }
Run
深度剖析 @EnableAutoConfiguration
Debug 运行 Application.java
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Debug Step1:选择要注册到Spring IoC 容器的classes
Debug Step2:
Debug Step3:
Debug Step4:
Debug Step5:
Debug Step6:spring.factories 是SpringBoot 的解耦扩展机制,这种机制实际上是仿照了Java SPI 扩展机制来实现的
Debug Step7:执行完getCandidateConfigurations 方法后,可以看到在spring.factories 中的需要被装载到Spring IoC 容器中的127个classes
自定义实现 spring.factories
Project Directory
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.fool.spring.factory.AppConfig
SRC
App.java
package org.fool.spring.factory; public class App { public String info() { return "app desc"; } }
AppConfig.java
package org.fool.spring.factory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public App app() { return new App(); } }
Application.java
package org.fool.core; import org.fool.spring.factory.App; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); App app = context.getBean(App.class); System.out.println(app.info()); } }
Debug Run
Note:自定义的AppConfig 已经被装载到Spring IoC 容器中
查看最后的启动结果
Note:
AppConfig.java 是在 org.fool.spring.factory 包下,而标注了@SpringBootApplication 注解的 Application.java 是在 org.fool.core 包下,两个类属于不同包。
所以,如果SpringBoot 装载不在 标注了@SpringBootApplication 注解的启动类所在包及其子包目录的classes,需要在 META-INF/spring.factories 自定义注册,否则抛异常。
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.fool.spring.factory.App' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127) at org.fool.core.Application.main(Application.java:12)
反之,如果AppConfig.java 和 标注了@SpringBootApplication 注解的 Application.java 是在同一个包目录或者在其子包目录,是不需要在 META-INF/spring.factories 自定义注册的。
欢迎点赞关注和收藏