如果還沒有看第一篇文章的伙伴,建議先看第一篇文章 手寫自己的Springboot-1-整合tomcat,該文章在第一篇文章基礎(chǔ)上進行擴展.
在Springboot工程中怎樣對Servlet容器進行切換
在我們使用Springboot時,如果不想用Tomcat,想用Jetty,應(yīng)該怎么辦呢?
其實很簡單,我們只需要把Tomcat的依賴排除掉,然后引入Jetty即可.
那Springboot底層究竟是怎樣處理的呢?
底層原理猜想,需要做哪些事情
首先要明確,Springboot的自動配置其實就是幫我們自動配置注入了很多bean, 比如原來的Spring需要整合Tomcat,一定需要配置一些Tomcat的bean, 需要整合Jetty,就一定需要配置一些Jetty相關(guān)的bean.
1.pom依賴
Springboot能夠啟動Jetty,能夠啟動tomcat,那它在自己內(nèi)部工程一定是同時引入這兩個依賴的.
那為什么使用者在切換的時候還需要額外引入Jetty的依賴呢?
其實這里利用了依賴的傳遞性,在Springboot內(nèi)部工程pom中,其實對于可選擇的依賴加上了<optional>true</optional>
,加上該配置后,表示該依賴并不會傳遞,而tomcat依賴是沒有加該配置的,會進行依賴的傳遞.
如果不理解這部分的maven的知識,可以看一下這個文章Maven常見知識、沖突解決.
所以,使用者默認是不需要額外引入Tomcat依賴的,而進行Jetty的切換時,需要額外引入Jetty的依賴.
那為什么要這么做呢?為什么不也直接讓使用者也都引入這些依賴呢?
對于使用者而言,比如說Servlet容器,其實一般最多都只用一個的,大部分都是Tomcat,那我就不需要引入其他的多余的依賴,那樣會使項目變得更加龐大, 還會極大增加依賴沖突的可能性.
2.判斷注入哪個bean
知道了怎么進行切換,那現(xiàn)在就應(yīng)該進行自動配置bean, 那怎么知道應(yīng)該注入哪個bean呢?是Tomcat的,還是Jetty的?
其實這里也非常簡單,就是通過判斷能不能加載到某一個類進行處理的.
比如說使用Tomcat,那我是一定有這個依賴,能夠加載到這個對象的.然后才把這個對象注入到Spring容器中.
也就是說配置這個bean是需要滿足一定的條件的.
具體代碼如下:
以下代碼就表示當加載到對應(yīng)的Tomcat,或 Jetty對象時, 才將對應(yīng)的bean注入到Spring容器中.
@BlingConditionOnClass注解是模仿Springboot中的@ConditionOnClass注解自己定義的,他倆起到的效果是一樣的.都是加載到某一個類才生效.
@BlingConditionOnClass 這個注解其實底層最關(guān)鍵的其實是運用了Spring的原生注解@Conditional
Conditional注解需要傳入一個實現(xiàn)Condition接口的實現(xiàn)類, 實現(xiàn)matches方法.
筆者這里是傳入了一個OnClassCondition類.在這個matches方法中就是做了一件事情,
就是判斷注解傳入的類路徑能不能被正常加載.
public class OnClassCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//獲取BlingConditionOnClass注解中的所有屬性
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(BlingConditionOnClass.class.getName());
//獲取其中的value屬性對應(yīng)的值
List<Object> classPathList = attributes.get("value");
if(classPathList == null || classPathList.isEmpty()){
return false;
}
try {
//加載傳入的類路徑
Objects.requireNonNull(context.getClassLoader()).loadClass((String) classPathList.get(0));
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
總體思路是這樣的:
- 當Spring掃描加載到WebServerAutoConfigration這個類的時候.會解析類上的注解.該類上標識了@Configuration注解.Spring就會解析調(diào)用這個類中標識了@Bean的方法.
- 當解析到tomcatWebServer()方法時,則通過反射可以解析到該方法上的注解@BlingConditionOnClass(“org.apache.catalina.startup.Tomcat”),并記錄傳入注解的值org.apache.catalina.startup.Tomcat
- 然后會解析到@BlingConditionOnClass注解中的注解@Conditional(OnClassCondition.class)
- 接下來會調(diào)用Condition接口實現(xiàn)類OnClassCondition的matches方法,根據(jù)方法該方法的返回值判斷是否將該對象注入到Spring容器中.
根據(jù)以上流程就可以達到引入不同maven依賴而自動注入不同bean的效果.
3.讓使用者能夠加載到自動配置類
目前問題及通用處理方式
現(xiàn)在看起來好像是已經(jīng)能達到我們剛開始想要的效果了, 通過引入不同的maven依賴向Spring容器注入不同的bean,但是還有一個很關(guān)鍵的點.
就是當使用者引入該項目,他怎么能夠掃描加載到項目中WebServerAutoConfigration這個自動配置類呢?
我們知道Spring會默認掃描傳入啟動類所在的包路徑.比如說下面這個工程,傳入的是TestApplication, 這個類所在的包路徑是com.bling.test,那么Spring就會掃描加載這個路徑下所有的類.
而我們自己的寫的Springboot項目需要加載的自動配置類,在com.bling.springboot路徑下.默認當然是不會掃描加載到的.
此時,最常見的有兩種方法,可以從使用者的角度去加載的這個自動配置類 :
- 使用Import注解.直接導(dǎo)入
- 指定掃描路徑,從而去掃描加載對應(yīng)的類.
那Springboot真實是怎樣做的呢? 以上兩種方法都沒有采用, 因為在我們的工程中可能會導(dǎo)入很多的依賴, 有很多很多的自動配置類,也有很多很多不同的項目路徑, 難道我們要把這些配置類和路徑全都寫一遍嗎? 這是非常不方便的.
真實Springboot處理方式
真實Spring在進行處理的時候會在@SpringBootApplication注解上Import一個類,AutoConfigurationImportSelector, 見名知義,也就是自動配置導(dǎo)入選擇器. 這里也模仿Springboot這樣處理.
這個類實現(xiàn)了ImportSelector接口,重寫了selectImports方法,這個方法要返回什么呢?
其實就是返回需要被Spring掃描管理的類的全類名.返回的類將會被Spring容器加載管理.
那在這個方法中又應(yīng)該怎樣獲取到所有自動配置類的全類名呢?
這里Spring利用的是SPI機制,SPI (Service Provider Interface)本身是一個概念,有很多的框架會去實現(xiàn)自己的SPI.比如Spring,它自己實現(xiàn)加載的spring.factories, 就是SPI機制, 當然JDK本身也有實現(xiàn)的一套SPI的機制.
我們這里就不去實現(xiàn)Spring的這一套SPI,比較麻煩,我們這里直接使用JDK的SPI進行演示處理.核心理解為什么要在這里用到SPI就行.
使用JDK SPI機制步驟:
首先明確JDK SPI是針對接口進行處理的,就是當你傳入一個接口, 它可以獲取這個接口對應(yīng)的所有配置的實現(xiàn)類.
-
所以先定義一個AutoConfigration接口.讓WebServerAutoConfigration去實現(xiàn)它.
-
在resources目錄下新建文件夾META-INF,在該文件夾下再新建文件夾services.在services下新建一個文件,文件名就是接口全類名.
在該文件中配置好對應(yīng)的實現(xiàn)類. -
使用JDK中的ServiceLoader類進行加載.
/**
* @ClassName:
* @Description: 該類標識在啟動類中,使用Import注解導(dǎo)入,所以Spring會對該類進行加載
* @author:
* @date:
*
*/
public class AutoConfigurationImportSelector implements ImportSelector {
/**
* @param importingClassMetadata
* @return 返回需要加載到Spring容器的全類名
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//這里暫時不引入Spring中的SPI,比較復(fù)雜
//直接使用Java中的SPI
ArrayList<String> result = new ArrayList<>();
ServiceLoader<AutoConfigration> load = ServiceLoader.load(AutoConfigration.class);
for (AutoConfigration autoConfigration : load) {
result.add(autoConfigration.getClass().getName());
}
return result.toArray(new String[0]);
}
}
至此,就完完全全的能夠達到我們在最開始提到的想法,想切換不同的Servlet容器,直接切換maven依賴就可以.
進行測試
想看該項目完整代碼的可以到Github上下載:手寫自己的springboot
如果有網(wǎng)絡(luò)不通的小伙伴也可以到CSDN中下載CSDN地址下載.
我們還是將該項目進行打包, 額外新建一個非常簡單的maven項目,引入該項目依賴,如下:
可以正常啟動tomcat…
然后,排除Tomcat依賴,加入Jetty依賴.
現(xiàn)在就可以啟動Jetty.
關(guān)鍵點總結(jié)
整個流程下來,其實主要有三點:文章來源:http://www.zghlxwxcb.cn/news/detail-452076.html
- springboot本身是引入所有組件依賴的,只不過利用maven依賴的傳遞性, 讓使用者不需要引入額外的依賴
- 關(guān)于自動配置bean,其實大量運用了條件注解, 通過一定的條件判斷該bean是否需要注入到Spring容器
- 為了讓使用者能夠較為方便的加載到自動配置類, 使用了SPI技術(shù)進行處理.
今天的分享就到這里了,有問題可以在評論區(qū)留言,均會及時回復(fù)呀.
我是bling,未來不會太差,只要我們不要太懶就行, 咱們下期見.文章來源地址http://www.zghlxwxcb.cn/news/detail-452076.html
到了這里,關(guān)于手寫自己的Springboot-2-從Servlet容器選擇徹底理解自動配置的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!