信息
在上一章中我们已经实现了一个基本完美的rpc框架,但是我们不能骄傲,需要不断完善。
首先我们来处理下消息传递的问题,之前是以‘-’分割字符串,只能传递一个参数。现在我们把它修改为json格式传递。
这里json选择使用阿里的fastjson,啊,真是强强联和~~
maven:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
定义传输类Message
public class Message {
/**
* 服务类名称
*/
private String klassName;
/**
* 服务别名
*/
private String alias;
/**
* 方法名称
*/
private String methodName;
/**
* 方法参数类型
*/
private Class<?>[] parameterKlassNameArrays;
/**
* 方法参数值
*/
private Object[] parameterArrays;
}
客户端设置好参数后格式化为json格式传输
JSONObject.toJSONString(message)
然后服务端接收到后解析
Message message = JSONObject.parseObject(msg, Message.class);
然后按照对应字段继续执行。
啊~破费
等等,服务端匹配服务还是写死名称的,嗯,有点low,不是,有点不合适~~
服务实现自动加载
我们可以使用注解标注需要加载的实现类,代码如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String name() default "";
}
@Service
public class DemoServiceImpl implements DemoService {
public String sayHi(String name) {
return "hi " + name;
}
}
那现在就需要扫描到这个类,并把这个类保存下来。
从根路径开始扫描
String root = URLDecoder.decode(RPCServer.class.getResource("/").getPath(), String.valueOf(Charset.defaultCharset()));
private static void scan(String root) {
System.out.println("start scan");
File file = new File(root);
allFiles(file, root);
loadImpl();
}
private static void allFiles(File file, String root) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files == null)
return;
for (File f : files) {
if (f.isDirectory())
allFiles(f, root);
else {
String path = f.getAbsolutePath();
Context.INSTANCE.addFile(handlePathToClass(path, root));
}
}
}
}
private static void allFiles(File file, String root) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files == null)
return;
for (File f : files) {
if (f.isDirectory())
allFiles(f, root);
else {
String path = f.getAbsolutePath();
Context.INSTANCE.addFile(handlePathToClass(path, root));
}
}
}
}
整体思路就是从根路径开始,如果碰到目录则搜索目录下文件,然后把所有的文件路径记录下来。
handlePathToClass是用来处理路径为类全限定名
private static String handlePathToClass(String path, String root) {
path = path.substring(root.length());
path = path.replace('/', '.');
return path.substring(0, path.length() - ".class".length());
}
这里的Context是我们创建的上下文,用于记录扫描信息(代码在git上看吧,不贴了)。
扫描完所有类之后,我们对扫描结果进行处理,将其中标注了servic注解的类放到Context中。
到这里服务端的东西就差不多了。再来看看客户端
动态代理
目前客户端使用一个静态代理来代理接口,静态的缺点很明显,就是。。。不高端,作为一个高大上的框架,怎么能用静态的,必须动态的。
使用Java自带的动态代理实现目标接口的代理类,在代理类中实现远程调用服务端服务逻辑,然后神不知鬼不觉的把结果返回给客户端。
主要代码如下
public class RemoteProxy<T> implements InvocationHandler {
private Class<T> klass;
private String alias;
public RemoteProxy(Class<T> klass, String alias) {
this.klass = klass;
this.alias = alias;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Message message = new Message();
message.setKlassName(klass.getName());
message.setAlias(alias);
message.setMethodName(method.getName());
message.setParameterKlassNameArrays(method.getParameterTypes());
message.setParameterArrays(args);
return RPCClient.sendMsg(JSONObject.toJSONString(message));
}
}
public class ServiceFactory {
public static <T> T createService(Class<T> klass, String alias) {
if (klass == null)
return null;
RemoteProxy<T> rp = new RemoteProxy<T>(klass, alias);
Object subject = Proxy.newProxyInstance(rp.getClass().getClassLoader(), new Class[]{klass}, rp);
return (T) subject;
}
}
然后客户端就可以非常简单的远程调用了
DemoService service = ServiceFactory.createService(DemoService.class, "multi");
System.out.println(service.sayHi("haha"));
一个服务多个实现
这种需求还是比较常见的,客户端指定需要使用的服务别名就可以使用不同的服务。在我们的Service注解中有name字段,该字段就标注了该服务的别名。
@Service(name = "multi")
public class MultiDemoServiceImpl implements DemoService {
@Override
public String sayHi(String name) {
return "multi " + name;
}
}
然后在扫描类之后的加载服务阶段会根据name字段作为查找类的key的一部分。
Key key = new Key(c.getName());
Service service = (Service) klass.getDeclaredAnnotation(Service.class);
if (StringUtil.isNotEmpty(service.name())) {
key.setAlias(service.name());
}
Context.INSTANCE.addServiceImpl(key, path);
然后客户端只需要在调用的时候指定alias就好了。
鼓掌~.~