原理
@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' availableat 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 {@Overridepublic 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;@EnableMockConfigpublic 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
@SpringBootApplicationpublic 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;@Configurationpublic class AppConfig {@Beanpublic 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;@SpringBootApplicationpublic 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' availableat 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 自定义注册的。
泰克风格 只讲干货 不弄玄虚




