Android開發中無處不在的設計模式——動態代理模式
繼續更新設計模式系列,寫這個模式的主要原因是最近看到了動態代理的代碼。
先來回顧一下前5個模式:
- Android開發中無處不在的設計模式——單例模式
- Android開發中無處不在的設計模式——Builder模式
- Android開發中無處不在的設計模式——觀察者模式
- Android開發中無處不在的設計模式——原型模式
- Android開發中無處不在的設計模式——策略模式
動態代理模式在Java WEB中的應用簡直是隨處可見,尤其在Spring架構中大量的用到了動態代理;算是最重要的一個設計模式,也是最難理解的設計模式之一。
那麼什麼叫動態代理呢
代理類在程式運行前不存在、運行時由程式動態產生的代理方式稱為動態代理。
當前的網路請求庫多種多樣,其中Square公司的OkHttp簡直是完美的一個網路請求庫,而在其上又封裝了一層的Retrofit庫,為方便快捷的調用Restful Api提供了一種捷徑。如果你用過Retrofit,一定不會忘記有會有這麼一個過程:
首先定義一個介面,介面中定義網路請求的具體方法,在方法上通過註解配置host,header,params等資訊。
然後建立一個Retrofit對象,通過該對象產生一個你定義的介面對象。
通過介面對象調用具體的方法完成請求。
就像這樣子:
> listRepos(@Path("user") String user);}" data-snippet-id="ext.fadc3883ecfd2cd1a1ca67e15e7b1971" data-snippet-saved="false" data-csrftoken="5VgX1Wh4-W-l4cPYta7C6PsnrxDn_HUux6Fk">public interface GitHubService { @GET("users/{user}/repos") Call> listRepos(@Path("user") String user);}
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .build();GitHubService service = retrofit.create(GitHubService.class);
> repos = service.listRepos("octocat");" data-snippet-id="ext.ee6b799b6e05337f344653d3f6028237" data-snippet-saved="false" data-csrftoken="zatspdc2-6xr9Qf-RbSgSei_XQsqIqeqXvQQ">Call> repos = service.listRepos("octocat");
那麼你有沒有想過一個問題,介面是不可以直接new出來的,GitHubService介面的執行個體是如何產生的呢,retrofit.create方法內部到底做了什麼呢。沒錯,答案就是動態代理。該對象是程式運行期產生的代理對象。
動態代理雖然在Java WEB中大量的用到,但是在用戶端,由於考慮到效能的問題,所以用動態代理都會謹慎考慮,但是,一旦動態代理用的好,就會產生不一樣的效果,就比如這個Retrofit庫。下面,我們實現一個Retrofit的最最簡易的版本。過一下動態代理的原理。由於是簡易版,所以很多東西和Retrofit還是有差距的,自然也沒有Retrofit那麼方便,這點無視就好了。我們就以實現上面那個例子為例:
首先說明一點,我們的請求是非同步,所以傳回值我們使用void,增加一個回調的參數,約定最後一個參數是回調。
{ void onSuccess(Object t); void onFailed(Exception e);}" data-snippet-id="ext.a886f274abbdc51fa06b7d1abed39036" data-snippet-saved="false" data-csrftoken="notDIIJF-5EKuT6X6LWWipbRR2DHvKJcZYxM">public interface Callback { void onSuccess(Object t); void onFailed(Exception e);}
最終的介面定義會是這個樣子。
> callback); /** * 約定最後一個參數是callback */}" data-snippet-id="ext.f115dcec337f47ddaaf65522ab40a2e0" data-snippet-saved="false" data-csrftoken="hOCzSeOZ-2RNrlcsbRh9aW4trRlUk_l6C1D4">public interface GithubService { @GET("users/{user}/repos") void listRepos(@Path("user") String user,Callback> callback); /** * 約定最後一個參數是callback */}
用到了兩個註解,一個是方法註解,一個是參數註解
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface GET { String value() default "";}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.PARAMETER)public @interface Path { String value();}
Repo實體類是使用GsonFormat根據json自動產生的。
然後我們編寫Retrofit類,這個類應該是一個builder模式,裡面可以設定baseUrl,姑且忽略其他所有參數。還有一個create方法,則原型如下:
public class Retrofit { private String baseUrl; private Retrofit(Builder builder) { this.baseUrl = builder.baseUrl; } public T create(Class clazz) { return null } static class Builder { private String baseUrl; Builder baseUrl(String host) { this.baseUrl = host; return this; } Retrofit build() { return new Retrofit(this); } }}
最最關鍵的內容就是create方法的實現了。原理就是先拿到最後一個參數,也就是回調,再拿到方法上的註解,獲得具體的值,然後拿到除了回調之外的其他參數,獲得參數上的註解,然後根據註解取得對應的值,還有原來的參數值,將方法上的註解的值中進行替換。使用OkHttp構造請求,請求完成後根據將結果解析為回調中的類型。整個過程如下
public T create(Class clazz) { /** * 緩衝中去 */ Object o = serviceMap.get(clazz); /** * 取不到則取構造代理對象 */ if (o == null) { o = (T) Proxy.newProxyInstance(Retrofit.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final Callback callback = (Callback) args[args.length - 1]; final GET get = method.getAnnotation(GET.class); if (get != null) { /** * 獲得GET註解的值 */ String getValue = get.value(); System.out.println(getValue); /** * 獲得所有參數上的註解 */ Annotation[][] methodParameterAnnotationArrays = method.getParameterAnnotations(); if (methodParameterAnnotationArrays != null) { int count = methodParameterAnnotationArrays.length; for (int i = 0; i < count; i++) { /** * 獲得單個參數上的註解 */ Annotation[] methodParameterAnnotations = methodParameterAnnotationArrays[i]; if (methodParameterAnnotations != null) { for (Annotation methodParameterAnnotation : methodParameterAnnotations) { /** * 如果是Path註解 */ if (methodParameterAnnotation instanceof Path) { /** * 取得path註解上的值 */ Path path = (Path) methodParameterAnnotation; String pathValue = path.value(); System.out.println(pathValue); /** * 這是對應的參數的值 */ System.out.println(args[i]); Request.Builder builder = new Request.Builder(); /** * 使用path註解替換get註解中的值為參數值 */ String result = getValue.replaceAll("\\{" + pathValue + "\\}", (String) args[i]); System.out.println(result); /** * 開始構造請求 */ Request request = builder.get() .url(baseUrl + "/" + result) .build(); okHttpClient.newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, IOException e) { /** * 失敗則回調失敗的方法 */ callback.onFailed(e); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { /** * 請求成功 */ String body = response.body().string(); /** * 使用fastjson進行zhuan轉換 */ Type type = callback.getClass().getGenericInterfaces()[0]; Object o1 = JSON.parse(body); /** * 回調成功 */ callback.onSuccess(o1); } } }); } } } } } } return null; } }); /** * 扔到緩衝中 */ serviceMap.put(clazz, o); } return (T) o; }
然後我們就可以根據Retrofit那樣進行調用了
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .build();GithubService githubService = retrofit.create(GithubService.class);githubService.listRepos("lizhangqu", new Callback>() { @Override public void onSuccess(Object t) { System.out.println(t); } @Override public void onFailed(Exception e) { }});
這隻是Retrofit中最簡單的一個模組實現,如果對其他內容感興趣,可以閱讀retrofit的源碼。