简易RPC注解化
Posted 天思悦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易RPC注解化相关的知识,希望对你有一定的参考价值。
order-service 注解化
1.定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) // 在运行时保留
@Component
public @interface MyRemoteService {
}
该注解是为了标记要使用的类,也就是哪个类需要添加到RPC中进行调用。
1.创建储存实例的容器Mediator
public class Mediator {
// 用来存储发布服务的实例 服务调用的路由
public static Map<String, BeanMethod> map = new ConcurrentHashMap<>();
private volatile static Mediator instance;
private Mediator() {}
public static Mediator getInstance() {
if (instance == null) {
synchronized (Mediator.class) {
if (instance == null) {
instance = new Mediator();
}
}
}
return instance;
}
public Object processor(RpcRequest request) {
String key = request.getClassName() + "." + request.getMethodName();
BeanMethod beanMethod = map.get(key);
if (beanMethod == null) {
return null;
}
Object bean = beanMethod.getBean();
Method method = beanMethod.getMethod();
try {
return method.invoke(bean, request.getArgs());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
目的是为了在spring初始化的时候把实力化的类保存在map里面,后面使用的时候可以直接拿到。
1.初始化处理
在spring初始化的时候,会调用BeanPostProcessor接口,这个接口里面有两个方法postProcessBeforeInitialization和postProcessAfterInitialization,功能是在注解前后需要进行的操作。
只要重写这两个方法,就可以在注解使用之前和之后进行需要的操作了。在服务端是在注解之后进行使用,获取使用了注解的接口,然后发布到socket中让客户端进行使用。
1.服务发布
public class InitialMediator implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 对加了@MyRemoteService注解的类进行处理,远程发布
if (bean.getClass().isAnnotationPresent(MyRemoteService.class)) {
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method:methods) {
// 以接口的全路径和方法名为路径
String key = bean.getClass().getInterfaces()[0].getName() + "." + method.getName();
BeanMethod beanMethod = new BeanMethod();
beanMethod.setBean(bean);
beanMethod.setMethod(method);
Mediator.map.put(key, beanMethod);
}
}
return null;
}
}
注意添加@Component注解,不然spring加载不到类的信息。
1.建立socket
在spring加载完成后,就需要启动socket服务类类。在spring容器启动完成后已发布该事件,监听该事件就可以处理socket服务启动类。
public class SocketServerInit implements ApplicationListener<ContextRefreshedEvent> {
private final ExecutorService threadPool = Executors.newCachedThreadPool();
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
ServerSocket serverSocket = null;
try {
// 监听端口信息
serverSocket = new ServerSocket(8088);
while (true) {
Socket socket = serverSocket.accept(); // 不停的去监听请求信息
threadPool.execute(new ProcessHandle(socket));
}
}catch (Exception e){
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这样,服务端注解就改造完成了。接下来就是改造客户端了。
user-service注解化
1.定义注解
同样的操作,先定义一个注解,用来标记要操作的类
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) // 在运行时保留
@Component
public @interface MyReference {
}
1.处理注解标注的方法
public class ReferenceInvokeProxy implements BeanPostProcessor {
MyInvocationHandler invocationHandler;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field:fields) {
if (field.isAnnotationPresent(MyReference.class)) {
field.setAccessible(true);
// 存在定义的注解,设置为一个代理的值
Object proxy = Proxy.newProxyInstance(field.getType().getClassLoader(), new Class<?>[]{field.getType()},
invocationHandler);
try {
field.set(bean, proxy);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
}
由于客户端是使用注解的,所以需要在注解调用之前进行类的反射实例化。实例化数据通过socket进行接受,也就是invocationHandler里面进行处理。
1.MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler {
"${rpc.host}") (
private String host;
"${rpc.port}") (
private int port;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 建立远程连接
RpcNetTransport rpcNetTransport = new RpcNetTransport(host, port);
rpcNetTransport.createSocket();
// 传递数据 传递调用的接口、类以及参数
RpcRequest request = new RpcRequest();
request.setArgs(args);
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setArgType(method.getParameterTypes());
Object send = rpcNetTransport.send(request);
return send;
}
}
rpc.host=localhost
rpc.port=8088
server.port=8082
这样就完成客户端的注解化了。建立一个测试类进行测试。
@RestController
public class TestController {
@MyReference
IOrderService iOrderService;
@GetMapping("/test")
public String test() {
return iOrderService.queryOrderList();
}
}
先启动服务端,然后启动客户端。通过localhost:8082/test进行调用就可以看到效果了。
1.测试结果
服务端日志:
以上是关于简易RPC注解化的主要内容,如果未能解决你的问题,请参考以下文章