客戶端使用動態代理,根據不同的協議生成 不同的代理類,代理類中就是協議的客戶端實現。服務器的地址
和端口可以使用註冊中心來通知。下面使用netty來簡單實現。
1、請求、響應封裝類,使用jdk的序列化,序列化工具類
package com.rpc.msg;
import java.io.Serializable;
import java.util.Arrays;
public class RequestMsg implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String requestId;
private String className;
private String methodName;
private Class<?>[] parameterTypes;
private Object[] parameters;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Object[] getParameters() {
return parameters;
}
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
@Override
public String toString() {
return "RequestMsg [requestId=" + requestId + ", className=" + className + ", methodName=" + methodName
+ ", parameterTypes=" + Arrays.toString(parameterTypes) + ", parameters=" + Arrays.toString(parameters)
+ "]";
}
}
package com.rpc.msg;
import java.io.Serializable;
public class ResponseMsg implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String requestId;
private Object result;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
package com.rpc.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializationUtil {
public static <T> byte[] serialize(T obj) {
if (obj == null) {
return null;
}
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo;
try {
oo = new ObjectOutputStream(bo);
oo.writeObject(obj);
return bo.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("unchecked")
public static <T> T deserialize(byte[] objBytes, Class<T> cls) {
if (objBytes == null || objBytes.length == 0) {
return null;
}
ByteArrayInputStream bi = new ByteArrayInputStream(objBytes);
ObjectInputStream oi;
try {
oi = new ObjectInputStream(bi);
return (T) oi.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
2netty管道處理編碼解碼
package com.rpc.ende;
import com.rpc.util.SerializationUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class RpcEncoder extends MessageToByteEncoder<Object>{
private Class<?> genericClass;
public RpcEncoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
if (genericClass.isInstance(in)) {
byte[] data = SerializationUtil.serialize(in);
out.writeInt(data.length);
out.writeBytes(data);
}
}
}
package com.rpc.ende;
import java.util.List;
import com.rpc.util.SerializationUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
public class RpcDecoder extends ByteToMessageDecoder {
private Class<?> genericClass;
public RpcDecoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 4) {
return;
}
in.markReaderIndex();
int dataLength = in.readInt();
if (dataLength < 0) {
ctx.close();
}
if (in.readableBytes() < dataLength) {
in.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
in.readBytes(data);
Object obj = SerializationUtil.deserialize(data, genericClass);
out.add(obj);
}
}
3 服務端
ProviderServer 容器初始化完成後,找到所有業務接口的實現類,啓動netty服務器
RpcService用來標識接口實現類,在spring容器中的bean
NettyServer啓動netty服務器,接受管道傳來的request,使用反射處理得到結果,寫回管道
NettyServerHandler處理request的過程
package com.rpc.provider;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.rpc.provider.transation.NettyServer;
public class ProviderServer implements ApplicationContextAware{
private NettyServer nettyServer;
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
Map<String, Object> handlerMap = new HashMap<String, Object>();
Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class);
if (serviceBeanMap.values() != null) {
for (Object serviceBean : serviceBeanMap.values()) {
String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName();
handlerMap.put(interfaceName, serviceBean);
}
}
nettyServer.setHandlerMap(handlerMap);
try {
nettyServer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setNettyServer(NettyServer nettyServer) {
this.nettyServer = nettyServer;
}
}
package com.rpc.provider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class<?> value();
}
package com.rpc.provider.transation;
import java.util.Map;
import com.rpc.ende.RpcDecoder;
import com.rpc.ende.RpcEncoder;
import com.rpc.msg.RequestMsg;
import com.rpc.msg.ResponseMsg;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
private String serverAddress;
private Map<String,Object> handlerMap;
public NettyServer(String serverAddress) {
super();
this.serverAddress = serverAddress;
}
public void setHandlerMap(Map<String, Object> handlerMap) {
this.handlerMap = handlerMap;
}
public void start() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new RpcDecoder(RequestMsg.class))
.addLast(new RpcEncoder(ResponseMsg.class))
.addLast(new NettyServerHandler(handlerMap));
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
String[] array = serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
ChannelFuture future = bootstrap.bind(host, port).sync();
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
package com.rpc.provider.transation;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;
import com.rpc.msg.RequestMsg;
import com.rpc.msg.ResponseMsg;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class NettyServerHandler extends SimpleChannelInboundHandler<RequestMsg> {
private Map<String,Object> handlerMap;
public NettyServerHandler(Map<String, Object> handlerMap) {
super();
this.handlerMap = handlerMap;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, RequestMsg request) throws Exception {
ResponseMsg response = new ResponseMsg();
response.setRequestId(request.getRequestId());
try {
Object result = handle(request);
response.setResult(result);
} catch (Throwable t) {
response.setResult(t);
}
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private Object handle(RequestMsg request) throws InvocationTargetException {
String className = request.getClassName();
Object serviceBean = handlerMap.get(className);
Class<?> serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters();
/*
* Method method = serviceClass.getMethod(methodName, parameterTypes);
* method.setAccessible(true);
* return method.invoke(serviceBean,parameters);
*/
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
return serviceFastMethod.invoke(serviceBean, parameters);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
RpcProxy生成接口代理類方法,具體實現需要根據傳輸協議,使用jdk動態代理
NettyClient 客戶端連接服務器,發送請求,得到響應,使用CountDownLatch阻塞線程
ConsumerService爲了使用方便,將配置的接口,使用RpcProxy生成代理類,註冊到spring容器中
package com.rpc.consumer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
import com.rpc.consumer.transation.NettyClient;
import com.rpc.msg.RequestMsg;
import com.rpc.msg.ResponseMsg;
public class RpcProxy {
private String serverAddress;
public RpcProxy(String serverAddress) {
this.serverAddress = serverAddress;
}
@SuppressWarnings("unchecked")
public <T> T create(Class<?> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RequestMsg request = new RequestMsg();
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
String[] array = serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
NettyClient client = new NettyClient(host, port);
ResponseMsg response = client.send(request);
return response.getResult();
}
}
);
}
}
package com.rpc.consumer.transation;
import java.util.concurrent.CountDownLatch;
import com.rpc.ende.RpcDecoder;
import com.rpc.ende.RpcEncoder;
import com.rpc.msg.RequestMsg;
import com.rpc.msg.ResponseMsg;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient extends SimpleChannelInboundHandler<ResponseMsg>{
private String host;
private int port;
public NettyClient(String host, int port) {
super();
this.host = host;
this.port = port;
}
private CountDownLatch cdt = new CountDownLatch(1);
private ResponseMsg responseMsg;
public ResponseMsg send(RequestMsg request) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new RpcEncoder(RequestMsg.class))
.addLast(new RpcDecoder(ResponseMsg.class))
.addLast(NettyClient.this);
}
})
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().writeAndFlush(request).sync();
cdt.await();
if (responseMsg != null) {
future.channel().closeFuture().sync();
}
return responseMsg;
} finally {
group.shutdownGracefully();
}
}
@Override
protected void channelRead0(ChannelHandlerContext arg0, ResponseMsg arg1) throws Exception {
responseMsg = arg1;
cdt.countDown();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.rpc.consumer;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
public class ConsumerService implements ApplicationContextAware{
private RpcProxy rpcProxy;
public void setRpcProxy(RpcProxy rpcProxy) {
this.rpcProxy = rpcProxy;
}
private List<String> clsNames;
public ConsumerService(List<String> clsNames) {
super();
this.clsNames = clsNames;
}
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) arg0;
ConfigurableListableBeanFactory beanFactory = cac.getBeanFactory();
for (String string : clsNames) {
try {
Class<?> interfaceClass = Class.forName(string);
Object singletonObject = rpcProxy.create(interfaceClass);
beanFactory.registerSingleton(string, singletonObject);
} catch (ClassNotFoundException e) {
e.printStackTrace();
continue;
}
}
}
}
5 測試
測試接口及實現
App服務器端啓動類
application-provider.xml服務器配置文件
package test;
public interface HelloService {
String hi(String name);
}
package test.server;
import com.rpc.provider.RpcService;
import test.HelloService;
@RpcService(HelloService.class)
public class HelloServiceImpl implements HelloService {
public String hi(String name) {
return "hi " + name;
}
}
package test.server;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
@SuppressWarnings("resource")
public static void main(String[] args) {
new ClassPathXmlApplicationContext("application-provider.xml");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop--4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx--4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="test"/>
<bean id="nettyServer" class="com.rpc.provider.transation.NettyServer">
<constructor-arg name="serverAddress" value="127.0.0.1:8000"/>
</bean>
<bean id="providerServer" class="com.rpc.provider.ProviderServer">
<property name="nettyServer" ref="nettyServer"></property>
</bean>
</beans>
客戶端
application-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop--4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx--4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="rpcProxy" class="com.rpc.consumer.RpcProxy">
<constructor-arg name="serverAddress" value="127.0.0.1:8000"/>
</bean>
<bean id="providerServer" class="com.rpc.consumer.ConsumerService">
<constructor-arg name="clsNames">
<list>
<value>test.HelloService</value>
</list>
</constructor-arg>
<property name="rpcProxy" ref="rpcProxy"></property>
</bean>
</beans>
package test.client;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import test.HelloService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "application-consumer.xml")
public class HelloServiceTest {
@Autowired
private HelloService helloService;
@Test
public void test1(){
System.out.println(helloService.hi("world"));
}
}