简易RPC注解化

Posted 天思悦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易RPC注解化相关的知识,希望对你有一定的参考价值。

order-service 注解化

1.定义一个注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME) // 在运行时保留@Componentpublic @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.服务发布

@Componentpublic class InitialMediator implements BeanPostProcessor {
@Override    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服务启动类。

@Componentpublic class SocketServerInit implements ApplicationListener<ContextRefreshedEvent> {
    private final ExecutorService threadPool = Executors.newCachedThreadPool();
@Override    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) // 在运行时保留@Componentpublic @interface MyReference {}

1.处理注解标注的方法

@Componentpublic class ReferenceInvokeProxy implements BeanPostProcessor {
@Autowired MyInvocationHandler invocationHandler;
@Override    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

@Componentpublic class MyInvocationHandler implements InvocationHandler {
@Value("${rpc.host}") private String host; @Value("${rpc.port}") private int port;
@Override    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=localhostrpc.port=8088server.port=8082

这样就完成客户端的注解化了。建立一个测试类进行测试。

@RestControllerpublic class TestController {
@MyReference IOrderService iOrderService;
@GetMapping("/test") public String test() { return iOrderService.queryOrderList(); }}

先启动服务端,然后启动客户端。通过localhost:8082/test进行调用就可以看到效果了。

1.测试结果

服务端日志:



以上是关于简易RPC注解化的主要内容,如果未能解决你的问题,请参考以下文章

自行实现一个简易RPC框架

Redis 简易消息队列

手写简易RPC

Dubbo源码学习系列 手写简易RPC

简易RPC

简易版本vue的实现和注解