Java动态代理-AOP-实战

2018-12-28  本文已影响0人  儍侽孩R

<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="https://img.haomeiwen.com/i188580/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="https://img.haomeiwen.com/i188580/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&lt;User&gt; <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&lt;CheckUpdate&gt; <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> &amp;&amp; annotations.length &gt; <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 &lt; 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 &lt; 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 &lt; params.length - <span class="hljs-number">1</span>) {

                    url += <span class="hljs-string">"&amp;"</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 -&gt; {

            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> &lt;T&gt; T newProxy(<span class="hljs-class"><span class="hljs-keyword">Class</span>&lt;<span class="hljs-title">T</span>&gt; <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&lt;CheckUpdate&gt; checkUpdateObservable = apiService.checkUpdate(<span class="hljs-string">"3.1.0"</span>);

    checkUpdateObservable.subscribeOn(Schedulers.io())

            .subscribe(checkUpdate -&gt; L.d(checkUpdate.toString()),

                  throwable -&gt; 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="https://img.haomeiwen.com/i188580/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="https://img.haomeiwen.com/i188580/61415f812aeb0f2f.png"></div>

</div>

<div class="image-caption">执行结果</div>

</div>

<h4>最后一点小Tip</h4>

<p>可以看到,我们上面的低配的Retrofit,并没有被代理的类,因为我们仅仅通过解析ApiService接口中的注解中的信息已经足够我们去发起Http请求,所以技术在于灵活运用。<br>

好了,这篇先到这里,大家开心发财!</p>

          </div>

        </div>

上一篇下一篇

猜你喜欢

热点阅读