信息
在上一章中我們已經實現了一個基本完美的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就好了。
鼓掌~.~