如何将你的服务优雅的暴露出去

这里的服务指的是接口API,在代码解耦中,有一种非常重要的方法就是“面向接口编程”,面向接口编程使得协作的模块之间只需要关注接口API,而无需关注API的具体实现。一套好的面向接口编程架构应该至少包含两个方面:简洁通用的接口定义,以及无迹可寻的接口实现。本文介绍的是基于动态代理实现的服务框架,作用场景可以是APP模块化开发或者SDK开发。

先从动态代理说起

Java的代理模式可以分成静态代理和动态代理。

静态代理模式很简单,它有三部分组成:接口、委托类、代理类。代理类直接持有委托类的实例,代理类实现了接口里面的方法,没有方法的执行内部直接通过调用委托类实例对应的方法执行。

动态代理比静态代理来的更加方便些,当然其本质也是一样的。看过动态代理源码之后可以简单的总结一下:动态代理在运行时生成代理类的字节码,从字节码中创建出代理类的实例,对其所有的方法调用都转发到 invocation handler 的 invoke 方法,在 invoke 方法中执行额外的逻辑。

下面,我们简单从代码层面来回顾一下动态代理的原理。

接口定义

public interface Interface {
    void doSomething();
}

动态代理调用

  1. 动态生成代理类
Interface anInterface = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class},
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
});
  1. 通过代理执行:当我们执行 anInterface.doSomething() 方法时,会自动 invoke 方法,在 invoke 方法中执行真正的逻辑。

动态代理生成的字节码

跟踪动态代理源码,可以很清晰的看到其动态创建 proxy 类的过程。这里我简单做了一个实验,我将动态生成的字节码保存到文件中,再反编译读取出来。

// 获取字节码再保存到文件中
String proxyName = "$Proxy0";
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
        new Class[]{Interface.class});
saveToFile(proxyClassFile);

// 通过 JD_GUI 工具读取 $Proxy0.class 

public final class $Proxy0 extends Proxy implements Interface
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void doSomething()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.xud.proxy.principle.Interface").getMethod("doSomething", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

从生成的代理类可以非常清晰的看到,对代理类中 doSomeThing() 方法的调用最终会回调 InvocationHandler.invoke() 方法。

具体实现

阅读过 Retrofit 源码的同学一定对它面向接口的服务使用方式赞叹不已,Retrofit 也是基于动态代理来实现如此优雅的方式的,本文介绍的方式跟 Retrofit 的方式也有类似之处。不过, Retrofit 的动态代理是没有委托类的,我们只负责定义接口。

假设目前有个接口叫做 AuthService, 我们先来看看这个接口的使用方式,从而对这种方式有个初步的认识。

接口定义:

public interface AuthService extends Service {
    AbortableFuture<String> login(LoginInfo var1);
    void logout();
}

接口的使用:

AuthService authService = ServiceClient.getService(AuthService.class);
authService.login(loginInfo).setCallback(new RequestCallback() {
    @Override
    public void onSuccess(Object var1) {

    }

    @Override
    public void onFailed(int var1) {

    }

    @Override
    public void onException(Throwable var1) {

    }
});

简洁通用的接口定义

接口通信需要支持三种模式:

  1. 直接返回数据
  2. 异步回调返回onSuccess onFail onException
  3. 异步回调返回,同时支持调用后中断服务

针对这三种情况,定义了以下几个接口,用以处理接口数据返回:

RequestCallback
数据异步回调接口

public interface RequestCallback<T> {
    void onSuccess(T var1);

    void onFailed(int var1);

    void onException(Throwable var1);
}

RequestCallbackWrapper
简化后的数据回调接口

public abstract class RequestCallbackWrapper<T> implements RequestCallback<T> {
    public RequestCallbackWrapper() {
    }

    public abstract void onResult(int var1, T var2, Throwable var3);

    public void onSuccess(T var1) {
        this.onResult(200, var1, (Throwable)null);
    }

    public void onFailed(int var1) {
        this.onResult(var1, (Object)null, (Throwable)null);
    }

    public void onException(Throwable var1) {
        this.onResult(1000, (Object)null, var1);
    }
}

InvocationFuture
正常情况下的回调接口封装

public interface InvocationFuture<T> {
    void setCallback(RequestCallback<T> var1);
}

AbortableFuture
可中断的回调接口封装

public interface AbortableFuture<T> extends InvocationFuture {
    boolean abort();
}

有了这几个通用接口时,我们在定义具体的服务API接口就非常方便了,比如上面已经写过的 AuthService 接口。

封装服务的获取入口

ServiceClient

在获取服务的时候,我希望有个统一的口子,这个口子就是类 ServiceClient. 这个类中有个核心的方法:getService(), 这是获取服务接口的唯一入口。

public class ServiceClient {

    private static ServiceCache serviceCache = new ServiceCache();

    private static ServiceMethodExcuter excuter = new ServiceMethodExcuter();

    public static <T> T getService(Class<T> cls) {
        return serviceCache.getService(cls);
    }

    public static Object excute(ServiceMethodContainer container) {
        return excuter.invoke(container);
    }
}

ServiceCache

ServiceCache主要是对服务进行缓存,避免每次获取时候重复性创建。

public class ServiceCache {

    private final Map<Class<?>, Object> caches = new HashMap();

    public ServiceCache() {
    }

    public final <T> T getService(Class<T> cls) {
        if (!cls.isInterface()) {
            throw new IllegalArgumentException("only accept interface: " + cls);
        } else {
            synchronized (this.caches) {
                T hitProxy;
                if ((hitProxy = (T) this.caches.get(cls)) == null) {
                    hitProxy = (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, new ServiceInvovationHandler());
                    this.caches.put(cls, hitProxy);
                }
                return hitProxy;
            }
        }
    }
}

在这个类中,我们看到了动态代理的影子,是的,关键就在于它。所以,这个 cache 缓存的是接口服务的代理类。

ServiceInvovationHandler

接口API方法的调用最终会回调 ServiceInvovationHandler.invoke() 方法,我在这个方法中去具体执行接口方法调用,这个类中的 ServiceMethodContainer 是对 method 和 args 的封装。

public class ServiceInvovationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy: " + proxy.getClass() + ", method:" + method + ", args: " + args);
        ServiceMethodContainer methodContainer = new ServiceMethodContainer(method, args);
        return ServiceClient.excute(methodContainer);
    }
}

public class ServiceMethodContainer {
    public Method method;
    public Object[] args;

    public ServiceMethodContainer() {
    }

    public ServiceMethodContainer(Method method, Object[] args) {
        this.method = method;
        this.args = args;
    }

    public String getMethodDeclarClassName() {
        return method.getDeclaringClass().getSimpleName();
    }

    public String getMethodName() {
        return method.getName();
    }
}

ServiceMethodExcuter

在 invoke 方法中,是通过调用 ServiceClient.excute(methodContainer) 来执行具体方法的。为此,有一个类 ServiceMethodExcuter 专门用来做这个事情了。这个类中,也可以看到服务接口的具体委托类的实现,即 代码中的 AuthServiceImpl。所以,本文这种动态代理是一种标准的代理,它有接口、代理类、委托类,这个跟 Retrofit 的设计是不同的,下面来具体看代码:

public class ServiceMethodExcuter {

    private final Map<String, A> serviceMap = new HashMap<>();

    ServiceMethodExcuter() {
        System.out.println("Register Service Start");
        this.addService(AuthService.class, AuthServiceImpl.class);
        this.addService(UserService.class, UserServiceImpl.class);
        System.out.println("Register Service End");
    }

    public final Object invoke(ServiceMethodContainer container) {
        ServiceMethodExcuter.A a;
        if ((a = this.serviceMap.get(container.getMethodDeclarClassName())) == null) {
            return null;
        } else {
            try {
                Object object = a.invoke(container);
                return object;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    private void addService(Class<?> interfaceCls, Class<? extends Service> implCls) {
        this.serviceMap.put(interfaceCls.getSimpleName(), new ServiceMethodExcuter.A(interfaceCls, implCls));
    }

    private static class A {
        private final Map<String, Method> methodMap = new HashMap<>();
        private Service realService;

        public A (Class<?> interfaceCls, Class<? extends Service> implCls) {
            Method[] methods;
            int length = (methods = interfaceCls.getDeclaredMethods()).length;
            for (int i = 0; i < length; i++) {
                Method method = methods[i];
                this.methodMap.put(method.getName(), method);
            }
            try {
                this.realService = (Service) implCls.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public final Object invoke(ServiceMethodContainer container) throws Exception {
            return this.methodMap.get(container.getMethodName()).invoke(this.realService, container.args);
        }
    }
}

这个类也是这个架构中最关键的一个类,当然,它也很简洁。这个类核心的只有三点:

  1. 对接口和接口委托类的 cache,cache 的 key 是接口的 className,value 则是封装的委托类;
  2. 通过 method.getDeclaringClass().getSimpleName() 可以拿到 method 所对应的接口类,从而找到它的委托类;
  3. 通过 newInstance 动态创建委托类,并对其 method 进行 cache, 最终执行的本质还是 method.invoke()

还需要考虑的问题

以上,就把基本的原理介绍清楚了,使用时,就直接通过 ServiceClient.getService() 来获取服务。

由于篇幅问题,本文只对原理性的东西进行展示,并没有把更多细节的处理展示出来。所以以下这些问题是读者在实操过程中要去考虑的。

  1. ServiceClient 初始化问题,它的初始化可以放在 Application 中,它可以持有 ApplicationContext。从而维持和APP一样的生命周期;
  2. 多进程环境下 ServiceClient 的初始化需要去考虑;
  3. 多线程环境下 ServiceCache、ServiceMethodExcuter 线程安全问题,我现在是直接在 ServiceClient 中申明成 static 了,实际情况下是不够好的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容