前言:代码以上传点击跳转
1.1 相信大家使用过RPC框架,例如(dubbo等等)和netty,我这里就不再多说了,基本项目架构如下
1.2 基于上面,netty也是一样,不过是consumer是netty的客户端,provider是netty的服务端,基本如图所示
1.3 即一共三个项目
该项目需要完成的功能,interface项目中定义了一个接口(BookService),其实现类在provider项目中,
现在consumer项目使用interface项目中的接口(BookService)调用其方法(findBookById),
得到其方法的结果
2.1
服务提供者(也就是netty服务端,后面我统称服务提供者)
服务消费者(也就是netty客户端,后面我统称服务消费者)
服务消费者传递一个数据对象给服务端,再从服务端得到返回的数据即可。那么重点就是
2.2
解决如上两个问题便完成了简易的RPC
先说第一个:
客户端传输给服务端的数据对象是什么?
- 那个类
- 类中的什么方法
- 方法的参数类型(一个类又有许多的方法)
- 方法的参数值
再说第二个:
服务端又如何才能根据数据对象进行本地方法的调用?
直接通过反射调用本地的实现类即可,再将得到的数据返回
该项目中包括consumer和provider都需要的东西
我这里就定义一个数据返回对象(Book),set/get等等省略
public class Book implements Serializable {
private String id;
private String name;
}
既然消费者需要通过反射,那么传输对象应是如下
public class ClassInfo implements Serializable {
/**
* 调用的具体类的类名全路径(即包名加类名 service.BookService)
*/
private String fullPath;
/**
* 类中的那个方法
*/
private String methodName;
/**
* 方法中的参数类型
*/
private Class []paramType;
/**
* 方法中的参数值
*/
private Object []paramValue;
public interface BookService {
/**
* 根据Id查找指定的图书
* @param bookId
* @return
*/
Book findBookById(String bookId);
}
该项目中包括
public class BookServiceImpl implements BookService {
@Override
public Book findBookById(String bookId) {
// 如果bookId为1 就返回图书对象
if("1".equals(bookId)){
return new Book("1","骆驼祥子");
}
// 其它返归空
return null;
}
}
因代码以上传github,就不在在这里写了
其中需要用到reflections的jar包实现,根据结果类型,找到其下所有实现类
/**
* 得到某接口下某个实现类的全路径
* @param classInfo
* @return 类的全路径(包名加类名)如:service.impl.BookServiceImpl
* @throws Exception
*/
private String getImplClassName(ClassInfo classInfo) throws Exception{
// 拿到BookService类
Class superClass=Class.forName(classInfo.getFullPath());
int indexOf = classInfo.getFullPath().lastIndexOf(".");
// 指定从那个包下开始搜索(我这里是因为service与service.impl都在service下,所以我直接截取接口的包名即可)
Reflections reflections = new Reflections(classInfo.getFullPath().substring(0,indexOf-1));
//得到某接口下的所有实现类
Set<Class> ImplClassSet=reflections.getSubTypesOf(superClass);
if(ImplClassSet.size()==0){
System.out.println("未找到实现类");
return null;
}else if(ImplClassSet.size()>1){
System.out.println("找到多个实现类,未明确使用哪一个");
return null;
}else {
//把集合转换为数组
Class[] classes=ImplClassSet.toArray(new Class[0]);
//得到实现类的名字
return classes[0].getName();
}
}
测试如下
/**
* 测试上面结果
*/
public static void testGetImplClassName(){
ClassInfo classInfo = new ClassInfo("service.BookService","findBookById",
new Class[]{String.class},new Object[]{"1"});
String implClassName = getImplClassName(classInfo);
// service.impl.BookServiceImpl
System.out.println(implClassName);
}
接下来就可以根据反射调用实现类的具体方法了
/**
* 通过反射调用其方法并返回
* @param classInfo
* @return
*/
private Object invokeAndReturn(ClassInfo classInfo) {
try {
String implClassName = getImplClassName(classInfo);
Class<?> clazz = Class.forName(implClassName);
Object newInstance = clazz.newInstance();
Method method = clazz.getMethod(classInfo.getMethodName(), classInfo.getParamType());
return method.invoke(newInstance,classInfo.getParamValue());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
该项目中包括
根据netty中的ChannelFuture得到通道,后发送数据
ChannelFuture future = b.connect("127.0.0.1", 9999).sync();
// 将需要调用的方法数据发到服务端
future.channel().writeAndFlush(classInfo).sync();
在客户端收到服务端发送消息处,返回数据结果即可
public class ClientHandler extends ChannelInboundHandlerAdapter {
private Object response;
public Object getResponse() {
return response;
}
/**
* 读取服务器端返回的数据(远程调用的结果)
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
response = msg;
ctx.close();
}
}
数据的返送使用反射中的代码
public class TestRPC {
public static void main(String[] args) {
// 通过代理获得接口对象
BookService bookService = (BookService)RpcProxy.create(BookService.class);
// 调用接口其方法,就会激活Proxy的invoke方法,也就打开了netty的client,发送数据
Book book = bookService.findBookById("1");
System.out.println(book);
}
}
代码地址:点击跳转