<div class="show-content" data-note-content="">
<div class="show-content-free">
<blockquote>
<p>只要是写Java的,动态代理就一个必须掌握的知识点,当然刚开始接触的时候,理解的肯定比较浅,渐渐的会深入一些,这篇文章通过实战例子帮助大家深入理解动态代理。</p>
</blockquote>
<p>说动态代理之前,要先搞明白什么是代理,代理的字面意思已经很容易理解了,我们这里撇开其他的解释,我们只谈设计模式中的<strong>代理模式</strong></p>
<h3>什么是代理模式(Proxy)</h3>
<p><strong>定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用</strong></p>
<p>在代理模式中,是需要代理对象和目标对象实现<strong>同一个接口</strong>(如果是不同的接口,那就是适配器模式了),看下面的UML图</p>
<div class="image-package">
<div class="image-container" style="max-height: 242px; max-width: 437px; background-color: transparent;">
<div class="image-container-fill" style="padding-bottom: 55.379999999999995%;"></div>
<div class="image-view" data-height="242" data-width="437"><img style="cursor: zoom-in;" src="//upload-images.jianshu.io/upload_images/188580-387a886bf94d09d4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/437" data-original-filesize="12126" data-original-format="image/png" data-original-height="242" data-original-width="437" data-original-src="//upload-images.jianshu.io/upload_images/188580-387a886bf94d09d4.png"></div>
</div>
<div class="image-caption">动态代理 uml.png</div>
</div>
<h4>为什么要用代理</h4>
<p>最最最主要的原因就是,<strong>在不改变目标对象方法的情况下对方法进行增强</strong>,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截,等等...</p>
<h5>举一个例子</h5>
<p>现有一个IPerson接口,只有一个方法say()</p>
<pre class="hljs java"><code class="java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IPerson</span> </span>{
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">say</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<p>有一个Man类,实现了IPerson</p>
<pre class="hljs java"><code class="java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Man</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IPerson</span></span>{
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">say</span><span class="hljs-params">()</span> </span>{
L.d(<span class="hljs-string">"man say"</span>);
}
}
</code></pre>
<p>现在需要在say方法被调用的时候,记录方法被调用的时间,最直接的就是修改Man的say方法,但是这样做的弊端就是如果有很多实现了IPerson接口的类,那就需要修改多处代码,而且这样的修改可能会导致其他的代码出问题(可能并不是所有的say都需要记录调用时间)。怎么办呢,这时候代理就要登场了!</p>
<h4>静态代理</h4>
<pre class="hljs java"><code class="java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ManProxy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IPerson</span></span>{
<span class="hljs-keyword">private</span> IPerson target;
<span class="hljs-function"><span class="hljs-keyword">public</span> IPerson <span class="hljs-title">getTarget</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> target;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> ManProxy <span class="hljs-title">setTarget</span><span class="hljs-params">(IPerson target)</span> </span>{
<span class="hljs-keyword">this</span>.target = target;
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;
}
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">say</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">if</span> (target != <span class="hljs-keyword">null</span>) {
L.d(<span class="hljs-string">"man say invoked at : "</span> + System.currentTimeMillis());
target.say();
}
}
}
</code></pre>
<p>这样我们需要新建一个ManProxy类同样实现IPerson接口,将要代理的对象传递进来,这样就可以在不修改Man的say方法的情况下实现了我们的需求。这其实就是<strong>静态代理</strong>。那你可能要问,既然有了静态代理,为什么需要动态代理呢,因为静态代理有一个最大的缺陷:<strong>接口与代理类是1对1的,有多个接口需要代理,就需要新建多个代理类,繁琐,类爆炸</strong>。</p>
<h4>动态代理</h4>
<p>我们先尝试用动态代理来解决上面的问题。先新建一个类实现InvocationHandler,</p>
<pre class="hljs java"><code class="java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NormalHandler</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">InvocationHandler</span> </span>{
<span class="hljs-keyword">private</span> Object target;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">NormalHandler</span><span class="hljs-params">(Object target)</span> </span>{
<span class="hljs-keyword">this</span>.target = target;
}
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
L.d(<span class="hljs-string">"man say invoked at : "</span> + System.currentTimeMillis());
method.invoke(target, args);
<span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
}
}
</code></pre>
<p>然后可以这样使用</p>
<pre class="hljs php"><code class="php">Man man = <span class="hljs-keyword">new</span> Man();
NormalHandler normalHandler = <span class="hljs-keyword">new</span> NormalHandler(man);
AnnotationHandler annotationHandler = <span class="hljs-keyword">new</span> AnnotationHandler();
IPerson iPerson = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(),
<span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">Class</span>[] </span>{IPerson.class, IAnimal.class}, annotationHandler);
iPerson.say();
</code></pre>
<p>可以看到NormalHandler中代理的对象是Object类型,所以它是被多个接口代理复用的,这样就解决静态代理类爆炸,维护困难的问题。我们重点看NormalHandler中的invoke方法,第二个参数method就是我们实际调用时的方法,所以动态代理使用了反射,为了灵活稍稍牺牲一点性能。</p>
<h4>动态代理的成功案例</h4>
<ul>
<li>Square公司出品的Android Restful 网络请求库Retrofit</li>
<li>Spring AOP (默认使用动态代理,如果没有实现接口则使用CGLIB修改字节码)</li>
</ul>
<p>这2个库不用多说了,Github上面Star数都是好几万的网红项目。</p>
<h4>利用动态代理实现一个低配的Retrofit</h4>
<p>“talk is cheap, show me the code”, 所以捋起袖子干起来。<br>
先新建需要用到的注解类和实体类</p>
<pre class="hljs java"><code class="java"><span class="hljs-meta">@Target</span>(ElementType.METHOD)
<span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> GET {
<span class="hljs-function">String <span class="hljs-title">value</span><span class="hljs-params">()</span></span>;
}
<span class="hljs-meta">@Target</span>(ElementType.METHOD)
<span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> POST {
<span class="hljs-function">String <span class="hljs-title">value</span><span class="hljs-params">()</span></span>;
}
<span class="hljs-meta">@Target</span>(ElementType.PARAMETER)
<span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Query {
<span class="hljs-function">String <span class="hljs-title">value</span><span class="hljs-params">()</span></span>;
}
<span class="hljs-comment">//更新实体类</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CheckUpdate</span> </span>{
<span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> hasUpdate;
<span class="hljs-keyword">private</span> String newVersion;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isHasUpdate</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> hasUpdate;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setHasUpdate</span><span class="hljs-params">(<span class="hljs-keyword">boolean</span> hasUpdate)</span> </span>{
<span class="hljs-keyword">this</span>.hasUpdate = hasUpdate;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getNewVersion</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> newVersion;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setNewVersion</span><span class="hljs-params">(String newVersion)</span> </span>{
<span class="hljs-keyword">this</span>.newVersion = newVersion;
}
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-string">"Has update : "</span> + hasUpdate + <span class="hljs-string">" ; The newest version is : "</span> + newVersion;
}
}
</code></pre>
<p>接下来是接口方法类, 接口url地址这里随便写的,大家知道意思就OK了。</p>
<pre class="hljs java"><code class="java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ApiService</span> </span>{
<span class="hljs-meta">@POST</span>(<span class="hljs-string">"http://www.baidu.com/login"</span>)
<span class="hljs-function">Observable<User> <span class="hljs-title">login</span><span class="hljs-params">(@Query(<span class="hljs-string">"username"</span>)</span> String username, @<span class="hljs-title">Query</span><span class="hljs-params">(<span class="hljs-string">"password"</span>)</span> String password)</span>;
<span class="hljs-meta">@GET</span>(<span class="hljs-string">"http://www.baidu.com/checkupdate"</span>)
<span class="hljs-function">Observable<CheckUpdate> <span class="hljs-title">checkUpdate</span><span class="hljs-params">(@Query(<span class="hljs-string">"version"</span>)</span> String version)</span>;
}
</code></pre>
<p>接下来就是我们的重点代理类RequestHandler,里面的核心是解析方法注解的返回值和参数,包括返回值的泛型,在Json反序列化的时候回用到</p>
<pre class="hljs java"><code class="java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RequestHandler</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">InvocationHandler</span> </span>{
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
Annotation[] annotations = method.getAnnotations();
<span class="hljs-keyword">if</span> (annotations != <span class="hljs-keyword">null</span> && annotations.length > <span class="hljs-number">0</span>) {
Annotation annotation = annotations[<span class="hljs-number">0</span>];
<span class="hljs-keyword">if</span> (annotation <span class="hljs-keyword">instanceof</span> GET) {
GET get = (GET) annotation;
<span class="hljs-keyword">return</span> handleGetRequest(method, get, args);
}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (annotation <span class="hljs-keyword">instanceof</span> POST) {
POST post = (POST) annotation;
<span class="hljs-keyword">return</span> handlePostRequest(method, post, args);
}
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
}
<span class="hljs-function"><span class="hljs-keyword">private</span> Observable <span class="hljs-title">handleGetRequest</span><span class="hljs-params">(Method method, GET get, Object[] params)</span> </span>{
String url = get.value();
Type genericType = method.getGenericReturnType();
Parameter[] parameters = method.getParameters();
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Class returnGenericClazz = <span class="hljs-keyword">null</span>;
<span class="hljs-comment">//解析方法返回值的参数类型</span>
<span class="hljs-keyword">if</span> (parameterizedType != <span class="hljs-keyword">null</span>) {
Type[] types = parameterizedType.getActualTypeArguments();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < types.length; i++) {
Class cls = (Class) types[i];
returnGenericClazz = cls;
<span class="hljs-keyword">break</span>;
}
}
<span class="hljs-comment">//解析请求参数,然后拼接到url</span>
<span class="hljs-keyword">if</span> (params != <span class="hljs-keyword">null</span>) {
url += <span class="hljs-string">"?"</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < params.length; i++) {
Query query = parameters[i].getAnnotation(Query.class);
url += query.value() + <span class="hljs-string">"="</span> + params[<span class="hljs-number">0</span>].toString();
<span class="hljs-keyword">if</span> (i < params.length - <span class="hljs-number">1</span>) {
url += <span class="hljs-string">"&"</span>;
}
}
}
<span class="hljs-keyword">final</span> String getUrl = url;
<span class="hljs-keyword">final</span> Class returnClazz = returnGenericClazz;
<span class="hljs-keyword">return</span> Observable.create(observableEmitter -> {
Request request = <span class="hljs-keyword">new</span> Request.Builder().url(getUrl).build();
Response response = <span class="hljs-keyword">new</span> OkHttpClient()
.newCall(request).execute();
<span class="hljs-keyword">if</span> (response.isSuccessful()) {
<span class="hljs-comment">// String responseStr = response.body().string();</span>
<span class="hljs-comment">//这里mock返回数据</span>
String responseStr = MockFactory.mockCheckUpdateStr();
Object result = <span class="hljs-keyword">new</span> Gson().fromJson(responseStr, returnClazz);
observableEmitter.onNext(result);
}<span class="hljs-keyword">else</span> {
observableEmitter.onError(<span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"http request failed!"</span>));
}
observableEmitter.onComplete();
});
}
<span class="hljs-function"><span class="hljs-keyword">private</span> Observable <span class="hljs-title">handlePostRequest</span><span class="hljs-params">(Method method, POST post, Object[] params)</span> </span>{
<span class="hljs-comment">//篇幅关系,这里省略,可以参考get 实现</span>
<span class="hljs-comment">//。。。。。</span>
}
}
</code></pre>
<p>新建一个门面类Retrofit,方便调用</p>
<pre class="hljs php"><code class="php"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Retrofit</span> </span>{
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <T> T newProxy(<span class="hljs-class"><span class="hljs-keyword">Class</span><<span class="hljs-title">T</span>> <span class="hljs-title">clazz</span>) </span>{
<span class="hljs-keyword">return</span> (T) Proxy.newProxyInstance(clazz.getClassLoader(),
<span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">Class</span>[] </span>{clazz}, <span class="hljs-keyword">new</span> RequestHandler());
}
}
</code></pre>
<p>一个低配版的Retrofit就完成了,赶紧去测试一下</p>
<pre class="hljs java"><code class="java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
ApiService apiService = ApiService apiService = Retrofit.newProxy(ApiService.class);
Observable<CheckUpdate> checkUpdateObservable = apiService.checkUpdate(<span class="hljs-string">"3.1.0"</span>);
checkUpdateObservable.subscribeOn(Schedulers.io())
.subscribe(checkUpdate -> L.d(checkUpdate.toString()),
throwable -> L.d(throwable.getMessage()));
<span class="hljs-comment">//等待工作线程执行完成</span>
Scanner sc = <span class="hljs-keyword">new</span> Scanner(System.in);
<span class="hljs-keyword">if</span> (sc.next() != <span class="hljs-keyword">null</span>) {}
}
</code></pre>
<p>最终的执行结果,当然这里只是初步实现了Retrofit的一点点功能,我们的目标还是讲解动态代理这个技术,以及它能够干什么</p>
<br>
<div class="image-package">
<div class="image-container" style="max-height: 136px; max-width: 680px; background-color: transparent;">
<div class="image-container-fill" style="padding-bottom: 20.0%;"></div>
<div class="image-view" data-height="136" data-width="680"><img style="cursor: zoom-in;" src="//upload-images.jianshu.io/upload_images/188580-61415f812aeb0f2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/680" data-original-filesize="23753" data-original-format="image/png" data-original-height="136" data-original-width="680" data-original-src="//upload-images.jianshu.io/upload_images/188580-61415f812aeb0f2f.png"></div>
</div>
<div class="image-caption">执行结果</div>
</div>
<h4>最后一点小Tip</h4>
<p>可以看到,我们上面的低配的Retrofit,并没有被代理的类,因为我们仅仅通过解析ApiService接口中的注解中的信息已经足够我们去发起Http请求,所以技术在于灵活运用。<br>
好了,这篇先到这里,大家开心发财!</p>
</div>
</div>