SpringBoot的一大優點就是Starter,因爲SpringBoot有不少開箱即用的Starter依賴,使得咱們開發變得簡單,咱們不須要過多的關注框架的配置。java
在平常開發中,咱們也會自定義一些Starter,特別是如今微服務框架,咱們一個項目分紅了多個單體項目,而這些單體項目中會引用公司的一些組件,這個時候咱們定義Starter,可使這些單體項目快速搭起,咱們只須要關注業務開發。程序員
在此以前咱們再深刻的瞭解下SpringBoot啓動原理。然後再將如何自定義starter。web
要想了解啓動原理,咱們能夠Debug模式跟着代碼一步步探究,咱們從入口方法開始:面試
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
複製代碼
這裏是建立一個SpringApplication對象,並調用了run方法spring
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();
}
複製代碼
從這個方法中能夠看出,這個sql
第一步:保存主配置類。apache
第二步:肯定web應用類型。緩存
第三步:setInitializers方法,這個方法走咱們看帶入的參數是getSpringFactoriesInstances(ApplicationContextInitializer.class),咱們再往下查看getSpringFactoriesInstancesspringboot
再進入這個方法:bash
這裏就是從類路徑下找到META-INF/spring.factories配置的全部ApplicationContextInitializer,而後再保存起來,放開斷點,咱們能夠看到這個時候獲取到的
第四步:從類路徑下找到ETA-INF/spring.factories配置的全部ApplicationListener,原理也基本相似,進入斷點
第五步:從多個配置類中找到有main方法的主配置類。這個執行完以後,SpringApplication就建立完成
先貼出代碼
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(場景啓動器),咱們要作的事情是兩個:肯定依賴和編寫自動配置。咱們重點要作的就是編寫自動配置,咱們以前寫過一些自動配置,主要是註解配置的使用,主要的註解有:
按照這些註解寫好自動配置類後,咱們還須要進行自動配置的加載,加載方式是將須要啓動就加載的自動配置類,配置在META-INF/spring.factories,啓動器的大體原理是如此,而啓動器的實際設計是有必定模式的,就是啓動器模塊是一個空 JAR 文件,僅提供輔助性依賴管理,而自動配置模塊應該再從新設計一個,而後啓動器再去引用這個自動配置模塊。Springboot就是如此設計的:
另外還有一個命名規則:
官方命名空間
自定義命名空間
第一步:由於咱們須要建立兩個模塊,因此先新建一個空的項目,而後以模塊形式建立兩個模塊。
第二步:再建立兩個模塊,一個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
複製代碼
最後的結構以下:
至此,代碼以及編寫完成,這個時候咱們將其裝入倉庫中,讓其餘項目引用
建立一個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的一些高級場景整合,如緩存、消息、檢索、分佈式等。
分享免費學習資料
針對於Java程序員,我這邊準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!
資料領取方式:加入Java技術交流羣963944895
,點擊加入羣聊,私信管理員便可免費領取