在本版本中引入了SPI機制,關於Java的SPI機制與Dubbo的SPI機制在以前的文章中介紹過。 傳送門: "Dubbo的SPI機制與JDK機制的不同及原理分析" 因為設計的RPC框架是基於Spring的,時常會遇到依賴註入問題。Spring中也有SPI機制,但是它有有個缺點,就是在利用SPI機制 ...
在本版本中引入了SPI機制,關於Java的SPI機制與Dubbo的SPI機制在以前的文章中介紹過。
傳送門:Dubbo的SPI機制與JDK機制的不同及原理分析
因為設計的RPC框架是基於Spring的,時常會遇到依賴註入問題。Spring中也有SPI機制,但是它有有個缺點,就是在利用SPI機制實例化具體的服務類時,如果具體的服務類中調用其他的bean,就會實例化失敗。主要因為該具體的服務類並沒有放入到Spring容器中。本項目將有效解決這個問題。
在設計的RPC框架中加入了該機制,來實現不同序列化方式的切換。
Spring的SPI機制
我們知道在SprngBoot中好多的配置和實現都有預設的實現,我們只需要修改部分配置,比如資料庫配置,我們只要在配置文件中寫上對應的url,username,password就可以使用了。其實他這邊用的就是SPI的方式實現的。Spring的SPI機制原理與Java的SPI原理是一致的。
SpringBoot會利用SpringFactoriesLoader
載入META-INF/spring.factories文件,從CLASSPATH下的每個Jar包中搜尋所有META-INF/spring.factories配置文件,然後將解析properties文件,找到指定名稱的配置後返回。需要註意的是,其實這裡不僅僅是會去ClassPath路徑下查找,會掃描所有路徑下的Jar包,只不過這個文件只會在Classpath下的jar包中。
調用方式:
List<AService> services = SpringFactoriesLoader.loadFactories(AService.class, null);
for (AService service : services) {
service.info();
}
相關源碼:
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;
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式為:key=value1,value2,value3
// 從所有的jar包中找到META-INF/spring.factories文件
// 然後從文件中解析出key=factoryClass類名稱的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得資源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍歷所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根據資源文件URL解析properties文件,得到對應的一組@Configuration類
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 組裝數據,並返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
改進的SPI機制
該機制有兩個緩存變數:
private static final ConcurrentHashMap<Class<?>, Map<String, Class<?>>> cacheClasses= new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Class<?>, Map<String, Object>> cacheIntances = new ConcurrentHashMap<>();
兩個Map的key都是擴展服務的介面類的Class對象
cacheClasses
的value也是一個Map,這個map的key是定義的擴展名,即META-INF/roadspi/
目錄下文件中的key,value是具體的擴展類的Class對象。
cacheIntances
變數的value也是一個Map,該map的key是定義的擴展名,value是擴展類的具體實例化對象。
該機制的主要邏輯是先獲取要實現擴展的介面類Class對象,然後從cacheIntances
變數中根據key查找是否有緩存的實例,如果有直接返回。如果沒有,然後根據介面類Class對象和key在cacheClasses
變數中進行查找具體擴展類的Class對象,如果存在,就直接獲取對用的Class對象,然後利用BeanDefinitionBuilder
生成bean,並註冊到Spring容器中;如果找不到對應的Class對象,則到META-INF/roadspi/擴展介面類全稱
文件下進行資源載入。
支持自定義的RoadSpi
註解,來定義預設的具體服務類實現。
最主要部分實現
private void createService(Map<String, Object> extensionInstanceMap, Map<String, Class<?>> serviceClass, String serviceName, Class<?> type) {
Class<?> obj = serviceClass.get(serviceName);
if (obj == null) {
log.error("serviceClass is null!");
}
String beanName = obj.getSimpleName().concat(serviceName);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(obj);
GenericBeanDefinition definition = (GenericBeanDefinition)builder.getRawBeanDefinition();
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext)context;
DefaultListableBeanFactory register = (DefaultListableBeanFactory)configurableApplicationContext.getBeanFactory();
register.registerBeanDefinition(beanName, definition);
extensionInstanceMap.put(serviceName, context.getBean(beanName));
cacheIntances.put(type, extensionInstanceMap);
}
具體詳細代碼地址:RoadSPI