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就好了。

鼓掌~.~

源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值