在本版本中引入了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

轻量级RPC设计与实现第四版的更多相关文章

  1. 轻量级RPC设计与实现第三版

    在前两个版本中,每次发起请求一次就新建一个netty的channel连接,如果在高并发情况下就会造成资源的浪费,这时实现异步请求就十分重要,当有多个请求线程时,需要设计一个线程池来进行管理.除此之外, ...

  2. 轻量级RPC设计与实现第五版(最终版)

    在最近一段时间里,通过搜集有关资料加上自己的理解,设计了一款轻量级RPC,起了一个名字lightWeightRPC.它拥有一个RPC常见的基本功能.主要功能和特点如下: 利用Spring实现依赖注入与 ...

  3. 轻量级RPC设计与实现第二版

    在上一个版本中利用netty实现了简单的一对一的RPC,需要手动设置服务地址,限制性较大. 在本文中,利用zookeeper作为服务注册中心,在服务端启动时将本地的服务信息注册到zookeeper中, ...

  4. 轻量级RPC设计与实现第一版

    什么是RPC RPC (Remote Procedure Call Protocol), 远程过程调用,通俗的解释就是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应 ...

  5. 【文章内容来自《Android 应用程序开发权威指南》(第四版)】如何设计兼容的用户界面的一些建议(有删改)

    最近一直在看的一本书是<Android 应用程序开发权威指南>(第四版),十分推荐.书中讲到了一些用户界面设计的规范,对于初学者我认为十分有必要,在这里码给大家,希望对我们都有用. 在我们 ...

  6. 微博轻量级RPC框架Motan

    Motan 是微博技术团队研发的基于 Java 的轻量级 RPC 框架,已在微博内部大规模应用多年,每天稳定支撑微博上亿次的内部调用.Motan 基于微博的高并发和高负载场景优化,成为一套简单.易用. ...

  7. 微博轻量级RPC框架Motan正式开源:支撑千亿调用

    支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了,项目地址为https://github.com/weibocom/motan. 微博轻量级RPC框架Motan正式开源 Motan 是微 ...

  8. C# 的轻量级 RPC 框架

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Comple ...

  9. 《Thinking In Java第四版》拾遗

    <Thinking In Java第四版>拾遗 转自我的github(http://katsurakkkk.github.io/2016/05/Thinking-In-Java%E7%AC ...

随机推荐

  1. eclipse新下载,安装和配置

    question1 java11没有jre,无法通过eclipse-inst-win64进行安装 solution Windows 7 64bit 安装jdk i586还是jdk x64?jdk x6 ...

  2. Dubbo 服务 IP 注册错误踩坑经历

    个人博客地址 studyidea.cn,点击查看更多原创文章 踩坑 公司最近新建一个机房,需要将现有系统同步部署到新机房,部署完成之后,两地机房同时对提供服务.系统架构如下图: 这个系统当前对外采用 ...

  3. 题解【[AHOI2013]作业】

    \[ \texttt{Preface} \] 数据貌似很水,据说 \(A_i\leq n\) ,连离散化都不需要. 不知道为啥设块大小为 \(\frac{n}{\sqrt m}\) 会一直 Runti ...

  4. IniRealm讲解

    Shiro自定义Realm 内置Realm: user.ini [users] Mark=123456,admin [roles] admin=user:delete,user:update IniR ...

  5. Hapi+MySql项目实战环境初始化(一)

    因为项目要求特殊的原因,公司要求使用Nodejs+HApi(纯英文的API)+Mysql构建新的项目.网上找了一堆资料,看了半天一脸懵逼.结论就是:版本的差异,资料国内几乎都是旧的17年前的了.根据资 ...

  6. python3配置阿里云短信服务

    1.申请阿里云短信服务,具体步骤看我的python2-Django配置短信服务 2.安装依赖 aliyun-python-sdk-core-v3 aliyun-python-sdk-dysmsapi= ...

  7. Hibernate入门之命名策略(naming strategy)详解

    前言 JPA和Hibernate都提供了默认映射策略,通过映射将每个实体类映射到具有相同名称的数据库表,它的每个属性都映射到具有相同属性的列, 但是,在实际项目开发中可能出现与默认命名约定不匹配,也就 ...

  8. Linux部署.NetCore站点 使用Supervisor进行托管部署

    前言 之前终于在Linux上部署好了.NetCore站点,但是这个站点非常“脆弱”.当我的ssh连接关闭或者我想在当前连接执行其他命令时候就必须关闭dotnet站点的执行程序.这显然不是我想要达到的效 ...

  9. 【JZOJ 5048】【GDOI2017模拟一试4.11】IQ测试

    题目大意: 判断一个序列是否是另外一个序列删除若干个数字之后得到的. 正文: 我们可以定义两个指针,分别指向长序列和短序列. 拿样例来举例: 如果指针指的数相同,两个指针都往右跳: 如果不同,则指向长 ...

  10. Mac下appium-doctor提示错误汇总

    一.            提示 [Error: Could not detect Mac OS X Version from sw_vers output: '10.12'] 解决方法: 1.终端执 ...