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

這裡的服務指的是介面API,在程式碼解耦中,有一種非常重要的方法就是“面向介面程式設計”,面向介面程式設計使得協作的模組之間只需要關注介面API,而無需關注API的具體實現。一套好的面向介面程式設計架構應該至少包含兩個方面:簡潔通用的介面定義,以及無跡可尋的介面實現。本文介紹的是基於動態代理實現的服務框架,作用場景可以是APP模組化開發或者SDK開發。
先從動態代理說起
Java的代理模式可以分成靜態代理和動態代理。
靜態代理模式很簡單,它有三部分組成:介面、委託類、代理類。代理類直接持有委託類的例項,代理類實現了接口裡面的方法,沒有方法的執行內部直接通過呼叫委託類例項對應的方法執行。
動態代理比靜態代理來的更加方便些,當然其本質也是一樣的。看過動態代理原始碼之後可以簡單的總結一下:動態代理在執行時生成代理類的位元組碼,從位元組碼中創建出代理類的例項,對其所有的方法呼叫都轉發到 invocation handler 的 invoke 方法,在 invoke 方法中執行額外的邏輯。
下面,我們簡單從程式碼層面來回顧一下動態代理的原理。
介面定義
public interface Interface { void doSomething(); }
動態代理呼叫
- 動態生成代理類
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; } });
- 通過代理執行:當我們執行 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) { } });
簡潔通用的介面定義
介面通訊需要支援三種模式:
- 直接返回資料
- 非同步回撥返回onSuccess onFail onException
- 非同步回撥返回,同時支援呼叫後中斷服務
針對這三種情況,定義了以下幾個介面,用以處理介面資料返回:
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); } } }
這個類也是這個架構中最關鍵的一個類,當然,它也很簡潔。這個類核心的只有三點:
- 對介面和介面委託類的 cache,cache 的 key 是介面的 className,value 則是封裝的委託類;
- 通過 method.getDeclaringClass().getSimpleName() 可以拿到 method 所對應的介面類,從而找到它的委託類;
- 通過 newInstance 動態建立委託類,並對其 method 進行 cache, 最終執行的本質還是 method.invoke()
還需要考慮的問題
以上,就把基本的原理介紹清楚了,使用時,就直接通過 ServiceClient.getService() 來獲取服務。
由於篇幅問題,本文只對原理性的東西進行展示,並沒有把更多細節的處理展示出來。所以以下這些問題是讀者在實操過程中要去考慮的。
- ServiceClient 初始化問題,它的初始化可以放在 Application 中,它可以持有 ApplicationContext。從而維持和APP一樣的生命週期;
- 多程序環境下 ServiceClient 的初始化需要去考慮;
- 多執行緒環境下 ServiceCache、ServiceMethodExcuter 執行緒安全問題,我現在是直接在 ServiceClient 中申明成 static 了,實際情況下是不夠好的