一、引言 SpringBoot的一大優勢就是Starter,由於SpringBoot有很多開箱即用的Starter依賴,使得我們開發變得簡單,我們不需要過多的關註框架的配置。 在日常開發中,我們也會自定義一些Starter,特別是現在微服務框架,我們一個項目分成了多個單體項目,而這些單體項目中會引用 ...
一、引言
SpringBoot的一大優勢就是Starter,由於SpringBoot有很多開箱即用的Starter依賴,使得我們開發變得簡單,我們不需要過多的關註框架的配置。
在日常開發中,我們也會自定義一些Starter,特別是現在微服務框架,我們一個項目分成了多個單體項目,而這些單體項目中會引用公司的一些組件,這個時候我們定義Starter,可以使這些單體項目快速搭起,我們只需要關註業務開發。
在此之前我們再深入的瞭解下SpringBoot啟動原理。而後再將如何自定義starter。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
這裡是創建一個SpringApplication對象,並調用了run方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); //保存主配置類 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //確定web應用類型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); //從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然後保存起來 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //從類路徑下找到ETA-INF/spring.factories配置的所有ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //從多個配置類中找到有main方法的主配置類 this.mainApplicationClass = deduceMainApplicationClass(); }
從這個方法中可以看出,這個
第一步:保存主配置類。
第二步:確定web應用類型。
第三步:setInitializers方法,這個方法走我們看帶入的參數是getSpringFactoriesInstances(ApplicationContextInitializer.class),我們再往下查看getSpringFactoriesInstances
再進入這個方法:
這裡就是從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然後再保存起來,放開斷點,我們可以看到這個時候獲取到的
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); //從類路徑下META-INF/spring.factories獲取SpringApplicationRunListeners SpringApplicationRunListeners listeners = getRunListeners(args); //回調所有的獲取SpringApplicationRunListener.starting()方法 listeners.starting(); try { //封裝命令行參數 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //準備環境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//創建環境完成後回調SpringApplicationRunListener.environmentPrepared();表示環境準備完成 configureIgnoreBeanInfo(environment); //列印Banner圖 Banner printedBanner = printBanner(environment); //創建ApplicationContext,決定創建web的ioc還是普通的ioc context = createApplicationContext(); //異常分析報告 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //準備上下文環境,將environment保存到ioc中 //applyInitializers():回調之前保存的所有的ApplicationContextInitializer的initialize方法 //listeners.contextPrepared(context) //prepareContext運行完成以後回調所有的SpringApplicationRunListener的contextLoaded() prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新容器,ioc容器初始化(如果是web應用還會創建嵌入式的Tomcat) //掃描,創建,載入所有組件的地方,(配置類,組件,自動配置) refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //所有的SpringApplicationRunListener回調started方法 listeners.started(context); //從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回調, //ApplicationRunner先回調,CommandLineRunner再回調 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { //所有的SpringApplicationRunListener回調running方法 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } //整個SpringBoot應用啟動完成以後返回啟動的ioc容器 return context; }
前面的代碼不用分析,主要是準備對象,我們從 SpringApplicationRunListeners listeners = getRunListeners(args)開始分析,
第一步:是從類路徑下META-INF/spring.factories獲取SpringApplicationRunListeners,
這個方法跟前面分析的兩個獲取配置方法類似。
第二步:回調所有的獲取SpringApplicationRunListener.starting()方法。
第三步: 封裝命令行參數。
第四步:準備環境,調用prepareEnvironment方法。
第五步:列印Banner圖(就是啟動時的標識圖)。
第六步:創建ApplicationContext,決定創建web的ioc還是普通的ioc。
第七步:異常分析報告。
第八步:準備上下文環境,將environment保存到ioc中,這個方法需要仔細分析下,我們再進入這個方法
這裡面有一個applyInitializers方法,這裡是回調之前保存的所有的ApplicationContextInitializer的initialize方法
還有一個listeners.contextPrepared(context),這裡是回調所有的SpringApplicationRunListener的contextPrepared(),
最後listeners.contextLoaded(context) 是prepareContext運行完成以後回調所有的SpringApplicationRunListener的contextLoaded()。
第九步:刷新容器,ioc容器初始化(如果是web應用還會創建嵌入式的Tomcat),這個就是掃描,創建,載入所有組件的地方,(配置類,組件,自動配置)。
第十步:所有的SpringApplicationRunListener回調started方法。
第十一步:從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回調,ApplicationRunner先回調,CommandLineRunner再回調。
第十二步:所有的SpringApplicationRunListener回調running方法。
第十三步:整個SpringBoot應用啟動完成以後返回啟動的ioc容器。
這就是run的全部過程,想要更詳細的瞭解還需自己去看源碼。
三、
自定義starter(場景啟動器),我們要做的事情是兩個:確定依賴和編寫自動配置。我們重點要做的就是編寫自動配置,我們之前寫過一些自動配置,主要是註解配置的使用,主要的註解有:
-
@Configuration :指定這個類是一個配置類
-
@ConditionalOnXXX :在指定條件成立的情況下自動配置類生效
-
@AutoConfigureAfter:指定自動配置類的順序
-
@Bean:給容器中添加組件
-
@ConfigurationPropertie:結合相關xxxProperties類來綁定相關的配置
-
@EnableConfigurationProperties:讓xxxProperties生效加入到容器中
按照這些註解寫好自動配置類後,我們還需要進行自動配置的載入,載入方式是將需要啟動就載入的自動配置類,配置在META-INF/spring.factories,啟動器的大致原理是如此,而啟動器的實際設計是有一定模式的,就是啟動器模塊是一個空 JAR 文件,僅提供輔助性依賴管理,而自動配置模塊應該再重新設計一個,然後啟動器再去引用這個自動配置模塊。Springboot就是如此設計的:
另外還有一個命名規則:
官方命名空間
– 首碼:“spring-boot-starter-”
– 模式:spring-boot-starter-模塊名
– 舉例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc
自定義命名空間
– 尾碼:“-spring-boot-starter”
– 模式:模塊-spring-boot-starter
– 舉例:mybatis-spring-boot-starter
具體的創建過程就不贅述了,就是最簡單的項目,去掉不需要的文件,創建完成結構如下:
第三步:我們先將自動配置模塊導入starter中,讓啟動模塊依賴自動配置模塊
啟動模塊的POM文件加入依賴
<dependencies> <!--引入自動配置模塊--> <dependency> <groupId>com.yuanqinnan-starter</groupId> <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
自動配置模塊的完整POM文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.yuanqinnan-starter</groupId> <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--引入spring-boot-starter;所有starter的基本配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> </project>
至此,兩個項目基本創建完成,現在我們實現簡單的配置。
第五步:對自動配置類進行自動配置代碼編寫
先編寫一個配置類,用於配置:
@ConfigurationProperties(prefix = "yuanqinnan.hello") public class HelloProperties { //首碼 private String prefix; //尾碼 private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } }
再編寫一個服務
public class HelloService { HelloProperties helloProperties; public HelloProperties getHelloProperties() { return helloProperties; } public void setHelloProperties(HelloProperties helloProperties) { this.helloProperties = helloProperties; } public String sayHello(String name) { return helloProperties.getPrefix() + "-" + name + helloProperties.getSuffix(); } }
然後再將這個服務註入組件:
@Configuration @ConditionalOnWebApplication //web應用才生效 @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { @Autowired HelloProperties helloProperties; @Bean public HelloService helloService(){ HelloService service = new HelloService(); service.setHelloProperties(helloProperties); return service; } }
這個時候我們的自動配置以及寫完,還差最後一步,因為SpringBoot讀取自動配置是在META-INF的spring.factories文件中,所以我們還要將我們的自動配置類寫入其中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yuanqinnan.starter.HelloServiceAutoConfiguration
最後的結構如下:
至此,代碼以及編寫完成,這個時候我們將其裝入倉庫中,讓其他項目引用
3.2 使用自定義starter
創建一個web項目,然後在項目中引入依賴
<!--引入自定義starter--> <dependency> <groupId>com.yuanqinnan.starter</groupId> <artifactId>yuanqinnan-springboot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
在application.properties 配置中加上配置:
yuanqinnan.hello.prefix=早安
yuanqinnan.hello.suffix=晚安
加入測試:
@Autowired HelloService helloService; @Test public void contextLoads() { System.out.println(helloService.sayHello("世界")); }
這樣自定義Starter和引用自定義都已完成,Springboot的核心知識已經總結完成,後面再進行Springboot的一些高級場景整合,如緩存、消息、檢索、分散式等。