如何通过协议id找到(调用)方法入口

如果前端传来一个请求,服务端如何识别这个请求,找到对应的要执行的方法?

一直觉得这个效果很神奇,自己动手实现一下。

 

一、问题的简单分析

(1)servlet

最开始学习java web的时候,我用servlet写过一些小项目。一个servlet类extends HttpServlet父类后,可以实现doPost/doGet方法,接收http请求。画风大概是这个样子的:

Server.java

如果用post/get的方式访问/server这个路径,就会自动进到server类的doPost/doGet方法,执行相应的操作。

按照上面的规则,如果要实现登录接口,我就配一个/login路径,写一个login类。如果要实现注册接口,我就要配一个/register路径,写一个register类。

那么问题来了:如果我有一大堆请求,岂不是要写一大堆servlet类,配上一大堆路径?我的项目还要不要管理了…

于是我们想到了一个办法,只配一个/server路径,写一个server类。至于请求想调用哪个方法,由请求带进来的method参数来决定。

Server.java

如果请求是post方法并且method为login,就会匹配上doPost方法中的

执行登录方法。

看上去是解决了,但是问题又来了:如果写的api越来越多,把逻辑全写在这个Server.java里面,这个类岂不是很臃肿?

你可能会说:那我自己写几个service类总行了吧。Server.java只留下if else和方法调用,具体逻辑全搬到service类里面,不就得了吗。

但是,要是api很多很多,Server.java里面可能会堆积一大堆if else,这个类还不是一样臃肿吗?

你可能会说:你嫌Server.java臃肿是吧,我写多几个servlet类不就行了!我要写一个Server2.java,不够再加Server3.java、Server4.java…

不得不说这个思路确实不错,一定程度上能解决类过于臃肿的问题。

但是问题又来了:servlet的入口是可以自己定义的,一个不够我们可以写多几个。但是,如果使用Netty,入口类只有那一个Handler,我们怎么办?

(2)Netty中的Handler

拿这篇文章的一个Handler做个例子:

使用Netty实现简单的http服务器

HttpHelloWorldServerHandler.java

我们只能把逻辑都写在channelRead方法中。如果要处理的方法多了,这个HttpHelloWorldServerHandler类又会变得臃肿。但是这次,我们无法写多几个Handler帮助分担责任,上面的方法行不通了!

我们再想想,HttpHelloWorldServerHandler类的作用是什么?个人认为,比起处理请求,这个类更像是一个请求入口。对于请求的处理,我们应该写在另外一个Service层中,而不是写在Handler层中。换言之,Handler层中不应该出现具体业务的逻辑代码。

到这里其实就是设计模式的范畴了,我将把重点放在如何解决上面的问题。

(顺带一提,写代码要注意SOLID原则。个人认为,设计模式就是该原则的参考实现,用于帮助我们写出合理的代码。具体可以参考:https://www.cnblogs.com/lanxuezaipiao/archive/2013/06/09/3128665.html

二、我的解决方案

(1)初步解决方案

用注解配合反射的方式,分离入口类的Service层的逻辑实现。

首先写上一个注解,用来修饰要调用的方法,方便反射找到。

FindMethod.java

写好要调用的方法,加上注解,约定方法的id(Service层)。

Methods.java

接下来要在入口类中写好反射,通过id对方法进行调用。我用一个main方法模拟一下入口类:

Main.java

现在id为1,即调用注解id为1的testGetMethod1方法,输出结果。运行结果为:

(2)拓展Service类

到了这里,你可能会问:这算什么优化嘛!如果要处理的方法多了,这个Methods类还不是会变得臃肿吗?

个人认为:写多一个Methods类不就好了吗…在反射的部分稍微改动一下就行了。

Main.java

要注意的是:找到相应的方法就可以停止遍历,及时break结束循环。

(3)快速找到id对应的方法

上面的程序有个问题,如果Service类多了,可能要用反射遍历一大堆Service类。

举个例子:一共有10个service类(service1到service10),我要调用的方法在service10中,但是反射只能一个个遍历,从service1找到service10,最后才找到,效率太低了。

除此之外,反射的速度本来就比直接调用要慢很多,如果要遍历的方法很多,就会慢上加慢,速度将令人难以接受!

(至于反射为什么慢,可以参考我以前的总结《java反射为什么慢?》)

java反射为什么慢?

其实这个问题特别容易解决。我想要通过调用的id找到该id所在的类,映射关系为:调用方法的id -> 方法所在的全类名。

那我加个缓存不就好了吗?只要访问过一次,下次访问速度就会显著变快。写个全局的ConcurrentHashMap应该就能解决问题了。

1.错误示范

这样写是不行的,Handler中的ConcurrentHashMap不是全局对象,只在当前线程内(体现为每条链接都有独立的缓存,没有缓存到一起)。还是要老老实实写个单例模式。

2.把缓存写成单例模式

Cache.java

再使用QueryStringDecoder解析一下请求的参数内容:

HttpHelloWorldServerHandler.java

这样我们就实现了一个全局的缓存。

这仅仅是一个示例,自行替换为:方法的id -> 方法所在的全类名即可。可以通过id快速找到方法所在的全类名,加快调用速度。

3.技巧

写一个类专门存放id的静态变量:

MsgHead.java

凡是涉及id的地方都替换为MAIMAIMAI_ACTIVITY之类的静态变量。在debug时,可以直接ctrl + alt + h这个静态变量,找到方法id相关的所有方法。

三、总结

算是java反射的小应用吧,属于技巧性的问题。

发表评论

电子邮件地址不会被公开。 必填项已用*标注