一羣熱愛技術並且嚮往優秀的程序猿同學,不喜歡水文,不喜歡販賣焦慮,只喜歡談技術,分享的都是技術乾貨。Talk is cheap. Show me the code
Spring Boot 20天入門(day7)
Springboot啓動配置原理
啓動類註解@SpringbootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
前面4個都是java的元註解,沒啥好看的,重要的是後面幾個:
1)、@SpringBootConfiguration
2)、@EnableAutoConfiguration
3)、@ComponentScan
@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}
這個註解其實沒啥特別的,個人認爲就是用來做語義化的註解,裏面還是用的@Configuration,標註這是一個配置類
@EnableAutoConfiguration
開啓Springboot的自動配置,這個在以前的文章講過:
https://blog.csdn.net/WXZCYQ/article/details/106167302
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
容器組件掃描的註解。用於掃描所有的容器組件,是最核心的註解之一。
SpringbootApplication.run()
我們都知道,Springboot的啓動類(標註了@SpringBootApplication註解和擁有main()),會執行一個叫
SpringApplication.run()
的方法,來啓動Springboot應用。
讓我們來debug,跟一下Springboot到底是如何啓動應用的。
run()
的截圖:
該方法會傳遞一個啓動類的class對象,以及啓動類參數,然後執行另外一個run()
點擊step into
,將會看到新建一個SpringApplication
, 並將配置類傳遞進去:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
繼續step into
,查看SpringbootApplication的構造方法
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 將啓動類保存起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 獲得當前應用的類型 ,WebApplicationType 是個枚舉對象,NONE,SERVLET(servlet類型),REACTIVE(響應式)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 從類路徑下的/META-INF/spring.factories中獲取所有的ApplicationContextInitializer並保存起來
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 從類路徑下的/META-INF/spring.factories中獲取所有的ApplicationListener並保存起來
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 尋找擁有main方法的啓動類
this.mainApplicationClass = deduceMainApplicationClass();
}
如何判斷是啓動類的呢?deduceMainApplicationClass()
是這樣判斷的:
// 獲取所有的方法名,匹配是不是main方法
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
創建完SpringApplication
後,來到我們的重頭戲,SpringApplication.run()
:
public ConfigurableApplicationContext run(String... args) {
// 用於監控啓動時間的計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 支持報告關於啓動的錯誤對象的集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// java.awt.headless是J2SE的一種模式用於在缺少顯示屏、鍵盤或者鼠標時的系統配置,很多監控工具如jconsole 需要將該值設置爲true,系統變量默認爲true
configureHeadlessProperty();
//獲取並啓動監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 獲取命令行參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 配置容器環境(spring.profile.active)
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置不需要初始化的bean對象,可以在配置文件中設置,前綴是spring.beaninfo.ignore
configureIgnoreBeanInfo(environment);
// 打印Banner,也就是我們看到的那個大大的Spring
Banner printedBanner = printBanner(environment);
// 創建容器的上下文對象(ApplicationContext)
context = createApplicationContext();
// 註冊支持報告關於啓動錯誤的對象
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 將上文代碼中註冊的配置注入進上下文對象中
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器,清除之前的緩存,從配置文件中導入環境,組件,如果是Web應用,還會啓動web容器(Tomcat)
refreshContext(context);
// 這是個空方法,個人認爲應該是留着擴展,因爲傳遞的是命令行參數
afterRefresh(context, applicationArguments);
// 容器啓動完畢,關閉計時器,並在控制檯打印 : Root WebApplicationContext: initialization completed in 27695 ms
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//回調所有 SpringApplicationRunListener實現類 的starting方法
listeners.started(context);
// 從容器中獲取所有ApplicationRunner,CommandLineRunner,並調用他們的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
prepareContext:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 給容器設置環境(spring.profile.active)
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 給容器註冊一些組件
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
refreshContext:
調用另一個refresh
方法,將上下文對象傳遞進去
private void refreshContext(ConfigurableApplicationContext context) {
refresh((ApplicationContext) context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
調用容器的refresh
方法
// 該方法是一個接口方法,最終會調用一個叫AbstractApplicationContext的refresh方法
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
AbstractApplicationContext的refresh
方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 從容器中獲取當前應用環境,初始化Web容器環境
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 爲容器註冊一些依賴
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 爲servletContext上下文對象註冊web應用的域對象和環境
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 最終會調用AbstractRefreshableWebApplicationContext的onRefresh方法
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
自定義starter
starter:
1、場景需要使用的依賴是什麼?
2、如何編寫自動配置
@Configuration // 指定是一個配置類
@ConditionOnxxx // 載指定的條件成立的情況下配置生效
@AutoConfigureAfter // 指定自動配置類的順序
@Bean //給容器中添加組件
@ConfigurationProperties //結合線管的xxxproperties類來綁定
@EnableConfigurationProperties // 讓xxxProperties生效加入到容器中
自動配置類要能加載
將需要啓動就加載的自動配置類,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
3、模式:
啓動器只用來做依賴導入:
例如spring-boot-starter-web:
啓動器依賴自動配置,打包成xxx-starter,使用時引入就行
編寫自定義starter
創建兩個模塊,一個只引入必要的自定義依賴,一個用來編寫自定義的核心代碼
weleness-spring-boot-starter-autoconfigurer的pom文件:
因爲這個模塊是寫核心代碼的,所以需要引入springboot的自動配置包
<?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 https://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.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.github.anhTom2000</groupId>
<artifactId>weleness-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>weleness-spring-boot-starter-autoconfigurer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
weleness-starter的pom文件:
這個模塊是被別人當成自定義starter引入的,不需要寫任何代碼,只需要將編寫核心功能代碼的依賴引入進來
<?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>
<groupId>com.githun.starter</groupId>
<artifactId>weleness</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 引入自動配置模塊-->
<dependencies>
<dependency>
<groupId>com.github.anhTom2000</groupId>
<artifactId>weleness-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
編寫配置文件類
編寫一個測試用的配置文件類,在配置文件中以weleness.hello
進行配置
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "weleness.hello")
public class HelloProperties {
private String prefix;
private String sufffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSufffix() {
return sufffix;
}
public void setSufffix(String sufffix) {
this.sufffix = sufffix;
}
}
編寫測試用服務
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.getSufffix();
}
}
編寫自動配置類
瞭解springboot自動配置原理的同學就不必我多說了,懂的人自然懂。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description : TODO
* @Author : Weleness
* @Date : 2020/05/22
*/
@Configuration
@ConditionalOnWebApplication// web應用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired
HelloProperties helloProperties;
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setHelloProperties(helloProperties);
return helloService;
}
}
在類路徑下編寫Springboot的自動配置文件
這樣我們自定義的starter就創建好了,現在將他們install到maven倉庫中:
測試自定義starter
新建一個工程,引入剛剛install完畢的依賴
<?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 https://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.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.github</groupId>
<artifactId>weleness-test-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>weleness-test-starter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入自定義starter-->
<dependency>
<groupId>com.githun.starter</groupId>
<artifactId>weleness</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
編寫controller測試
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@RequestMapping("/hello")
public String hello(){
return helloService.sayHello("weleness");
}
}
編寫配置文件
server.port=8889
weleness.hello.prefix=nihao
weleness.hello.sufffix=woshi
以上…