dubbo學習(三)--優化調用功能

信息

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

鼓掌~.~

源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章