在本版本中引入了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. 剑指offer刷题笔记

    删除链表中重复的结点:较难 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3->4->4- ...

  2. Windows搭建IIS服务器使用NATAPP实现内网穿透

    目的:外网可以访问本地网页. 步骤: 一.实现内网访问 1.Win+Q搜索[控制面板],选择[程序],点击[启用或关闭Windows功能], 2.勾选[Internet Information Ser ...

  3. python学习(8)实例:写一个简单商城购物车的代码

    要求: 1.写一段商城程购物车序的代码2.用列表把商城的商品清单存储下来,存到列表 shopping_mail3.购物车的列表为shopping_cart4.用户首先输入工资金额,判断输入为数字5.用 ...

  4. Python 元类 - Metaclasses

    Python 元类 - Metaclasses 默认情况下儿, classes 是有 type() 构造的. 类的结构体在一个新的 namespace 被执行, 类的名字 class name 绑定( ...

  5. Redis中RDB和AOF持久化区别和联系

    RDB和AOF持久化   ​RDB持久化 RDB是什么? 原理是redis会单独创建(fork) 一个与当前进程一模一 样的子进程来进行持久化,这个子进程的所有数据(变量.环境变量,程序程序计数器等) ...

  6. vue学习(四)登陆、注册、首页模板页区分

    按照上面文章配置完毕后,会发现有个问题,我登陆页面.注册页面是不需要视图页的. 开始配置路由 重新配置main.js 引入 import App from './App' //引入vue组件 更改启动 ...

  7. Java Stack使用

    1.Stack继承自Vector.遵从先进后出的规则. 2.Stack 是线程同步的.(map.List.Set是线程不同步的,需要在外部封装的时候来同步) 试例代码: public static v ...

  8. 计算机网络 From Mr.Liu

    引言 本博客摘自Mr.Liu,原帖请点击这里. 感谢Mr.Liu,这个文章很充分的描述了计算机网络的核心知识点. 我还在学习中,所以没有进行自己的转述.图片因为是copy代码而没有获得,想看更详尽的, ...

  9. 刷题94. Binary Tree Inorder Traversal

    一.题目说明 题目94. Binary Tree Inorder Traversal,给一个二叉树,返回中序遍历序列.题目难度是Medium! 二.我的解答 用递归遍历,学过数据结构的应该都可以实现. ...

  10. 大白话工厂方法模式(Factory Method)

    目录 简单工厂模式缺陷 简单工厂模式改造 工厂方法模式定义 工厂方法模式结构 工厂方法模式分析 参考文献 简单工厂模式缺陷 大白话简单工厂模式(Simple Factory Pattern)中通过买车 ...