網站首頁 編程語言 正文
寫在前面:
各位讀友們好,最近已經很久沒有更新文章了,并不是覺得寫文章沒意思之類的,筆者很希望能在"亂七八糟"的互聯上做一些開源(能力有限,先做現有技術和思想開源。除了靠編程賺錢以外,這可能是支撐我一直學習的動力,希望能學到更多的內容開源出去)。之所以沒有持續更新的原因——真的沒時間。白天上班,晚上回去學習(為了以后給各位讀者寫更有深度的文章)。至于每天在學習什么內容,這個以后會無條件分享給大家(大概是偏底層方面的,各種中間件的源碼和Linux內核源碼等等)。
好了,多的不提了,回歸正題,今天也是在公司接手了一個老員工的項目。由于公司都是使用war包的形式,運行在公司的服務器的tomcat中。并不是現在流行的jar包內嵌tomcat的形式,兩者恰恰相反。就看到項目中以下的代碼。
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(TelecomApplication.class);
}
}
正文:
比較好奇的我,接手這個項目并不是直接看業務層面的內容,而是在思考出幾個問題:
- 打成war包是如何運行的?
- 項目是spring boot的項目,那么在tomcat中如何運行spring boot的啟動邏輯呢?
抱著問題,筆者第一時間的思考是:我們項目打包成一個war包丟入tomcat中,運行和生命周期都是依賴于tomcat,而我們的項目又是一個spring boot的項目,就必須使用spring boot啟動邏輯來初始化項目(run方法)。而對于一個jar包項目都是運行我們項目中寫的啟動類中的main方法邏輯(也就是運行SpringApplication.run())。而tomcat自身啟動肯定也是main方法,而你自身的項目也是一個main方法,那肯定行不通,所以筆者大膽猜測是存在一些接口來設置當前項目的啟動邏輯(不走main方法),還有一些接口來做特定時期回調啟動當前項目。
說了這么多,好像還沒開始介紹我們的SpringBootServletInitializer類,那么了解一個類的入口在哪里,沒錯就是從注釋入手。
很明顯的意思就是說打war包的時候才需要這個類。并且上面的注釋還說,最后實現當前類,重寫configure方法,并且調用SpringApplicationBuilder.sources方法,將@Configuration類傳入(Spring boot啟動注解,也就是一個@Configuration)。所以就出現了本文章開頭的那段代碼邏輯
何時回調?我們暫時并不清楚,所以就先看SpringBootServletInitializer類中onStartup方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY, false);
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
}
else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
+ "return an application context");
}
}
?所有邏輯都在createRootApplicationContext()方法中,繼續追進去。
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
// 創建SpringApplicationBuilder來整合一些配置項,然后生成SpringApplication類。
SpringApplicationBuilder builder = createSpringApplicationBuilder();類
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
// configure方法就是我們重寫的方法,把我們當前項目的啟動類傳入
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
// 熟悉的SpringApplication,項目中啟動類main方法中也是用這個類調用run方法啟動項目
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty()
&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
application.setRegisterShutdownHook(false);
// 內部邏輯調用SpringApplication.run方法啟動項目。
return run(application);
}
對以上代碼做一個總結:
- 何時回調onStartup暫不清楚,后面會講(其實底子好點的讀者能思考到肯定是在tomcat中)
- 創建SpringApplicationBuilder 來整合配置,準備生成SpringApplication
- 整合配置的整體邏輯不做以詳細說明,不過能看到我們的configure,因為此文章開頭的代碼就是重寫了這個方法。將我們當前項目的啟動類通過SpringApplicationBuilder類中的sources放入(其實并不會走啟動類的main方法了,只是需要啟動類的元數據信息,比如啟動注解)。
- 然后調用run方法,內部的邏輯也就是調用SpringApplication.run(),這就是Spring boot啟動的具體邏輯了。
以上代碼是告訴讀者Spring boot項目的另一種啟動方式,所以接下來我們要找到onStartup方法的回調時機就能完美閉環。
而war包是運行在tomcat中,所以回調時機肯定是在tomcat源碼中的某一個位置。這里不明白tomcat源碼的讀者也無關緊要,不過建議大家有時間去學習tomcat的源碼。
我們看到tomcat源碼中ServletContainerInitializer接口(這是servlet的接口)。確切的說,Spring boot是通過ServletContainerInitializer接口來完成的回調。
然后看到StandardContext中啟動的生命周期startInternal回調函數中一部分代碼邏輯。
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
這里遍歷所有的ServletContainerInitializer接口,然后回調onStartup方法(這里是一個嵌套回調,這個onStartup并不是上面介紹的),而Spring通過SpringServletContainerInitializer實現了ServletContainerInitializer接口,重寫了onStartup。然后一個ServletContainerInitializer接口又對應一個set集合(存放的是WebApplicationInitializer,也就是SpringBootServletInitializer的父類,也就是我們項目啟動的回調類)。
所以我們回到Spring boot中先找到ServletContainerInitializer子類SpringServletContainerInitializer查看回調的具體邏輯。
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
對以上代碼做一個總結:
- 這里是一個嵌套回調,tomcat回調SpringServletContainerInitializer中的onStartup方法,然后onStartup方法里面又回調SpringBootServletInitializer的onStartup(注意這里的類命名有點類似,并且都是onStartup方法)
- 獲取到從tomcat中SpringServletContainerInitializer對應所有到的WebApplicationInitializer
- 做一些過濾處理
- for循環做WebApplicationInitializer的回調機制,也就是回調onStartup()方法,也就是會回調他的子類SpringBootServletInitializer的onStartup()方法,也就是回調上面描述的Spring boot啟動邏輯。?
總結:
并不復雜,首先先合理分析jar和war包的區別,就能很快的定位會在哪里處理回調。
比較困難的就是定位tomcat的源碼,這必須要明白他的架構(就是一個遞歸架構)。其實對于這個源碼分析,就算讀者不懂tomcat源碼也不會很影響讀者來理解,能明白tomcat會回調接口來初始化用戶的Spring boot的項目就足夠了。只不過懂tomcat源碼能夠完美閉環。
最后,如果本帖對您有一定的幫助,希望能點贊+關注+收藏!您的支持是給我最大的動力,后續會一直更新各種框架的使用和框架的源碼解讀~!
原文鏈接:https://blog.csdn.net/qq_43799161/article/details/125315579
相關推薦
- 2022-06-17 一文輕松了解ASP.NET與ASP.NET?Core多環境配置對比_實用技巧
- 2024-03-14 AOP切面編程,以及自定義注解實現切面
- 2022-03-22 .NET?6開發TodoList開發查詢分頁_實用技巧
- 2022-12-29 Kotlin數據存儲方式全面總結講解_Android
- 2022-06-01 Android實現極簡打開攝像頭_Android
- 2022-11-05 解決使用pip安裝報錯:Microsoft?Visual?C++?14.0?is?required.
- 2022-09-03 Matplotlib中文亂碼的兩種詳細解決方案_python
- 2022-09-21 ubuntu22通過docker安裝wechat啟動后無界面的問題及解決方法_docker
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支