前言
在看完dubbo和netty的源码以后,决定实践一下,利用netty来实现一个简易的RPC服务器。
正文
/**
* 资源提供者
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Provider {
}
标记这个注解的类表示可以被扫描
@Data
public class InvokeProtocol implements Serializable {
private static final long serialVersionUID = 109558080020766645L;
private String className;
private String methodName;
private Class<?>[] parameTypes;
private Object[] values;
}
用来表示目标类的实体类,使用了lombok来简化实体类的代码数量
public class RegisterMap {
private static final ConcurrentHashMap<String ,Object> registerMap = new ConcurrentHashMap<>();
private static final List<String> CLASS_LISTS = new ArrayList<>();
创建一个类RegisterMap,来存放被扫描的类的map
private static void scannerClass(String packageName) {
URL url = RegisterMap.class.getClassLoader().getResource(
packageName.replaceAll("\\.","/"));
if (url != null) {
File dir = new File(url.getFile());
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isDirectory()) {
scannerClass(packageName+"."+file.getName());
}else {
CLASS_LISTS.add(packageName+"."+
file.getName().replace(".class","").trim());
}
}
}
}
首先是根据包名来扫描类,如果是一个目录就继续递归,如果是一个类将.class的后缀给去掉然后加入list
private static void doRegister() {
for (String className : CLASS_LISTS) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Provider.class)) {
Class<?> i = clazz.getInterfaces()[0];
registerMap.put(i.getName(),clazz.newInstance());
}
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
遍历扫描的类list,获取Provider注解并且获取接口信息放入map
public static Object getClass(String clazzName) {
return registerMap.get(clazzName);
}
public static boolean containsClass(String clazzName) {
return registerMap.containsKey(clazzName);
}
暴露两个方法,根据接口名称获取和判断是否存在
public class RegisterHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Object result = new Object();
InvokeProtocol request = (InvokeProtocol) msg;
if (RegisterMap.containsClass(request.getClassName())) {
Object clazz = RegisterMap.getClass(request.getClassName());
Method method = clazz.getClass().getMethod(request.getMethodName(),
request.getParameTypes());
result = method.invoke(clazz, request.getValues());
}
ctx.write(result);
ctx.flush();
ctx.close();
}
}
RegisterHandler重写channelRead,接受一个InvokeProtocol类型的msg,RegisterMap获取实现类信息,然后根据java底层方法,根据方法名和参数类型获取方法后invoke进行返回
public static void main(String[] args) {
new Registry(8081).start();
}
private int port;
public Registry(int port) {
this.port = port;
}
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(
new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast("decoder",
new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RegisterHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = server.bind(port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
这一段就是标准的netty使用案例,最主要的是initChannel里面添加的handler,这里添加了帧解码器和对象解码器,并且在最后将RegisterHandler给添加进去了,当一个请求进来会将对象给解码交给channelRead
最后一步就是使用代理来将接口进行包装
private static class MethodProxy implements InvocationHandler{
private final Class<?> clazz;
public MethodProxy(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}else {
return rpcInvoke(proxy,method,args);
}
}
注意invoke方法,这里主要是进入rpcInvoke才能实现核心的rpc功能
public Object rpcInvoke(Object proxy, Method method, Object[] args) {
//将调用的方法以及参数信息创建
InvokeProtocol msg = new InvokeProtocol();
msg.setClassName(this.clazz.getName());
msg.setMethodName(method.getName());
msg.setValues(args);
msg.setParameTypes(method.getParameterTypes());
final ProxyHandler handler =new ProxyHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("handler", handler);
}
});
ChannelFuture future = bootstrap.connect(hosts,port).sync();
future.channel().writeAndFlush(msg).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
return handler.getResponse();
}
这里是用于netty的客户端,将信息包装成InvokeProtocol,发送给服务器并且接受服务器返回的内容,ProxyHandler就是用于接受服务器返回的内容,实现很简单
@Getter
public class ProxyHandler extends ChannelInboundHandlerAdapter {
private Object response;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.response = msg;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("发生异常");
}
}
@SuppressWarnings("unchecked")
public static <T> T create(Class<?> clazz) {
MethodProxy proxy = new MethodProxy(clazz);
Class<?>[] interfaces = clazz.isInterface() ? new Class[]{clazz}:clazz.getInterfaces();
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),interfaces,proxy);
}
RpcProxy的静态方法,将一个接口包装成MethodProxy,MethodProxy的invoke方法会给服务器发送一次TCP请求,服务器则会将真实的inoke对象给返回
rpc功能就实现完成了,测试一下
public interface HelloService {
String hello(String name);
}
@Provider
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "Hello "+name;
}
}
建立一个测试接口和实现类
public static void main(String[] args) {
HelloService helloService = RpcProxy.create(HelloService.class);
System.out.println(helloService.hello("123"));
}