如何將你的服務優雅的暴露出去

本文已受權公衆號「玉剛說」獨家發佈java

這裏的服務指的是接口API,在代碼解耦中,有一種很是重要的方法就是「面向接口編程」,面向接口編程使得協做的模塊之間只須要關注接口API,而無需關注API的具體實現。一套好的面向接口編程架構應該至少包含兩個方面:簡潔通用的接口定義,以及無跡可尋的接口實現。本文介紹的是基於動態代理實現的服務框架,做用場景能夠是APP模塊化開發或者SDK開發。編程

先從動態代理提及

Java的代理模式能夠分紅靜態代理和動態代理。緩存

靜態代理模式很簡單,它有三部分組成:接口、委託類、代理類。代理類直接持有委託類的實例,代理類實現了接口裏面的方法,沒有方法的執行內部直接經過調用委託類實例對應的方法執行。安全

動態代理比靜態代理來的更加方便些,固然其本質也是同樣的。看過動態代理源碼以後能夠簡單的總結一下:動態代理在運行時生成代理類的字節碼,從字節碼中建立出代理類的實例,對其全部的方法調用都轉發到 invocation handler 的 invoke 方法,在 invoke 方法中執行額外的邏輯。bash

下面,咱們簡單從代碼層面來回顧一下動態代理的原理。多線程

接口定義架構

public interface Interface {
    void doSomething();
}
複製代碼

動態代理調用app

  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 了,實際狀況下是不夠好的
相關文章
相關標籤/搜索