SpringBoot原理分析-1
SpringBoot原理分析
作為一個javaer,和boot打交道是很常見的吧。熟悉boot的人都會知道,啟動一個springboot應用,就是用鼠標點一下啟動main方法,然后等著就行了。我們來看看這個main里面。
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
這個類很簡單吧,有三個注意點。
- ExampleApplication就是類名,為了規范起見,這個類名一般都是xxxApplication。
- 有一個注解@SpringBootApplication。
- 然后就一個main方法,里面使用了SpringApplication的靜態方法run(),傳進去了倆參數。
這就神奇地啟動起來了,為什么呢?
分析的版本:SpringBoot 2.7.18
需要結合以前用xml文件來配置Spring容器的形式來對比Boot用注解的形式
1.注解&自動配置
目前只知道注解的作用即可,至于這些注解是怎么起作用的,見后續...
@SpringBootApplication
這一節來詳細聊一聊這個注解。@SpringBootApplication 是 Spring Boot 框架中的一個核心注解,用于簡化 Spring Boot 應用的配置和啟動
它的作用
@SpringBootApplication標注在應用的主類上,用于啟動 Spring Boot 應用。- 它啟用了 Spring Boot 的自動配置機制,根據項目的依賴自動配置 Spring 應用。
- 組件掃描:它啟用了組件掃描,自動發現并注冊帶有
@Component、@Service、@Repository、@Controller等注解的類。 - 配置類:它標記該類為 Spring 的配置類,相當于
@Configuration注解。
點進去看看,該注解的結構如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 四個元注解
@SpringBootConfiguration
@EnableAutoConfiguration //啟用SpringBoot的自動配置機制。SpringBoot會根據類路徑中的依賴自動配置應用。例如,如果類路徑中有spring-boot-starter-web,SpringBoot會自動配置Tomcat和SpringMVC。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) // 啟用組件掃描,并排除一些特定的過濾器。默認是注解標注的類所在包及其子包。實際上是不是就是主啟動類所在包及其子包!!!!!
public @interface SpringBootApplication {
..............
}
// 發現這是一個組合注解
@SpringBootConfiguration 注解:主要作用是標記一個類為 Spring Boot 的配置類與 @Configuration 類似,但它專門用于 Spring Boot 應用。這個注解和@ComponentScan注解結合到一起,達成了如下效果。(左邊是xml文件配置形式,右邊是SpringBoot注解的形式),這倆注解在一起,就是掃描主啟動類所在包及其子包下的被@Controller、@Service、.....標注的類,將他們歸到Spring容器里面去。

@EnableAutoConfiguration
@EnableAutoConfiguration注解: 這個注解不得了啊。
@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 {};
}
發現@Import注解。這個注解的作用是干啥的?這里給出GPT的回答:@Import 是 Spring 框架提供的一個注解,用于將一個或多個配置類(@Configuration 類)或組件類導入到當前的 Spring 應用上下文中。它可以用來顯式地引入其他配置類或 Bean 定義,從而實現模塊化配置和代碼復用。
這里有一個非常重要的東西,那就是當前的Spring上下文,也就是主啟動類所在包及其子包,@Import(AutoConfigurationImportSelector.class)這句話的意思是將SpringBoot官方寫的AutoConfigurationImportSelector類導入到當前Spring上下文中,在當前Spring上下文注冊這樣一個bean。
AutoConfigurationImportSelector探究
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
// 實現了DeferredImportSelector接口,DeferredImportSelector實現了ImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
...........
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); // 1.進去
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// 2.這個方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3.進入這里
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
...........
}
// 4. 3調用的這個方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
// 5. 繼續往下
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
// 6. 5處調用的方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
...... // loadSpringFactories
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 7
// 最終可以在loadSpringFactories方法里面看到這樣一行代碼
//Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
// public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 結合官方給的解釋The location to look for factories. Can be present in multiple JAR files. 尋找工廠的位置。可以存在于多個 JAR 文件中。
}
結合idea打斷點調試:我們發現getCandidateConfigurations掃描到了引入的所有jar包的META-INF/spring.factories,共有156個xxxAutoConfigure類。但是這些都會用到嗎,別忘了,SpringBoot有個非常強大的特點:那就是導入場景、對應的場景才會生效!!

繼續往下走,請看下圖:(發現只有50個了,例如上圖里面的amqp,由于項目中沒有用到,故在這里就沒有了,經過了filter過濾)

以RabbitMq自動配置為例
@AutoConfiguration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class }) // 這兩個類存在時才生效,項目里面沒導入相關jar包,故RabbitAutoConfiguration不會被導入Spring容器生效,下面就來說這個注解
@EnableConfigurationProperties(RabbitProperties.class)
@Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {
................
}
@ConditionalOnClass
先看看其源碼
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class) // 這里***
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
@ConditionalOnClass注解的作用是當項目中存在某個類時才會使標有該注解的類或方法生效;
先看我們的主類【1. 此時,沒有引入test.demo.test.list.Student所在的maven依賴】
/* 下面是這倆類
public class Cat {
private Integer id;
private String name;
}
public class Dog {
private Integer id;
private String name;
}*/
@SpringBootApplication
@MapperScan("com.feng.tackle.dao")
public class DateApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DateApplication.class, args);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Iterator<String> iterator = beanFactory.getBeanNamesIterator();
while (iterator.hasNext()) {
String name = iterator.next();
if ( name.equals("dog01") || name.equals("cat01") ) System.out.println(name); // 看看有沒有這兩個名字的bean
}
}
}
// 最后的輸出結果
/*
cat01
*/
在配置類中,我們這樣做
@Configuration
public class ConditionTestConfig {
@ConditionalOnClass(name = "test.demo.test.list.Student") // 類路徑有這個類,就往容器中放入該bean
@Bean
public Dog dog01() {
return new Dog(2, "汪汪汪");
}
@ConditionalOnMissingClass("test.demo.test.list.Student") // 類路徑沒有這個類,就往容器中放入該bean
@Bean
public Cat cat01() {
return new Cat(1, "喵喵喵");
}
}
main最后的輸出結果是cat01。"test.demo.test.list.Student"是另一個maven項目里面的類。

【2. 項目中引入Student所在的maven】
<dependency>
<groupId>com.feng.test</groupId>
<artifactId>test-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
再次啟動main,輸出結果dog01

通過這個例子,我們可以知道SpringBoot自動配置,導入具體starter,對應的場景才會生效。【由于沒有導入MQ的starter,故此配置類不會生效】

總結
**自動配置: **
以spring-boot-starter-web為例子,引入該啟動器,他的父級pom里面有spring-boot-starter,spring-boot-starter的父級pom有spring-boot-autoconfigure。
spring-boot-autoconfigure里面有Spring官方定義的所有場景。

@EnableAutoConfiguration注解:這是自動配置的入口,通常由@SpringBootApplication注解組合引入。spring.factories文件:Spring Boot 通過META-INF/spring.factories文件加載自動配置類。- 條件化配置:通過
@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean等)實現按需加載配置。
自動配置的流程:
- 加載自動配置類
@SpringBootApplication注解:該注解是一個組合注解,包含了@EnableAutoConfiguration,用于啟用自動配置。@EnableAutoConfiguration的作用:該注解會通過SpringFactoriesLoader加載META-INF/spring.factories文件中定義的自動配置類。
- 條件化配置
自動配置類通常使用 @Conditional 系列注解來控制是否生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
// 自動配置邏輯
}
//如果類路徑中存在 DataSource 和 EmbeddedDatabaseType,且容器中沒有 ConnectionFactory Bean,則自動配置 DataSource。
- 加載配置屬性
@EnableConfigurationProperties:自動配置類通常會通過該注解加載配置屬性。application.properties或application.yml:Spring Boot 會讀取這些配置文件中的屬性,并將其綁定到對應的配置類中。
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
// getters and setters
}
- 注冊bean
- 自動配置類通過
@Bean方法向容器中注冊 Bean。 - 這些 Bean 通常是框架的核心組件,如
DataSource、DispatcherServlet、SecurityFilterChain等。
2. 啟動過程
SpringApplication.run(ExampleApplication.class, args);這一行代碼做了啥?這才是重點!
從run開始看,一層一層往下面點進去
// 1.
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { // 會返回一個正在運行的Spring容器
return run(new Class<?>[] { primarySource }, args); // 調用重載的run方法
}
// 2.
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args); // 先調用該構造方法,然后再調用run方法,將程序參數傳進去
}
// 3.
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources); //primarySource 是傳入的主配置類(通常帶有 @SpringBootApplication 注解的類),Spring Boot 會將其作為配置源。
}
// 4. [構造方法] 構造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
........ // 見下一節構造方法
}
先調用該構造方法,然后再調用run方法
①構造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader; // null的
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 一、設置主配置類
this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 二、推斷應用類型
this.bootstrapRegistryInitializers = new ArrayList<>( // 三、
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 四、
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 五、
this.mainApplicationClass = deduceMainApplicationClass(); // 六、通過堆棧信息推斷出主類(即 main 方法所在的類)。
}
1.推斷應用類型
WebApplicationType.deduceFromClasspath()方法會根據類路徑中是否存在特定的類來推斷應用類型。- 可能的類型包括:
WebApplicationType.SERVLET:基于 Servlet 的 Web 應用(如 Spring MVC)。WebApplicationType.REACTIVE:基于 Reactive 的 Web 應用(如 Spring WebFlux)。WebApplicationType.NONE:非 Web 應用。
2.加載并初始化 BootstrapRegistryInitializer 實例
分析一下核心方法getSpringFactoriesInstances,下面兩個都會調用這個。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 下一步
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
// SpringFactoriesLoader.loadFactoryNames(type, classLoader) 到這兒來了
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
//loadSpringFactories(classLoaderToUse)
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
// 從緩存map中拿到對應classLoader的map,如果該classLoader已經存在了,就不用走下面的了
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
/*
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
結合官方給的解釋The location to look for factories. Can be present in multiple JAR files. 尋找工廠的位置。可以存在于多個 JAR 文件中。
*/
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
..............
return result;
}
可以看到這里,在第一章《注解&自動配置》也出現了這個,如今在SpringApplication的構造方法,底層也調用到了這里。二者有什么區別或者聯系呢?
SpringFactoriesLoader.loadFactoryNames是Spring Boot 提供的工具方法,用于從 META-INF/spring.factories 文件中加載指定類型的配置類。
兩者都依賴于 META-INF/spring.factories 文件,該文件是 Spring Boot 自動配置和擴展機制的核心配置文件。兩者都通過類路徑掃描,加載所有 META-INF/spring.factories 文件,并解析出指定類型的配置類。
(1)調用位置
AutoConfigurationImportSelector:- 位于
org.springframework.boot.autoconfigure包中。 - 是 Spring Boot 自動配置的核心組件之一,負責加載自動配置類。
- 在 Spring 容器的配置類解析階段被調用(具體是在
@EnableAutoConfiguration注解的處理過程中)。
- 位于
SpringApplication:- 位于
org.springframework.boot包中。 - 是 Spring Boot 應用的啟動類,負責初始化應用上下文、加載配置等。
- 在應用啟動時被調用。
- 位于
(2)加載的配置類型
AutoConfigurationImportSelector:- 主要加載
META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration鍵下的自動配置類。 - 這些配置類用于實現 Spring Boot 的自動配置功能(如
DataSourceAutoConfiguration、WebMvcAutoConfiguration等)。
- 主要加載
SpringApplication:- 加載多種類型的配置類,包括:
ApplicationContextInitializerApplicationListenerBootstrapRegistryInitializer
- 這些配置類用于初始化應用上下文、監聽應用事件等。
- 加載多種類型的配置類,包括:
(3)調用時機
AutoConfigurationImportSelector:- 在 Spring 容器解析配置類時調用(具體是在
ConfigurationClassPostProcessor處理@Configuration類時)。 - 屬于 Spring 容器初始化的早期階段。
- 在 Spring 容器解析配置類時調用(具體是在
SpringApplication:- 在應用啟動時調用(具體是在
SpringApplication的構造方法或run方法中)。 - 屬于應用啟動的早期階段。
- 在應用啟動時調用(具體是在
(4)返回值的使用
AutoConfigurationImportSelector:- 返回的自動配置類會被 Spring 容器加載并處理,最終生成相應的 Bean 定義。
- 這些配置類通常包含
@Configuration注解和@Conditional注解,用于按需加載 Bean。
SpringApplication:- 返回的配置類會被直接實例化并注冊到應用中。
- 例如,
ApplicationContextInitializer會被調用以初始化應用上下文,ApplicationListener會被注冊以監聽應用事件。
3.加載并設置 ApplicationContextInitializer 實例
同2
4.加載并設置 ApplicationListener 實例
同2
總結
BootstrapRegistryInitializer:用于應用啟動的最早階段,初始化引導階段的組件。ApplicationContextInitializer:用于在ApplicationContext創建之后、刷新之前,對上下文進行自定義初始化。ApplicationListener:用于監聽應用生命周期中的事件,并在特定階段執行邏輯。
這三者共同擴展了 Spring Boot 應用的啟動過程,提供了靈活的擴展點,可以滿足不同場景下的需求。
5.三者的實現
分別編寫實現類
public class MyBootstrapInit implements BootstrapRegistryInitializer {
@Override
public void initialize(BootstrapRegistry registry) {
System.out.println("【MyBootstrapInit】--------------方法執行了");
}
}
public class MyApplicationContextInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("【MyApplicationContextInit】------------" + applicationContext.getApplicationName());
}
}
public class MyContextListener implements ApplicationListener<ApplicationStartedEvent> { // 該監聽器對此事件感興趣
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
System.out.println("【MyContextListener】--監聽器--" + event.getSource());
}
}
然后在resource目錄下面創建META-INF,在其中的spring.factories中
org.springframework.boot.BootstrapRegistryInitializer=com.feng.tackle.config.source.MyBootstrapInit
org.springframework.context.ApplicationContextInitializer=com.feng.tackle.config.source.MyApplicationContextInit
org.springframework.context.ApplicationListener=com.feng.tackle.config.source.MyContextListener
啟動應用:

疑問? 我可以在這三者的實現類上面加@Configuration注解,不要spring.factories,可以實現類似效果嗎?
答案是,只有ApplicationListener可以。為什么?請看下面的run方法里面
②run
public ConfigurableApplicationContext run(String... args) {
// 記錄應用啟動的開始時間,用于后續計算啟動耗時。
long startTime = System.nanoTime();
//創建一個 BootstrapContext,用于在應用啟動的早期階段提供一些基礎設施支持(如環境配置、屬性源等)================
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 設置系統屬性 java.awt.headless,確保應用在沒有圖形界面環境(如服務器)中也能正常運行
configureHeadlessProperty();
/*
獲取所有 SpringApplicationRunListener 實例,用于監聽應用啟動的各個階段。調用 listeners.starting(),通知監聽器應用正在啟動。
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 加載和配置應用的環境(Environment),包括配置文件(如 application.properties 或 application.yml)、命令行參數等。
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印 Spring Boot 的啟動 Banner(默認或自定義)。
Banner printedBanner = printBanner(environment);
// 根據應用類型(Servlet、Reactive 等)創建相應的 ApplicationContext。
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 準備應用上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 調用 AbstractApplicationContext.refresh() 方法,完成 Bean 的實例化、依賴注入和初始化。
refreshContext(context);
afterRefresh(context, applicationArguments);
// 計算啟動耗時并記錄日志。
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
// 執行所有 ApplicationRunner 和 CommandLineRunner 的實現類,用于在應用啟動后執行自定義邏輯。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 計算應用啟動到就緒的總耗時。
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
這個run這里分析得不夠深入。受篇幅影響,在后續文章中再來分析。
end. 參考
- 尚硅谷雷豐陽老師的課

浙公網安備 33010602011771號