深入理解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來源。

    1. @Configuration Class
    2. 實現ImportSelector接口的類,該接口的實現者決定哪些@Configuration Class要導入
    3. ImportBeanDefinitionRegistrar
    4. @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加載的幾個階段,如下:

    1. starting
    2. environmentPrepared
    3. contextPrepared
    4. contextLoaded
    5. started
    6. running
    7. failed

    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有兩個作用

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

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



    分享到:


    相關文章: