Dubbo是一款高性能的Java RPC框架,是服务治理的重量级中间件。Dubbo采用dubbo:service描述服务提供者,dubbo:reference描述服务消费者,其共同必填属性为interface,即Java接口。Dubbo正是采用接口来作为服务提供者和消费者之间的“共同语言”的。
在移动网络中,Android作为服务消费者,一般通过HTTP网关调用后端服务。在国内的大型互联网公司中,Java后端大多采用了Dubbo及其变种作为服务治理、服务水平扩展的解决方案。因此,HTTP网关通常需要Android的网络请求中提供调用的服务名称、服务方法、服务版本、服务分组等信息,然后通过这些信息反射调用Java后端提供的RPC服务,实现从HTTP协议到RPC协议的转换。
关于Android访问网关请求,其分层结构可参考《基于Retrofit+RxJava的Android分层网络请求框架》。
那么,Android端能否以dubbo:reference化的方式申明需要访问的网络服务呢?如何这样,将极大提高Android开发人员和Java后端开发之间的沟通效率,以及Android端的代码效率。
首先,自定义服务的消费者注解Reference,通过该注解标记某个服务。
其次,通过接口定义某个服务消费(如果可以直接引入后端接口,此步骤可省略),在注解中指明该服务对应的后端服务接口名、服务版本、服务分组等信息;
@Reference(service = "com.yhthu.java.ClassTestService", group = "yhthu", version = "v_test_0.1") public interface ClassTestService { // 实例方法 Response echo(String pin); }这样就完成了服务的申明,接下来的问题是如何实现服务的调用呢?上述申明的服务接口如何定义实现呢?这里就涉及依赖注入和动态代理。我们先定义一个标记注解@Service,标识需要被注入实现的服务申明。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { } // 在需要使用服务的地方(比如Activity中)申明需要调用的服务 @Service private ClassTestService classTestService;在调用classTestService的方法之前,需要注入该接口服务的实现,因此,该操作可以在调用组件初始化的时候进行。
// 接口与对应实现的缓存 private Map<Class<?>, Object> serviceContainer = new HashMap<>(); // 依赖注入 public void inject(Object obj) { // 1. 扫描该类中所有添加@Service注解的域 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Service.class)) { Class<?> clazz = field.getType(); if (clazz.getAnnotation(Reference.class) == null) { Log.e("ClassTestService", "接口地址未配置"); continue; } // 2. 从缓存中取出或生成接口类的实现(动态代理) Object impl = serviceContainer.get(clazz); if (impl == null) { impl = create(clazz); serviceContainer.put(clazz, impl); } // 3. 设置服务接口实现 try { field.setAccessible(true); field.set(obj, impl); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }inject方法的关键有三步:
扫描该类中所有添加@Service注解的字段,即可得到上述代码示例中的ClassTestService字段;
从缓存中取出或生成接口类的实现。由于通过接口定义了服务,并且实现不同服务的实现方式基本一致(即将服务信息发送HTTP网关),在生成实现上可选择JDK的动态代理。
设置服务接口实现,完成为接口注入实现。
private <T> T create(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 获取服务信息 Annotation reference = service.getAnnotation(Reference.class); String serviceName = ((Reference) reference).service(); String versionName = ((Reference) reference).version(); String groupName = ((Reference) reference).group(); // 2. 获取方法名 String methodName = method.getName(); // 3. 根据服务信息发起请求,返回调用结果 return Request.request(serviceName, versionName, groupName, methodName, param); } }); }在HTTP网关得到服务名称、服务方法、服务版本、服务分组等信息之后,即可实现对后端服务的反射调用。总的来讲,即可实现Android端dubbo:reference化的网络访问。
// 调用ClassTestService服务的方法 classTestService.echo("yhthu").callback(// ……);上述代码实现均为伪代码,仅说明解决方案思路。
在该案例中,综合使用了自定义注解、反射以及动态代理,是对上述理论知识的一个具体应用。