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
- 被啟動器依賴