1. 程式人生 > 其它 >springboot原理(一)

springboot原理(一)


title: springboot原理(一)
date: 2018/6/24 12:12:55
tags: [springboot]
categories:

  • 開發
  • java

Spring Boot特點

  • springboot 是一個快速整合第三方框架 (maven子父專案依賴)
  • 簡化XML配置(完全採用註解化)
  • 內建Http伺服器(Jetty、undertow,Tomcat)
  • 最終以java應用程式進行執行(當然也可以打成war包)

完全採用註解化

  • 傳統的WEB-INF/web.xml
  • @EnableWebMvc (Spring mvc) -- 用java程式碼操作spingmvc初始化過程

內建Http伺服器

SPI機制

來源
  • jdk 為了方便應用程式進行擴充套件,提供了預設的 SPI (Service Provider Interface) 實現(ServiceLoader)
  • SPI機制主要針對廠商或者外掛,有點類似IOC的思想,就是將裝配的控制權移到程式之外,在模組化設計中這個機制尤其重要
約定

當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。 基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類:java.util.ServiceLoader

DEMO
// 來源於網路
// 定義搜尋介面(會有不同實現方式)
package my.xyz.spi;  
import java.util.List;  
public interface Search {  
   public List serch(String keyword);  
}   
/* 
* A公司實現 
在jar包中META-INF/services/my.xyz.spi.Search檔案中寫下如下內容:
com.A.spi.impl.FileSearch
* B公司實現 
在jar包中META-INF/services/my.xyz.spi.Search檔案中寫下如下內容:
  com.B.spi.impl.DatabaseSearch
*/


package com.xyz.factory;  
import java.util.Iterator;  
import java.util.ServiceLoader;  
import my.xyz.spi.Search;  
public class SearchFactory {  
    private SearchFactory() {  
    }  
    public static Search newSearch() {  
        Search search = null;  
        ServiceLoader<Search> serviceLoader = ServiceLoader.load(Search.class);  
        Iterator<Search> searchs = serviceLoader.iterator();  
        if (searchs.hasNext()) {  
            search = searchs.next();  
        }  
        return search;  
    }  
}  

package my.xyz.test;  
import java.util.Iterator;  
import java.util.ServiceLoader;  
import com.xyz.factory.SearchFactory;  
import my.xyz.spi.Search;  
public class SearchTest {  
    public static void main(String[] args) {  
        Search search = SearchFactory.newSearch();  
        search.search("java spi test");  
    }  
} 

Spring SPI
  • spring 也為我們提供了spi實現 SpringFactoriesLoader , 允許開發人員通過

    META-INF/spring.factories 檔案進行擴充套件

  • spring boot 正是利用這個擴充套件點,在 spring-framework 的基礎上為我們集成了常用的開源框架

  • spring spi 內建tomcat
  • java 程式碼建立tomcat例項
  • tomcat 載入.class ..

內建tomcat

  • 其實就是一個jar包 tomcat-embed-core.jar (springboot2.0依賴tomcat8.5+)

  • 然後用java程式碼建立tomcat例項

  • 可以用tomcat載入servlet

    new Tomcat - > tomcatServer
    setPort();
    new StandardContext
    StandardContext.setPath
    //監聽上下文
    StandardContext.addLifecycleListener(new FixContextListener())
    //把context新增到server-host
    tomcatServer.getHost.addChild(StandardContext);    
    tomcatServer.addServlet(new XXXServlet());
    tomcatServer.addServletMapping
    tomcatServer.start();
    tomcatServer.await();
    
    
  • 用tomcat載入springmvc註釋啟動方式 ,就是springboot啟動的大致原理了

    • 實現 AbstractAnnotationConfigDispatcherServletInitializer初始化dispatcherservlet

    • 實現以下方法

      • 載入spring 根配置資訊

        // RootConfig  componentScan("packages")
        class[] getRootConfigClasses()
        
      • 載入springmvc config

        // 用java程式碼方式配置springmvc  實現WebMvcConfigurerAdapter 
        // @componentScan("packages"),@enableWebMvc,@Config
        class[] getServletConfigClasses()
        
      • url mapping,攔截所有請求

        String [] getServletMappings(new String[]{"/"})
        
    • 用tomcat載入target/classes下面的class

Spring Boot啟動原理

@Target({ElementType.TYPE})  //註解的適用範圍,其中TYPE用於描述類,介面(包括包註解型別)活enum宣告
@Retention(RetentionPolicy.RUNTIME)//註解的生命週期,保留到class檔案中(三個生命週期)
@Documented  //  表明這個註解應該被javadoc記錄
@Inherited  // 子類可以繼承該註解
// 以上四個註解是java註解的元註解,不做說明
@SpringBootConfiguration   // 繼承了Configuration,表示當前是註解類
@EnableAutoConfiguration   // 開啟springboot 的註解功能,springboot的四大神器之一,其中藉助了@import的幫助
@ComponentScan(      // 掃描路徑設定
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    ...
}

@Configuration

springIOC容器的配置類,任何一個標註了@Configuration的java類都是一個javaConfig配置類

任何一個標註了@Bean的方法,其返回值將作為一個bean定義註冊到Spring的IOC容器中,方法名預設為該bean定義的id。如果一個bean的定義依賴其他bean,則直接呼叫對應的javaConfig類中依賴bean的建立方法就可以了。

@ComponentScan

@ComponentScan這個註解就是自動掃描載入符合條件的元件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義載入到Ioc容器中。我們可以通過basePackages等屬性來細粒度的定製@ComponentScan自動掃描的範圍,如果不指定,則預設spring框架實現會從宣告@ComponentScan所在類的package進行掃描。(這也就是為什麼springboot 的啟動類最好放在root package路徑下的原因,因為預設不指定basePackages)

@EnableAutoConfiguration

藉助@Import,將所有符合自動配置條件的bean定義載入到Ioc容器中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 以上四個註解是java註解的元註解,不做說明
@AutoConfigurationPackage  //  自動配置包
@Import({AutoConfigurationImportSelector.class})  //  匯入自動配置的元件
public @interface EnableAutoConfiguration {
    ...
}

@Import({AutoConfigurationImportSelector.class}) ,藉助AutoConfigurationImportSelector,

@EnableAutoConfiguration可以幫助springboot應用將所有符合條件的配置都載入到當前SpringBoot建立並使用的Ioc容器。藉助SpringFactoriesLoader工具類的支援,@EnableAutoConfiguration可以智慧的自動配置

由以上的分析可知,自動配置的幕後英雄是SpringFactoriesLoader

SpringFactoriesLoader

先來看看該類定義是什麼樣子的

public final class SpringFactoriesLoader {
// 最耀眼的屬性, 定義了一個檔案路徑 META-INF/spring.factories
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...   
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
 ...
}
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
... 
    }
}

配置檔案META-INF/spring.factories中最後的類名都是XXXAutoConfiguration,然後就是配合@EnableAutoConfiguration註解,拿著配置檔案中定義好的類名通過反射例項化為對應的標註了@Configuration的JavaConfig形式的IoC容器配置類,然後彙總為一個並載入到IoC容器。

執行流程

通過檢視原始碼,我們知道SpringBoot應用的執行入口是run方法。我們也是從該方法入手的。大體的流程如下:

  • 如果我們使用的是SpringApplication的靜態run方法,那麼,這個方法裡面首先要建立一個SpringApplication

    的例項,然後呼叫這個建立好的SpringApplication是例項方法。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
     return (new SpringApplication(primarySources)).run(args);
}

根據classpath裡面是否存在某個特徵類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該建立一個為Web應用使用的ApplicationContext型別。

  • 使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationContextInitializer

  • 使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationListener

推斷並設定main方法的定義類。

  • SpringApplication例項初始化完成並且完成設定後,就開始執行run方法的邏輯了,方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查詢到並載入的SpringApplicationRunListener。呼叫它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應用要開始執行咯!”。
  • 建立並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。
  • 遍歷呼叫所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當前SpringBoot應用使用的Environment準備好了咯!”。
  • 如果SpringApplication的showBanner屬性被設定為true,則列印banner。
  • 根據使用者是否明確設定了applicationContextClass型別以及初始化階段的推斷結果,決定該為當前SpringBoot應用建立什麼型別的ApplicationContext並建立完成,然後根據條件決定是否新增ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前準備好的Environment設定給建立好的ApplicationContext使用。
  • ApplicationContext建立好之後,SpringApplication會再次藉助Spring-FactoriesLoader,查詢並載入classpath中所有可用的ApplicationContext-Initializer,然後遍歷呼叫這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經建立好的ApplicationContext進行進一步的處理。(回撥)
  • 遍歷回撥所有SpringApplicationRunListener的contextPrepared()方法。
  • 我們可以按需實現 ApplicationContextInitializer , SpringApplicationRunListener
  • 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置載入到已經準備完畢的ApplicationContext。
  • 遍歷呼叫所有SpringApplicationRunListener的contextLoaded()方法。
  • 呼叫ApplicationContext的refresh()方法,完成IoC容器可用的最後一道工序。
  • 查詢當前ApplicationContext中是否註冊有CommandLineRunner,如果有,則遍歷執行它們。

以上就是SpringBoot的啟動流程。

自定義Starter

考慮使用到的依賴

編寫自動配置

// 參考WebMvcAutoConfiguartion.java // springmvc自動配置
@Configuration //指定這個類為配置類
@ConditionalOnXXX  // 滿足條件配置類生效
@AutoConfigureAfter // 配置順序
@Bean // 向容器中新增元件

@ConfigurationProperties
@EnableConfigurationProperties // 讓xxxProperties生效並加入到容器

// 自動配置類要想能夠載入,需要把它配置到 META-INF/spring.factories

定義模式

  • (啟動器)xxx-starter : 一個空的jar包

    • 提供依賴管理,這些依賴可用於自動裝配或者其它類庫

    • 如果他人要使用此啟動器,直接依賴xxx-starter.jar就可以

    • 推薦命名規則

      • 官方的:spring-boot-starter-模組名 ,eg. spring-boot-starter-web , spring-boot-starter-jdbc
      • 自定義:模組名-spring-boot-starter ,eg. mybatis-spring-boot-starter
  • (自動配置模組)xxx-starter-autoconfigurer

    • 被啟動器依賴