這個是rpc遠程調用的簡單demo:Consumer通過rpc遠程調用Provider的服務方法sayHelloWorld(String msg),然後Provider返回""Hello World"給Consumer。
這裏採用netty來實現遠程通信實現rpc調用,消費者通過代理來進行遠程調用遠程服務。本文涉及的知識點有代理模式,jdk動態代理和netty通信。這個簡單demo將服務提供者的服務註冊緩存在jvm本地,後續將會考慮將服務提供者的服務註冊到zookeeper註冊中心。
這個簡單demo將從以下四方面去進行實現,第一是公共基礎層,這一層是Consumer和Provider將會共用的api和netty遠程通信之間要交換的信息;第二是Provider本地註冊服務的實現;第三是Provider的實現,第四是Consumer的實現。廢話不多說,下面直接上代碼:
1,公共基礎層
1.1 調用信息:RpcMessage
package com.jinyue.common.message;
import java.io.Serializable;
/**
* netty遠程通信過程中傳遞的消息
*/
public class RpcMessage implements Serializable {
private String className;
private String methodName;
private Class<?>[] parameterType;
private Object[] parameterValues;
public RpcMessage(String className, String methodName, Class<?>[] parameterType, Object[] parameterValues) {
this.className = className;
this.methodName = methodName;
this.parameterType = parameterType;
this.parameterValues = parameterValues;
}
public void setClassName(String className) {
this.className = className;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public void setParameterType(Class<?>[] parameterType) {
this.parameterType = parameterType;
}
public void setParameterValues(String parameterValue) {
this.parameterValues = parameterValues;
}
public String getClassName() {
return className;
}
public String getMethodName() {
return methodName;
}
public Class<?>[] getParameterType() {
return parameterType;
}
public Object[] getParameterValues() {
return parameterValues;
}
}
複製代碼
1.2 接口api:IHelloWorld
package com.jinyue.common.api;
public interface IHelloWorld {
String sayHelloWorld(String name, String content);
}
複製代碼
2,Provider本地註冊服務的實現
2.1 Provider服務端啓動者類:LocalRegistryMain
package com.jinyue.registry;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import org.apache.log4j.Logger;
/**
* 這個作爲provider的提供者啓動類,實質就是啓動netty服務時,
* 添加ProviderRegistryHandler到netty的handler處理函數中。
*/
public class LocalRegistryMain {
private static final Logger logger = Logger.getLogger(LocalRegistryMain.class);
private static final int SERVER_PORT = 8888;
public static void main(String[] args) {
// 創建主從EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 將主從主從EventLoopGroup綁定到server上
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 這裏添加解碼器和編碼器,防止拆包和粘包問題
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
// 這裏採用jdk的序列化機制
pipeline.addLast("jdkencoder", new ObjectEncoder());
pipeline.addLast("jdkdecoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
// 添加自己的業務邏輯,將服務註冊的handle添加到pipeline
pipeline.addLast(new ProviderNettyHandler());
}
});
logger.info("server start,the port is " + SERVER_PORT);
// 這裏同步等待future的返回,若返回失敗,那麼拋出異常
ChannelFuture future = serverBootstrap.bind(SERVER_PORT).sync();
// 關閉future
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 最後記得主從group要優雅停機。
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
複製代碼
2.2Provider服務註冊Handler:ProviderNettyHandler
package com.jinyue.registry;
import com.jinyue.common.message.RpcMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.lang.reflect.Method;
/**
* 有consumer調用時,此時ProviderNettyHandler再從ProviderRestry類的緩存實例根據傳過來的接口名拿到實現類實例,
* 然後再拿到實現類實例的方法,再對該方法進行反射調用,最後將調用後的結果返回給consumer即可。
*/
public class ProviderNettyHandler extends ChannelInboundHandlerAdapter {
/**
* 當netty服務端接收到有consumer的請求時,此時將會進入到這個channelRead方法
* 此時就可以把consumer調用的參數提取出來,然後再從ProviderRestry類的緩存註冊中心instanceCacheMap裏
* 提取出反射實例,然後進行方法調用,再返回結果給consumer即可
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 提取consumer傳遞過來的參數
RpcMessage rpcMessage = (RpcMessage) msg;
String interfaceName = rpcMessage.getClassName();
String methodName = rpcMessage.getMethodName();
Class<?>[] parameterType = rpcMessage.getParameterType();
Object[] parameterValues = rpcMessage.getParameterValues();
// 將註冊緩存instanceCacheMap的provider實例提取出來,然後進行反射調用
Object instance = ProviderLocalRegistry.getInstanceCacheMap().get(interfaceName);
Method method = instance.getClass().getMethod(methodName, parameterType);
Object res = method.invoke(instance, parameterValues);
// 最後將結果刷到netty的輸出流中返回給consumer
ctx.writeAndFlush(res);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
複製代碼
2.3 服務提供者本地註冊類:ProviderLocalRegistry
package com.jinyue.registry;
import org.apache.log4j.Logger;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 該類主要時充當“註冊中心的作用”
* 將provider的服務實現類註冊到本地緩存裏面,採用ConcurrentHashMap【key爲接口名,value爲服務實例】
*/
public class ProviderLocalRegistry {
private static final Logger logger = Logger.getLogger(ProviderNettyHandler.class);
// 服務提供者所在的包
private static final String PROVIDER_PACKAGE_NAME = "com.jinyue.provider";
// 用來裝服務提供者的實例
private static Map<String, Object> instanceCacheMap = new ConcurrentHashMap<>();
// 用來存放實現類的類名
private static List<String> providerClassList = new ArrayList<>();
static {
// 掃描provider包下面的實現類,並放進緩存instanceMap裏面
loadProviderInstance(PROVIDER_PACKAGE_NAME);
}
/**
* 掃描provider包下面的實現類,並放進緩存instanceMap裏面
* @param packageName
*/
private static void loadProviderInstance(String packageName) {
findProviderClass(packageName);
putProviderInstance();
}
/**
* 找到provider包下所有的實現類名,並放進providerClassList裏
*/
private static void findProviderClass(final String packageName) {
// 靜態方法內不能用this關鍵字
// this.getClass().getClassLoader().getResource(PROVIDER_PACKAGE_NAME.replace("\\.", "/"));
// 所以得用匿名內部類來解決
// 這裏由classLoader的getResource方法獲得包名並封裝成URL形式
URL url = new Object() {
public URL getPath() {
String packageDir = packageName.replace(".", "/");
URL o = this.getClass().getClassLoader().getResource(packageDir);
return o;
}
}.getPath();
// 將該包名轉換爲File格式,用於以下判斷是文件夾還是文件,若是文件夾則遞歸調用本方法,
// 若不是文件夾則直接將該provider的實現類的名字放到providerClassList中
File dir = new File(url.getFile());
File[] fileArr = dir.listFiles();
for (File file : fileArr) {
if (file.isDirectory()) {
findProviderClass(packageName + "." + file.getName());
} else {
providerClassList.add(packageName + "." + file.getName().replace(".class", ""));
}
}
}
/**
* 遍歷providerClassList集合的實現類,並依次將實現類的接口作爲key,實現類的實例作爲值放入instanceCacheMap集合中,其實這裏也是模擬服務註冊的過程
* 注意這裏沒有處理一個接口有多個實現類的情況
*/
private static void putProviderInstance() {
for (String providerClassName : providerClassList) {
// 已經得到providerClassName,因此可以通過反射來生成實例
try {
Class<?> providerClass = Class.forName(providerClassName);
// 這裏得到實現類的接口的全限定名作爲key,因爲consumer調用時是傳接口的全限定名過來從緩存中獲取實例再進行反射調用
String providerClassInterfaceName = providerClass.getInterfaces()[0].getName();
// 得到Provicder實現類的實例
Object instance = providerClass.newInstance();
instanceCacheMap.put(providerClassInterfaceName, instance);
logger.info("註冊了" + providerClassInterfaceName + "的服務");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static Map<String, Object> getInstanceCacheMap() {
return instanceCacheMap;
}
}
複製代碼
3 具體服務提供者實現類:HelloWorldImpl
package com.jinyue.provider;
import com.jinyue.common.api.IHelloWorld;
/**
* 服務提供者
*/
public class HelloWorldImpl implements IHelloWorld {
public String sayHelloWorld(String name, String content) {
return name + " say:" + content;
}
}
複製代碼
4,服務消費者
4.1 consumer測試類:ConsumerTest
package com.jinyue.consumer;
import com.jinyue.common.api.IHelloWorld;
import com.jinyue.consumer.proxy.RpcProxyFactory;
/**
* z這個是consumer客戶端測試類
*/
public class ConsumerTest {
public static void main(String[] args) {
IHelloWorld helloWorld = (IHelloWorld)new RpcProxyFactory(IHelloWorld.class).getProxyInstance();
System.out.println(helloWorld.sayHelloWorld("jinyue", "hello world!"));
}
}
複製代碼
4.2 代理生成工廠類:RpcProxyFactory
package com.jinyue.consumer.proxy;
import com.jinyue.consumer.request.ConsumerNettyRequest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 動態代理工廠類,生成調用目標接口的代理類,這個代理類實質就是在InvocationHandler的invoke方法裏面調用
* netty的發送信息給服務端的相關請求方法而已,把調用目標接口類的相關信息(比如目標接口名,被調用的目標方法,
* 被調用目標方法的參數類型,參數值)發送給netty服務端,netty服務端接收到請求的這些信息後,然後再從緩存map
* (模擬註冊中心)拿到provider的實現類,然後再利用反射進行目標方法的調用。
*/
public class RpcProxyFactory {
private Class<?> target;
public RpcProxyFactory(Class<?> target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return new ConsumerNettyRequest().sendRequest(target.getName(), method.getName(),
method.getParameterTypes(), args);
}
});
}
}
複製代碼
4.3 消費端發送netty啓動及請求類:ConsumerNettyRequest
package com.jinyue.consumer.request;
import com.jinyue.common.message.RpcMessage;
import com.jinyue.consumer.handler.ConsumerNettyHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
* 這個類主要承擔consumer對netty服務端發請請求的相關邏輯
*/
public class ConsumerNettyRequest {
public Object sendRequest(String interfaceName, String methodName, Class<?>[] parameterType, Object[] parameterValues) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
ConsumerNettyHandler consumerNettyHandler = new ConsumerNettyHandler();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 這裏添加解碼器和編碼器,防止拆包和粘包問題
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0,
4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
// 這裏採用jdk的序列化機制
pipeline.addLast("jdkencoder", new ObjectEncoder());
pipeline.addLast("jdkdecoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
// 添加自己的業務邏輯,將服務註冊的handle添加到pipeline
pipeline.addLast(consumerNettyHandler);
}
});
ChannelFuture future = bootstrap.connect("127.0.0.1", 8888).sync();
future.channel().writeAndFlush(new RpcMessage(interfaceName, methodName, parameterType, parameterValues)).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
return consumerNettyHandler.getRes();
}
}
複製代碼
4.3 消費者處理相關Handler:ConsumerNettyHandler
package com.jinyue.consumer.handler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* 該類主要是客戶端請求netty服務端後且當返回結果時,會回調channelRead方法接收rpc調用返回結果
*/
public class ConsumerNettyHandler extends ChannelInboundHandlerAdapter {
private Object res;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.res = msg;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
public Object getRes() {
return res;
}
}複製代碼
最後執行以下代碼即運行前面的ConsumerTest類進行consumer通過netty rpc調用provider的sayHelloWorld方法進行測試:
public class ConsumerTest {
public static void main(String[] args) {
IHelloWorld helloWorld = (IHelloWorld)new RpcProxyFactory(IHelloWorld.class).getProxyInstance();
System.out.println(helloWorld.sayHelloWorld("jinyue", "hello world!"));
}
}複製代碼
最終的測試結果:
項目地址:
https://github.com/jinyue233/java-demo/tree/master/netty-rpc-demo