在实现的RPC框架中,如果使用传统的方式来进行通信的话,那么服务启动端需要先手动创建一个提供服务的类的实例
HelloService helloService = new HelloServiceImpl();
虽然在实现过程中只有这一个服务,好像并不会带来太大的问题,但如果有一堆服务对象的话,那有几个就得手动创建几个,这样肯定是不现实的,所以给框架加一个自动注册服务的方式是很有必要的。采用注解的方式来解决这个问题则是个很好的选择。
服务端肯定要有一个注解,当我们启动服务端后,原本是创建一个服务类的实例,需要将这一步省去,这个注解可以视为扫描注解,实现了这个注解之后,我们就知道了在这个位置需要进行扫描。同时,提供服务的类也可以有一个注解,表明它提供了一个服务。
我们就可以设计如下的流程:获取服务启动端所在的类名→判断该类下有无扫描注解→获取注解的值如果值与我们定义的相同,说明是我们需要操作的类→获取它所在的包的名称→扫描包下所有类,逐个判断是否有服务注解→获取注解类实现的接口→得到接口信息,注册服务
//服务注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
public String name() default "";
}
//扫描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceScan {
public String value() default "";
}
服务端
@ServiceScan
public class NettyServerTest {
public static void main(String[] args) {
NettyServer server = new NettyServer("127.0.0.1", 9999, CommonSerializer.KRYO_SERIALIZER);
server.start();
}
}
这又会出现一个问题,我们知道在服务端启动时需要进行扫描,但是怎么获得这个服务端的类名呢?
我们是通过调用main方法来启动服务的,当一个方法被调用时,实际上是线程调用了它私有的虚拟机栈,将方法作为一个栈帧将其压入虚拟机栈中,一个方法从开始执行到执行完毕对应的就是它的入栈和出栈过程,所以main方法肯定是在栈底的,我们可以通过new Throwable().getStackTrace()方法得到栈帧信息。
getStackTrace()返回一个表示该线程堆栈转储的堆栈跟踪元素数组。如果该线程尚未启动或已经终止,则该方法将返回一个零长度数组。如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该序列中最新的方法调用。最后一个元素代表堆栈底,是该序列中最旧的方法调用。
public static String getStackTrace() {
StackTraceElement[] stack = new Throwable().getStackTrace();
return stack[stack.length - 1].getClassName();
}
有了基本思路以后,实现起来就没那么困难了