您好,登錄后才能下訂單哦!
微信號:GitShare
微信公眾號:愛折騰的稻草
如有問題或建議,請在公眾號留言[1]
為幫助廣大SpringBoot用戶達到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內(nèi)部工作原理。
1、[SpringBoot]利用SpringBoot快速構(gòu)建并啟動項目
2、[SpringBoot]詳解SpringBoot應用的啟動過程
3、[SpringBoot]深入淺出剖析SpringBoot的應用類型識別機制
Java SPI是什么?
SPI的全名為Service Provider Interface.大多數(shù)開發(fā)人員可能不熟悉,因為這個是針對廠商或者插件的。
我們系統(tǒng)里抽象的各個模塊,往往有很多不同的實現(xiàn)方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。
面向的對象的設計里,我們一般推薦模塊之間基于接口編程,模塊之間不對實現(xiàn)類進行硬編碼。一旦代碼里涉及具體的實現(xiàn)類,就違反了可拔插的原則,
如果需要替換一種實現(xiàn),就需要修改代碼。為了實現(xiàn)在模塊裝配的時候能不在程序里動態(tài)指明,這就需要一種服務發(fā)現(xiàn)機制。
Java SPI就是提供這樣的一個機制:為某個接口尋×××實現(xiàn)的機制。
Java SPI的約定
當服務的提供者,提供了服務接口的一種實現(xiàn)之后,在jar包的META-INF/services/目錄里同時創(chuàng)建一個以服務接口命名的文件。
該文件里就是實現(xiàn)該服務接口的具體實現(xiàn)類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/里的配置文件找到具體的實現(xiàn)類名,并裝載實例化,完成模塊的注入。
基于這樣一個約定就能很好的找到服務接口的實現(xiàn)類,而不需要再代碼里制定。
jdk提供服務實現(xiàn)查找的一個工具類:java.util.ServiceLoader 。
在Spring中也有一種類似與Java SPI的加載機制。它在META-INF/spring.factories文件中配置接口的實現(xiàn)類名稱,然后在程序中讀取這些配置文件并實例化。
這種自定義的SPI機制是Spring Boot Starter實現(xiàn)的基礎。
spring-core包里定義了SpringFactoriesLoader類,這個類實現(xiàn)了檢索META-INF/spring.factories文件,并獲取指定接口的配置的功能。
在這個類中定義了兩個對外的方法:
根據(jù)接口類獲取其實現(xiàn)類的實例,這個方法返回的是對象列表,其源代碼:
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
先獲取ClassLoader
調(diào)用loadFacotoryNames()方法,獲取factoryNames,其源碼如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//獲取到類名
String factoryClassName = factoryClass.getName();
//在當前ClassLoader下的所有META-INF/spring.factories文件的配置信息的Map中獲取指定的factory的值,如果不存在,則返回空列表
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
調(diào)用loadSpringFactories方法,獲取當前ClassLoader下的所有META-INF/spring.factories文件的配置信息
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//去緩存中獲取對應的
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null)
return result;
try {
//獲取當前ClassLoader下的所有包含META-INF/spring.factories文件的URL路徑
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
//初始返回對象
result = new LinkedMultiValueMap<>();
//遍歷所有的包含META-INF/spring.factories文件URL集合
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//轉(zhuǎn)換為Properties對象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//遍歷META-INF/spring.factories文件中的所有屬性
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//如果一個接口希望配置多個實現(xiàn)類,可以使用','進行分割,將當前Key對應的值轉(zhuǎn)換為List
List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
//添加到返回對象中
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
//添加到緩存中
cache.put(classLoader, result);
//返回結(jié)果
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
我們可以在自己的jar中配置spring.factories文件,不會影響到其它地方的配置,也不會被別人的配置覆蓋。
這一點非常有利于后期我們進行自定義擴展。
調(diào)用instantiateFactory,實例化獲取到的對應的Factory對象,其源碼:
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
//根據(jù)類名獲取類的實例,這個源碼在上一篇中已經(jīng)剖析過了。
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
//isAssignableFrom()是判斷instanceClassName的對象是否是FactoryClass的子類或者相同的類,如果是返回true。
if (!factoryClass.isAssignableFrom(instanceClass)) {
//如果實例化的目標類不是Factory類的子類或者不是Factory類,則拋出異常
throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
//通過java反射機制,實例化目標類
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
從這段代碼中我們可以知道,它只支持沒有參數(shù)的構(gòu)造方法。
根據(jù)接口獲取其接口類的名稱,這個方法返回的是類名的列表,其源代碼已經(jīng)在上面分析過了。
在SpringBoot啟動過程中,創(chuàng)建SpringApplication對象是,有如下兩行代碼:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationContextInitializer
使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationListener
在日常工作中,我們可能需要實現(xiàn)一些SDK或者Spring Boot Starter給被人使用,這個使用我們就可以使用Factories機制。
Factories機制可以讓SDK或者Starter的使用只需要很少或者不需要進行配置,只需要在服務中引入我們的jar包。
Class的isAssignableFrom()解析
public native boolean isAssignableFrom(Class<?> cls);
由方法簽名可見是一個本地方法,即C代碼編寫的。
其作用:
有兩個Class類型的類象,一個是調(diào)用isAssignableFrom方法的類對象(后稱對象a),以及方法中作為參數(shù)的這個類對象(稱之為對象b),這兩個對象如果滿足以下條件則返回true,否則返回false:
a對象所對應類信息是b對象所對應的類信息的父類或者是父接口,簡單理解即a是b的父類或接口
a對象所對應類信息與b對象所對應的類信息相同,簡單理解即a和b為同一個類或同一個接口
為幫助廣大SpringBoot用戶達到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內(nèi)部工作原理。 敬請關注[愛折騰的稻草(GitShare)]公眾號,為您提供更多更優(yōu)質(zhì)的技術(shù)教程。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。