深入理解Spring Boot

Spring Boot是什麼

Spring Boot是有大名鼎鼎的Spring Framework的開發者設計的,旨在用來簡化基於Spring的應用開發。

Spring Boot特性

創建可以直接運行的獨立的應用程序。可以直接內嵌應用服務器到應用程序中,無需生成war包部署到應用服務器中,簡化了web應用的開發部署和測試。引入了自動配置機制。

Spring Boot 設計原則

約定大於配置自動配置

Spring Boot應用是什麼樣子的

使用Spring Initializr來快速生成一個Spring Boot應用,下面只列出最重要的部分。

java

<code>@SpringBootApplication
public class DemoApplication {

\tpublic static void main(String[] args) {
\t\tSpringApplication.run(DemoApplication.class, args);
\t}

}/<code>pom.xml

<code>
<project>\txsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
\t<modelversion>4.0.0/<modelversion>
\t<parent>
\t\t<groupid>org.springframework.boot/<groupid>
\t\t<artifactid>spring-boot-starter-parent/<artifactid>
\t\t<version>2.2.4.RELEASE/<version>
\t\t<relativepath>
\t/<parent>
\t<groupid>com.example/<groupid>
\t<artifactid>demo/<artifactid>
\t<version>0.0.1-SNAPSHOT/<version>
\t<name>demo/<name>
\t<description>Demo project for Spring Boot/<description>

\t<properties>
\t\t<java.version>1.8/<java.version>
\t/<properties>

\t<dependencies>


\t\t<dependency>
\t\t\t<groupid>org.mybatis.spring.boot/<groupid>
\t\t\t<artifactid>mybatis-spring-boot-starter/<artifactid>
\t\t\t<version>2.1.1/<version>
\t\t/<dependency>
\t/<dependencies>

\t<build>
\t\t<plugins>
\t\t\t<plugin>
\t\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t\t<artifactid>spring-boot-maven-plugin/<artifactid>
\t\t\t/<plugin>
\t\t/<plugins>
\t/<build>

/<project>/<code>

Spring Boot是如何簡化Spring 開發的

創建Spring應用,首先要創建對應的ApplicationContext。Spring Boot根據classpath下的Class會自動推斷創建什麼類型的ApplicationContext,開發者只需引入正確的依賴即可。

Spring Boot 如何加載Bean Definition到ApplicationContext

從應用的入口main方法開始來看Spring Boot如何加載Bean Definition的。

<code> public static void main(String[] args) {
\t\tSpringApplication.run(DemoApplication.class, args);
\t}
/<code>

SpringApplication是用來啟動基於Spring的application的。

第一個參數告訴Spring Boot從哪裡找到Bean Definition信息,第二個參數args是應用啟動時傳入的參數。

在這個例子中Bean Definition信息是DemoApplication.class, 為什麼它可以提供Bean Definition信息呢,因為它是@Configuration Class。任何有@Configuration標記的類都可以是Spring Bean Definition的來源。讓我們回顧一下我們的Demo。

<code>@SpringBootApplication
public class DemoApplication {

\tpublic static void main(String[] args) {
\t\tSpringApplication.run(DemoApplication.class, args);
\t}

}
/<code>

@SpringBootApplication在我們的入口類,看一下它的定義。

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
\t\t@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
/<code>

@SpringBootApplication包含了@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan,下面會逐一介紹。

@SpringBootConfiguration

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
...
}
/<code>

它由@Configuraion標註。所以DemoApplication.class可以用來制定Bean Definition的信息。但是我們在這個類裡面沒有看到任何的Bean Definition信息。那spring boot如何找到Bean 定義信息呢。

@ComponentScan

@ComponentScan 是spring XML context:component-scan 元素對應的註解形式,它告訴Spring從哪裡找到@Component。注意@Configuration也是@Component, 所以@Configuration都可以被@ComponentScan識別。在沒有指定搜索路徑的情況下,Spring 會從該註解所標記的Class所屬的package來進行搜索。一個Spring Boot應用程序的典型佈局如下:

<code>com
+- example
+- myapplication
+- Application.java 應用程序入口
|
+- customer
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java
/<code>

@SpringBootApplication註解標記在Application.java上,這樣所有的@Configuration和@Component都能被識別到,然後加載到ApplicationContext,這也體現了約定大於配置的原則。

自動配置

自動配置的含義就是一旦啟用自動配置,一些Bean會自動的加載到ApplicationContext,供其他Bean來使用。 Spring Boot 自動配置的秘密都在EnableAutoConfiguration這裡,正如該註解名字所揭示的,它是用來啟用自動配置的。

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
\t...
}
/<code>

為什麼它可以啟用自動配置呢, 關鍵就在於@Import。@Import與@Configuration結合來引入Bean Definition。@Import可以指定四種Bean Definition來源。

@Configuration Class實現ImportSelector接口的類,該接口的實現者決定哪些@Configuration Class要導入ImportBeanDefinitionRegistrar@Component

在@EnableAutoConfiguration中指定的AutoConfigurationImportSelector.class實現了ImportSelector。該類會掃描classpath 下所有的META-INF/spring.factories文件來找到以org.springframework.boot.autoconfigure.EnableAutoConfiguration為Key的配置項。該Key所對應的配置項就是要加載的@Configuarion class。以MyBatis為例,看看是如何配置的。

<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
/<code>

Spring Boot 加載過程

<code>public ConfigurableApplicationContext run(String... args) {
\t\tStopWatch stopWatch = new StopWatch();
\t\tstopWatch.start();
\t\tConfigurableApplicationContext context = null;
\t\tCollection<springbootexceptionreporter> exceptionReporters = new ArrayList<>();
\t\tconfigureHeadlessProperty();
\t\t//加載所有的SpringApplicationRunListener
\t\tSpringApplicationRunListeners listeners = getRunListeners(args);
\t\t//通知SpringApplicationRunListener SpringBoot開始啟動了
\t\tlisteners.starting();
\t\ttry {
\t\t\t//封裝應用啟動參數
\t\t\tApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
\t\t\t//創建Environment,添加應用啟動參數到Environment中,通知environmentPrepared給SpringApplicationRunListener
\t\t\tConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
\t\t\tconfigureIgnoreBeanInfo(environment);
\t\t\tBanner printedBanner = printBanner(environment);
\t\t\t//創建Application Context
\t\t\tcontext = createApplicationContext();
\t\t\texceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
\t\t\t\t\tnew Class[] { ConfigurableApplicationContext.class }, context);
\t\t\t//關聯Enviromnent和ApplciationContext
\t\t\t//通知contextPrepared
\t\t\t//加載@Configuration class到Application Context
\t\t\t//通知contextLoaded\t\t
\t\t\tprepareContext(context, environment, listeners, applicationArguments, printedBanner);
\t\t\t//刷新Application Context,加載所有的non lazy Bean
\t\t\trefreshContext(context);


\t\t\tafterRefresh(context, applicationArguments);
\t\t\tstopWatch.stop();
\t\t\tif (this.logStartupInfo) {
\t\t\t\tnew StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
\t\t\t}
\t\t\t//通知Spring Boot started
\t\t\tlisteners.started(context);

\t\t\t//調用ApplicationRunner和CommandLineRunner
\t\t\tcallRunners(context, applicationArguments);
\t\t}
\t\tcatch (Throwable ex) {
\t\t\thandleRunFailure(context, ex, exceptionReporters, listeners);
\t\t\tthrow new IllegalStateException(ex);
\t\t}

\t\ttry {
\t\t\t//通知listener Spring Boot正在Running
\t\t\tlisteners.running(context);
\t\t}
\t\tcatch (Throwable ex) {
\t\t\thandleRunFailure(context, ex, exceptionReporters, null);
\t\t\tthrow new IllegalStateException(ex);
\t\t}
\t\treturn context;
\t}/<springbootexceptionreporter>/<code>

Spring Boot事件機制

在Spring Boot的加載過程中,SpringApplicationRunListener貫穿其中。每一個階段完成之後就會通知SpringApplicationRunListener。 SpringApplicationRunListener中定義了Spring Boot加載的幾個階段,如下:

startingenvironmentPreparedcontextPreparedcontextLoadedstartedrunningfailed

SpringApplicationRunListener的具體實現是EventPublishingRunListener。

<code>org.springframework.boot.SpringApplicationRunListener=\\
org.springframework.boot.context.event.EventPublishingRunListener
/<code>

在加載過程的每一階段,EventPublishingRunListener都會把當前階段進行封裝成ApplicationEvent,然後publish出去。這樣ApplicationEvent的監聽者ApplciationListener就可以參與SpringBoot的加載過程來實現任何想要實現的功能。

為了能夠監聽到Spring Boot加載的每一個階段,Spring Boot 從META-INF/spring.factories中加載ApplicationListener,而不是從Spring ApplicationContext中獲取到ApplicationListener,因為ApplicationContext中的ApplicationListener加載的比較晚。

<code># Application Listeners
org.springframework.context.ApplicationListener=\\
org.springframework.boot.ClearCachesApplicationListener,\\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\\
org.springframework.boot.context.FileEncodingApplicationListener,\\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\\
org.springframework.boot.context.config.ConfigFileApplicationListener,\\
org.springframework.boot.context.config.DelegatingApplicationListener,\\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\\
org.springframework.boot.context.logging.LoggingApplicationListener,\\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
/<code>

Spring Boot本身就是利用這個機制來實現了application.properties的加載,參見ConfigFileApplicationListener

Spring Boot Starter

Starter有兩個作用

引入合適的依賴到項目中自動配置

例如項目中決定使用mybatis,就可以引入mybatis starter到項目中,這樣mybatis的依賴會自動添加到項目中。使用mybatis,SqlSessionFactory是必不可少的, Spring Boot的自動配置機制會自動加載SqlSessionFactory到ApplicationContext中。