上篇讲了RPC服务端的实现。原理就是解析netty通道数据拿到类、方法及入参等信息,然后通过java反射机制调用本地接口返回结果。没有用到很复杂的技术。

这篇我们将客户端的实现。说白了客户端的任务很简单:一是建立socket长连接。二是封装发送服务端需要的数据包。三是处理返回结果。

demo地址

https://gitee.com/syher/grave-netty

RPC实现

同样定义注解扫描service接口。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({NettyClientScannerRegistrar.class, NettyClientApplicationContextAware.class})
public @interface NettyClientScan { String[] basePackages(); Class<? extends NettyFactoryBean> factoryBean() default NettyFactoryBean.class;
}

  

该注解用于spring boot启动类上,参数basePackages指定接口所在的包路径。

@SpringBootApplication
@NettyClientScan(basePackages = {
"com.braska.grave.netty.api.service"
})
public class GraveNettyClientApplication { public static void main(String[] args) {
SpringApplication.run(GraveNettyClientApplication.class, args);
} }

  

NettyServerScannerRegistrar类注册bean。

public class NettyClientScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// spring bean注册
NettyClientInterfaceScanner scanner = new NettyClientInterfaceScanner(registry); AnnotationAttributes annoAttrs =
AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(NettyClientScan.class.getName())); Class<? extends NettyFactoryBean> nettyFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!NettyFactoryBean.class.equals(nettyFactoryBeanClass)) {
scanner.setNettyFactoryBean(BeanUtils.instantiateClass(nettyFactoryBeanClass));
} List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
} scanner.doScan(StringUtils.toStringArray(basePackages));
}
}

  

NettyClientInterfaceScanner类使用jdk动态代理basePackages路径下的接口。

public class NettyClientInterfaceScanner extends ClassPathBeanDefinitionScanner {
private NettyFactoryBean nettyFactoryBean = new NettyFactoryBean(); @Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {
} else {
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} private void processBeanDefinitions(
Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 为对象属性赋值(这一块我也还不太明白)
       definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
// 这里的nettyFactoryBean是生成Bean实例的工厂,不是Bean本身
definition.setBeanClass(this.nettyFactoryBean.getClass()); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}

  

NettyFactoryBean

public class NettyFactoryBean<T> implements FactoryBean<T> {
private Class<T> nettyInterface; public NettyFactoryBean() {} public NettyFactoryBean(Class<T> nettyInterface) {
this.nettyInterface = nettyInterface;
} @Override
public T getObject() throws Exception {
// 通过jdk动态代理创建实例
return (T) Proxy.newProxyInstance(nettyInterface.getClassLoader(), new Class[]{nettyInterface}, c.getInstance());
} @Override
public Class<?> getObjectType() {
return this.nettyInterface;
} @Override
public boolean isSingleton() {
return true;
}
}

  

关键来了,NettyInterfaceInvoker类负责数据包封装及发送。

public class NettyInterfaceInvoker implements InvocationHandler {

    private RequestSender sender;
// 静态内部类做单例模式
private static class SINGLETON {
private static final NettyInterfaceInvoker invoker = new NettyInterfaceInvoker(); private static NettyInterfaceInvoker setSender(RequestSender sender) {
invoker.sender = sender;
return invoker;
}
} public static NettyInterfaceInvoker getInstance() {
return SINGLETON.invoker;
} public static NettyInterfaceInvoker setSender(RequestSender sender) {
return SINGLETON.setSender(sender);
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 数据包封装,包含类名、方法名及参数等信息。
Request request = new Request();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameters(args);
request.setParameterTypes(method.getParameterTypes());
request.setId(UUID.randomUUID().toString());
// 数据发送
Object result = sender.send(request);
Class<?> returnType = method.getReturnType();
// 处理返回数据
Response response = JSON.parseObject(result.toString(), Response.class);
if (response.getCode() == 1) {
throw new Exception(response.getError());
}
if (returnType.isPrimitive() || String.class.isAssignableFrom(returnType)) {
return response.getData();
} else if (Collection.class.isAssignableFrom(returnType)) {
return JSONArray.parseArray(response.getData().toString(), Object.class);
} else if (Map.class.isAssignableFrom(returnType)) {
return JSON.parseObject(response.getData().toString(), Map.class);
} else {
Object data = response.getData();
return JSONObject.parseObject(data.toString(), returnType);
}
}
}

  

接着我们来看看RequestSender怎么处理数据的。

public interface RequestSender {
Channel connect(SocketAddress address) throws InterruptedException; Object send(Request request) throws InterruptedException;
}

  

RequestSender本身只是一个接口。他的实现类有:

public class NettyClientApplicationContextAware extends ChannelInitializer<SocketChannel>
implements RequestSender, ApplicationContextAware, InitializingBean {
private static final Logger logger = Logger.getLogger(NettyClientApplicationContextAware.class.getName()); private String remoteAddress;
private Bootstrap bootstrap;
private EventLoopGroup group;
private NettyChannelManager manager;
private NettyClientHandler handler; @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.remoteAddress = applicationContext.getEnvironment().getProperty("remoteAddress");
this.bootstrap = new Bootstrap();
this.group = new NioEventLoopGroup(1);
this.bootstrap.group(group).
channel(NioSocketChannel.class).
option(ChannelOption.TCP_NODELAY, true).
option(ChannelOption.SO_KEEPALIVE, true).
handler(this);
this.manager = new NettyChannelManager(this);
this.handler = new NettyClientHandler(manager, remoteAddress);
} @Override
public void afterPropertiesSet() throws Exception {
// socket连接入口。
this.manager.refresh(Lists.newArrayList(remoteAddress));
} @Override
public Object send(Request request) throws InterruptedException {
Channel channel = manager.take();
if (channel != null && channel.isActive()) {
SynchronousQueue<Object> queue = this.handler.sendRequest(request, channel);
Object result = queue.take();
return JSONArray.toJSONString(result);
} else {
Response res = new Response();
res.setCode(1);
res.setError("未正确连接到服务器.请检查相关配置信息!");
return JSONArray.toJSONString(res);
}
} @Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new IdleStateHandler(0, 0, 30));
pipeline.addLast(new JSONEncoder());
pipeline.addLast(new JSONDecoder());
// 管道处理器
pipeline.addLast(this.handler);
} @Override
public Channel connect(SocketAddress address) throws InterruptedException {
ChannelFuture future = bootstrap.connect(address);
// 建立长连接,提供失败重连。
future.addListener(new ConnectionListener(this.manager, this.remoteAddress));
Channel channel = future.channel();//future.sync().channel();
return channel;
} public void destroy() {
this.group.shutdownGracefully();
}
}

  

NettyClientHandler类处理管道事件。与服务端不通,这个管道处理器是继承ChannelInboundHandlerAdapter类。

@ChannelHandler.Sharable
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = Logger.getLogger(NettyServerHandler.class.getName()); private ConcurrentHashMap<String, SynchronousQueue<Object>> queueMap = new ConcurrentHashMap<>();
private NettyChannelManager manager;
private String remoteAddress; public NettyClientHandler(NettyChannelManager manager, String remoteAddress) {
this.manager = manager;
this.remoteAddress = remoteAddress;
} @Override
public void channelInactive(ChannelHandlerContext ctx) {
InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
logger.info("与netty服务器断开连接." + address);
ctx.channel().close();
manager.remove(ctx.channel());
// 掉线重连
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(() -> {
manager.refresh(Lists.newArrayList(remoteAddress));
}, 1L, TimeUnit.SECONDS);
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 处理服务端返回的数据
Response response = JSON.parseObject(msg.toString(), Response.class);
String requestId = response.getRequestId();
SynchronousQueue<Object> queue = queueMap.get(requestId);
queue.put(response);
queueMap.remove(requestId);
} public SynchronousQueue<Object> sendRequest(Request request, Channel channel) {
// 使用阻塞队列处理客户端请求
SynchronousQueue<Object> queue = new SynchronousQueue<>();
queueMap.put(request.getId(), queue);
channel.writeAndFlush(request);
return queue;
} public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
logger.info("发送心跳消息...");
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.ALL_IDLE) {
Request request = new Request();
request.setMethodName("heartBeat");
ctx.channel().writeAndFlush(request);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}

  

这样,RPC的客户端就写好了,其中主要涉及到的关键内容就是netty实例及管道处理器、jdk动态代理、还有一个阻塞队列。

结合上篇RPC服务端。一个完整的RPC框架就搭建完了。

当然,有些地方处理的还是比较粗糙。后续有修改以git代码为准。

基于netty实现rpc框架-spring boot客户端的更多相关文章

  1. 基于netty实现rpc框架-spring boot服务端

    demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...

  2. 这样基于Netty重构RPC框架你不可能知道

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365天原创计划”第5天. 今天呢!灯塔君跟大家讲: 基于Netty重构RPC框架 一.CyclicBarrier方法说明 1. ...

  3. 《Java 编写基于 Netty 的 RPC 框架》

    一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...

  4. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  5. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  6. DIY一些基于netty的开源框架

    几款基于netty的开源框架,有益于对netty的理解和学习! 基于netty的http server框架 https://github.com/TogetherOS/cicada 基于netty的即 ...

  7. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

  8. 基于Netty的RPC简易实现

    代码地址如下:http://www.demodashi.com/demo/13448.html 可以给你提供思路 也可以让你学到Netty相关的知识 当然,这只是一种实现方式 需求 看下图,其实这个项 ...

  9. 实现基于netty的web框架,了解一下

    上一篇写了,基于netty实现的rpc的微框架,其中详细介绍netty的原理及组件,这篇就不过多介绍 这篇实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正 先普及几 ...

随机推荐

  1. OpenCV-Python 对极几何 | 五十一

    目标 在本节中 我们将学习多视图几何的基础知识 我们将了解什么是极点,极线,极线约束等. 基础概念 当我们使用针孔相机拍摄图像时,我们失去了重要信息,即图像深度. 或者图像中的每个点距相机多远,因为它 ...

  2. ​知识图谱与机器学习 | KG入门 -- Part1 Data Fabric

    介绍 如果你在网上搜索机器学习,你会找到大约20500万个结果.确实是这样,但是要找到适合每个用例的描述或定义并不容易,然而会有一些非常棒的描述或定义.在这里,我将提出机器学习的另一种定义,重点介绍一 ...

  3. 仅用200个样本就能得到当前最佳结果:手写字符识别新模型TextCaps

    由于深度学习近期取得的进展,手写字符识别任务对一些主流语言来说已然不是什么难题了.但是对于一些训练样本较少的非主流语言来说,这仍是一个挑战性问题.为此,本文提出新模型TextCaps,它每类仅用200 ...

  4. 事务框架之声明事务(自动开启,自动提交,自动回滚)Spring AOP 封装

    利用Spring AOP 封装事务类,自己的在方法前begin 事务,完成后提交事务,有异常回滚事务 比起之前的编程式事务,AOP将事务的开启与提交写在了环绕通知里面,回滚写在异常通知里面,找到指定的 ...

  5. Jmeter 注册多个用户 之 CSV Data set Config

    1. 打开Jmeter,新建一个测试计划 > 新建线程组> 创建一个Http 请求 2. 创建一个信息头管理器 > content-Type   application/json; ...

  6. AOJ 2214: Warp Hall(计数+dp)

    题目链接 题意 有一个 \(N × M\) 的二维平面, 平面上有 k 对虫洞, \(N, M ≤ 1e5, k ≤ 1e3\). 每对虫洞具有坐标 \(x_1, y_1, x_2, y_2\), 满 ...

  7. 纯html加css的键盘UI效果图

    先上效果图: 没有打字的功能,纯属是个界面图(一时无聊写的) 代码如下: <!DOCTYPE html> <html> <head> <meta charse ...

  8. ssh-add和ssh-agent

    注: 因为在ssh-agent异常关闭或者新开窗口是会导致ssh-add找不到私钥,导致添加的私钥无效,所以下面使用keychain管理 ssh-add 参数 -l 查看代理中的私钥 -L 查看代理中 ...

  9. python:列表生成式的学习

    看个例子: # 定义一个列表 l=[1,2,3,4,5] #()用于创建一个list,结果依次返回列表l的元素的平方,返回list s=[i*i for i in l] # 打印列表s print(s ...

  10. js中使用Timer来计时程序执行时 - [javascript] - [开发]

    在我们开发过程中,我们也在不断的学习,以及优化自己的代码质量. 我们时常需要一个计时器,来对代码某段或者某些段执行进行计时,以评估代码运行质量,考虑是否优化. 以及优化后的直观对比. JavaScri ...