记一次Java动态代理实践【首发自高可用架构公众号】
1. 背景
最近在做数据库(MySQL)方面的升级改造。现状是数据库同时被多个应用直连,存在了一些问题:
- 有大量的重复代码,维护成本较高,也不优雅;
- 出现SQL语句质量的问题无法很快定位到是哪个应用导致的;
- 数据库调用方过于分散,不便于统一控制,比如部分业务数据的读写、屏蔽等;
- 业务的发展,有的表数据量已经到了一定的规模,几百万到几千万不等,数据库存储拆分是必须要进行的事情。
解决问题的方式很简单,就是把各应用中与此业务相关的dao层抽象成为一个单独应用(以下称为internal-rpc-app),进行统一管理。
2. 具体实现
具体的业务应用与internal-rpc-app的内部通信使用公司内部具有服务治理功能的RPC框架JSF,JSF是一款稳定高效的框架,它对服务治理的粒度是接口,接口通过Spring做服务的发布和调用配置。每个接口对应一个数据表的CURD及特殊业务。
2.1 标准版本 1.0
2.1.1. 接口注册申请
注册接口: com.jd.xx.BizRpcService1
注册接口: com.jd.xx.BizRpcService2
注册接口: com.jd.xx.BizRpcService3
......
注册接口: com.jd.xx.BizRpcServiceN
2.1.2. 服务提供方配置
<jsf:provider id="rpc1" interface="com.jd.xx.BizRpcService1"/>
<jsf:provider id="rpc2" interface="com.jd.xx.BizRpcService2"/>
<jsf:provider id="rpc3" interface="com.jd.xx.BizRpcService3"/>
......
<jsf:provider id="rpcN" interface="com.jd.xx.BizRpcServiceN"/>
2.1.3. 客户端调用方配置
<jsf:consumer id="rpc1" interface="com.jd.xx.BizRpcService1"/>
<jsf:consumer id="rpc2" interface="com.jd.xx.BizRpcService2"/>
<jsf:consumer id="rpc3" interface="com.jd.xx.BizRpcService3"/>
......
<jsf:consumer id="rpcN" interface="com.jd.xx.BizRpcServiceN"/>
2.1.4 小结
此版本实现了我们的最初的目的,但是有一个不太好的地方,就是配置的工作量太高,前面有说到JSF框架的治理维度是接口。这也意味着,每次新增接口都要提交申请操作,同时要在consumer和provider做相对应的配置,几个还好,如果数据表有几十个上百个,重复的工作量就很大。同时内部接口也不需要做太细粒度的服务治理。于是有了第二版,主要目的是简化大量重复配置。
2.2 优化调用体验版本 2.0
2.2.1 内部实现 - 客户端
首先定义调用API:
BizRpcService1 bizRpcService1 = InternalPrxoyServices.BizRpcService1;
bizRpcService1.method1(param1, param2);
InternalPrxoyServices关键代码:
public class InternalPrxoyServices {
BizProxyRpcService1 BizRpcService1 = new BizProxyRpcService1();
BizProxyRpcService2 BizRpcService2 = new BizProxyRpcService2();
BizProxyRpcService2 BizRpcService3 = new BizProxyRpcService3();
......
BizProxyRpcServiceN BizRpcServiceN = new BizProxyRpcServiceN();
}
BizProxyRpcService1关键代码:
public class BizProxyRpcService1 extends BaseProxyService implements BizRpcService1 {
@Override
public String method1(String param1, String param2) throws InternalRpcException {
return super.execute(param1, param2);
}
}
BaseProxyService关键代码:
abstract class BaseProxyService implements InternalRpcService {
private InternalRpcService internalRpcService;
private Map<String, String> interfaceMap = new HashMap<String, String>();
BaseProxyService() {
// 已经被注入到一个静态变量中,此处直接获取
internalRpcService = InternalRpcServiceHolder.getInternalRpcService();
}
@Override
<T> T execute(Object... objects) throws InternalRpcException {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement callerStackTraceElement = stackTraceElements[2];
// 要取代理类实现到具体接口名称和方法名称以此定位具体到逻辑,框架不支持重名,所以重名问题不考虑
String interfaceName = getInterfaceName(callerStackTraceElement.getClassName());
String methodName = callerStackTraceElement.getMethodName();
return (T) internalRpcService.execute(interfaceName, methodName, objects);
}
}
InternalRpcService接口定义:
public interface InternalRpcService {
Object execute(String serviceName, String methodName, Object... objects) throws InternalRpcException;
}
2.2.2 内部实现 - 服务端
服务端实现比较简单,直接根据接口传过来当serviceName、methodName、objects即可定位到具体service到方法,直接执行即可。
2.2.3 小结
到这里,我们通过静态代理实现了具体的目标,通过实现具体的接口类。我们不再需要定义过多的配置了,客户端调用也变得简单明了。
那么,结束了吗?并没有,在解决了大量配置的问题的同时,因为要写大量的代理类,又引入了新的工作量。
2.3 最终版本 3.0
在这一版,我们很自然的引入了动态生成代理。
2.3.1 客户端具体实现
因为引入了动态代理,所以要重新改写InternalPrxoyServices:
public class InternalPrxoyServices {
BizRpcService1 BizRpcService1 = ProxyServiceFactory.create(BizRpcService1.class);
BizRpcService2 BizRpcService2 = ProxyServiceFactory.create(BizRpcService2.class);
BizRpcService3 BizRpcService3 = ProxyServiceFactory.create(BizRpcService3.class);
......
BizRpcServiceN BizRpcServiceN = ProxyServiceFactory.create(BizRpcServiceN.class);
}
ProxyServiceFactory关键代码:
public class ProxyServiceFactory {
public static <T> T create(Class<T> internalInterface) {
final InternalRpcProxy<T> internalRpcProxy = new InternalRpcProxy<T>(internalInterface);
return (T) Proxy.newProxyInstance(internalInterface.getClassLoader(), new Class[] { internalInterface }, internalRpcProxy);
}
}
InternalRpcProxy关键代码:
class InternalRpcProxy<T> implements InvocationHandler, Serializable {
private final Class<T> internalRpcInterface;
InternalRpcProxy(Class<T> internalRpcInterface) {
this.internalRpcInterface = internalRpcInterface;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return InternalRpcServiceHolder.internalRpcService.execute(internalRpcInterface.getCanonicalName(), method.getName(), args);
}
}
2.4 小结
至此,已经解决了2.2.3提到的大量创建代理类的问题。
当然我们还做了很多文章中没有提及的事情,比如:
- 通过声明哪些接口可以走动态代理
- 方法重名、客户端接口合法性等校验
- 将method存入缓存
3. 总结
通过建立独立的应用,解决了前面数据库被多应用读写所产生的问题,通过开发了统一接口解决了服务端和客户端配置过多的问题。代码经过一步步抽象后,最终发现实现了一个简单的RPC雏形,只不过通信层是基于公司的JSF框架。
这引发了一些框架方面的思考:
- 是否应该去掉人工审批类似的流程?
- 是否应该允许更灵活的发布服务,比如:根据注解自动扫描发布服务,根据注解自动获取服务。
这不是技术问题,而是怎么权衡的问题。
记一次Java动态代理实践【首发自高可用架构公众号】的更多相关文章
- [置顶]
来自 Google 的高可用架构理念与实践
转自: https://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=402738153&idx=1&sn=af5e76aad ...
- Java 动态代理机制分析及扩展
Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...
- [转]Java 动态代理机制分析及扩展
引言 Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执 ...
- Java 动态代理机制分析及扩展--转
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/#icomments http://www.ibm.com/developerworks/c ...
- Java 动态代理机制分析及扩展,第 1 部分
Java 动态代理机制分析及扩展,第 1 部分 http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 本文通过分析 Java 动态代理的机制和特 ...
- 一文读懂Java动态代理
作者 :潘潘 日期 :2020-11-22 事实上,对于很多Java编程人员来说,可能只需要达到从入门到上手的编程水准,就能很好的完成大部分研发工作.除非自己强主动获取,或者工作倒逼你学习,否则我们好 ...
- JAVA动态代理的全面深层理解
Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过 ...
- Java 动态代理作用是什么?
Java 动态代理作用是什么? 1 条评论 分享 默认排序按时间排序 19 个回答 133赞同反对,不会显示你的姓名 Intopass 程序员,近期沉迷于动漫ING 133 人赞同 ① 首先你 ...
- Java 动态代理
被代理的接口特点: 1. 不能有重复的接口,以避免动态代理类代码生成时的编译错误. 2. 这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败. 3. 需被代理的所有非 pub ...
随机推荐
- npm基本操作(安装,搭建环境,打包)
Nodejs(npm)环境安装: npm的安装配置(从nodejs网站上安装npm): Windows安装Nodejs操作步骤如下: 64 位安装包下载地址 : https://nodejs.org/ ...
- Pycharm 常用快捷键
常用快捷键 快捷键 功能 Ctrl + Q 快速查看文档 Ctrl + F1 显示错误描述或警告信息 Ctrl + / 行注释(可选中多行) Ctrl + Alt + L 代码格式化 Ctrl + A ...
- 【故障公告】10:30-10:45 左右 docker swarm 集群节点问题引发故障
非常抱歉,今天 10:30-10:45 左右由于 docker swarm 集群节点出现问题,造成除博客之外的站点出现访问异常,由此给您带来很大的麻烦,请您谅解. 故障开始时出现有时访问正常有时访问出 ...
- 学习storm实现求和操作
1 storm求和简单操作 主要逻辑,就是spout发送数据源,blot进行处理数据,主要注意的点就是 spout这有个nextTuple自旋,和使用父类的declare..方法声明要发送到下游的名称 ...
- mysql5.6.x 字符集修改
1 安装好mysql5.6.x 之后,修改字符集配置为utf8才能支持中文,因为默认为latin1 查看mysql字符集命令: SHOW VARIABLES LIKE 'char%' 2 修改配置文件 ...
- B树和B+树详解
一 B树 1.B树的定义:B树(B-tree)是一种树状数据结构,它能够存储数据.对其进行排序并允许以O(log n)的时间复杂度运行进行查找.顺序读取.插入和删除的数据结构.B树,概括来说是一个节点 ...
- HNOI2019游记
\(day~?\) 我们的老师告诉我说,你这次省选目标分:\(70\),拿不到,家法伺候.但其实,我的目标是不爆零!!! \(day~-1\) 这天晚上,我们的指导老师给我们试了一下ZJOI2019, ...
- webview 更新方法细节
plus.webview.getWebviewById("user.home").evalJS("getHomeData()");//调用页面方法 plus.w ...
- Python基础:数据类型-数字(5)
在Python中,所有的数据类型都是类,每一个变量都是类的实例. Python中有6种标准数据类型:数字(Number).字符串(String).列表(List).元组(Tuple).集合(Sets) ...
- jqgrid的增删改查
这个是要写的页面(需要引入下面的js页面) 1 <div class="modal-body" width="100%" style="over ...