AndroidInject项目使用动态代理增加对网络请求的支持

以下内容为原创,欢迎转载,转载请注明

来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3540427.html

 

AndroidInject项目是我写的一个使用注解注入来简化代码的开源项目

https://github.com/wangjiegulu/androidInject

今天新增功能如下:

1. 增加@AIScreenSize注解,作用于属性,用于注入当前设备的屏幕大小(宽高)
2. 增加对网络请求的支持,使用动态代理实现:@AIGet注解,作用于接口方法,表示以GET来请求url;@AIPost注解,作用于接口方法,表示以POST来请求url;@AIParam,用于注入请求参数
3. 增加@AINetWorker注解,作用于属性,用于注入网络请求服务
4. 增加GET或POST请求时请求参数可使用Params类传入,简化代码

 

主要执行代码如下:

用@AINetWorker注解注入NetWorker接口的子类代理(动态代理模式):

1 @AINetWorker
2 private PersonWorker personWorker;

然后启动线程,在线程中调用进行网络请求:

1 new Thread(new Runnable() {
2     @Override
3     public void run() {
4 //        RetMessage<Person> retMsg = personWorker.getPersonsForGet("a1", "b1", "c1");
5 //        RetMessage<Person> retMsg = personWorker.getPersonsForGet2(new Params().add("aa", "a1").add("bb", "b1").add("cc", "c1"));
6         RetMessage<Person> retMsg = personWorker.getPersonsForPost2(new Params().add("aa", "a1").add("bb", "b1").add("cc", "c1"));
7         System.out.println(retMsg.getList().toString());
8     }
9 }).start();

请求的结果封装在RetMessage类中(AndroidInject框架所作的事就是执行Get或者Post请求,获得返回结果,然后json解析后封装在RetMessage中):

package com.wangjie.androidinject.annotation.core.net;

import com.google.gson.Gson;

import java.util.List;

/**
 * Json响应结果包装类
 * Created with IntelliJ IDEA.
 * Author: wangjie  email:tiantian.china.2@gmial.com
 * Date: 14-2-7
 * Time: 下午4:25
 */
public class RetMessage<T>
{
    private int resultCode; // 结果码,必须包含

    private List<T> list; // 返回的数据

    private T obj; // 返回的数据

    private Integer size; // 返回数据长度

    private String errorMessage; // 返回错误信息

    public String toJson(){
        return new Gson().toJson(this);
    }
    // getter和setter方法省略...
}

接下来看下PersonWorker接口中所作的事情:

 1 package com.wangjie.androidinject;
 2
 3 import com.wangjie.androidinject.annotation.annotations.net.AIGet;
 4 import com.wangjie.androidinject.annotation.annotations.net.AIParam;
 5 import com.wangjie.androidinject.annotation.annotations.net.AIPost;
 6 import com.wangjie.androidinject.annotation.core.net.RetMessage;
 7 import com.wangjie.androidinject.annotation.util.Params;
 8 import com.wangjie.androidinject.model.Person;
 9
10 /**
11  * Created with IntelliJ IDEA.
12  * Author: wangjie  email:tiantian.china.2@gmail.com
13  * Date: 14-2-7
14  * Time: 下午1:44
15  */
16 public interface PersonWorker {
17     @AIGet("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons?aa=#{a3}&bb=#{b3}&cc=#{c3}")
18     public RetMessage<Person> getPersonsForGet(@AIParam("a3")String a2, @AIParam("b3") String b2, @AIParam("c3") String c2);
19
20     @AIPost("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")
21     public RetMessage<Person> getPersonsForPost(@AIParam("aa")String a2, @AIParam("bb") String b2, @AIParam("cc") String c2);
22
23     @AIGet("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")
24     public RetMessage<Person> getPersonsForGet2(Params params);
25
26     @AIPost("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")
27     public RetMessage<Person> getPersonsForPost2(Params params);
28
29
30 }

PersonWorker是自己写的一个接口(以后需要有新的网络请求,都可以类似编写Worker),声明了执行网络请求的各种方法,这些方法需要加上@AIGet或者@AIPost注解,用于声明请求方式,并在此注解中的value()值设置为所要请求的url(此注解的其他属性后续会陆续扩展)

方法的@AIParam注解是作用与mybatis的@Param注解类似,可以设置请求携带的参数

如果参数比较多,则推荐使用Params来存放参数,以此来简化代码,Params类实质上就是一个HashMap,存放参数的键值对即可。

 

接下来分析下框架是怎么实现的,其实上面讲过,主要是用Annotaion和动态代理了

首先看看PersonWorker的注入,在AIActivity(AIActivity,AndroidInject开源项目中的Activity使用注解的话,你写的Activity必须继承AIActivity,另外如果要使用FragmentActivity,则需要继承AISupportFragmentActivity)启动时,首先会去解析添加的注解,这里讨论@AINetWorker注解,内部代码很简单:

1 /**
2  * 注入NetWorker
3  * @param field
4  * @throws Exception
5  */
6  private void netWorkerBind(Field field) throws Exception{
7         field.setAccessible(true);
8         field.set(present, NetInvoHandler.getWorker(field.getType()));
9  }

通过代码可知,是使用反射来实现的,主要的代码是这句:

NetInvoHandler.getWorker(field.getType());

这句代码的作用是通过Class获得一个PersonWorker实现类的代理对象,这里很明显是使用了动态代理。

所以,最核心的类应该是NetInvoHandler这个类,这个类的代码如下(篇幅问题,所以就折叠了):

  1 package com.wangjie.androidinject.annotation.core.net;
  2
  3 import android.text.TextUtils;
  4 import com.google.gson.Gson;
  5 import com.wangjie.androidinject.annotation.annotations.net.AIGet;
  6 import com.wangjie.androidinject.annotation.annotations.net.AIParam;
  7 import com.wangjie.androidinject.annotation.annotations.net.AIPost;
  8 import com.wangjie.androidinject.annotation.util.Params;
  9 import com.wangjie.androidinject.annotation.util.StringUtil;
 10
 11 import java.lang.annotation.Annotation;
 12 import java.lang.reflect.InvocationHandler;
 13 import java.lang.reflect.Method;
 14 import java.lang.reflect.Proxy;
 15 import java.util.HashMap;
 16 import java.util.Map;
 17
 18 /**
 19  * Created with IntelliJ IDEA.
 20  * Author: wangjie  email:tiantian.china.2@gmail.com
 21  * Date: 14-2-7
 22  * Time: 下午1:40
 23  */
 24 public class NetInvoHandler implements InvocationHandler{
 25     private static HashMap<Class<?>, NetInvoHandler> invoHandlers = new HashMap<Class<?>, NetInvoHandler>();
 26
 27     private Object proxy; // 代理对象
 28
 29     public synchronized  static<T> T getWorker(Class<T> clazz){
 30         NetInvoHandler netInvoHandler = invoHandlers.get(clazz);
 31         if(null == netInvoHandler){
 32             netInvoHandler = new NetInvoHandler();
 33             netInvoHandler.setProxy(Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, netInvoHandler));
 34             invoHandlers.put(clazz, netInvoHandler);
 35         }
 36         return (T)netInvoHandler.getProxy();
 37     }
 38
 39     @Override
 40     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 41
 42         // get请求
 43         if(method.isAnnotationPresent(AIGet.class)){
 44             AIGet aiGet = method.getAnnotation(AIGet.class);
 45             String url = aiGet.value();
 46             if(TextUtils.isEmpty(url)){
 47                 throw new Exception("net work [" + method.getName() + "]@AIGet value()[url] is empty!!");
 48             }
 49             Annotation[][] annotaions = method.getParameterAnnotations();
 50             for(int i = 0; i < args.length; i++){
 51                 if(Params.class.isAssignableFrom(args[i].getClass())){ // 如果属性为Params,则追加在后面
 52                     url = StringUtil.appendParamsAfterUrl(url, (Params)args[i]);
 53                 }else{ // 如果属性添加了@AIParam注解,则替换链接中#{xxx}
 54                     String repName = ((AIParam)annotaions[i][0]).value();
 55                     url = url.replace("#{" + repName + "}", args[i] + "");
 56                 }
 57
 58             }
 59             StringBuilder sb = NetWork.getStringFromUrl(url);
 60             if(null == sb){
 61                 return null;
 62             }
 63             return new Gson().fromJson(sb.toString(), method.getReturnType());
 64         }
 65
 66         // post请求
 67         if(method.isAnnotationPresent(AIPost.class)){
 68             AIPost aiPost = method.getAnnotation(AIPost.class);
 69             String url = aiPost.value();
 70             if(TextUtils.isEmpty(url)){
 71                 throw new Exception("net work [" + method.getName() + "]@AIPost value()[url] is empty!!");
 72             }
 73             Annotation[][] annotaions = method.getParameterAnnotations();
 74             Map<String, String> map = new HashMap<String, String>();
 75             for(int i = 0; i < args.length; i++){
 76                 if(Params.class.isAssignableFrom(args[i].getClass())){ // 如果属性为Params,则追加在后面
 77                     map.putAll((Params)args[i]);
 78                 }else{
 79                     String repName = ((AIParam)annotaions[i][0]).value();
 80                     map.put(repName, args[i] + "");
 81                 }
 82
 83             }
 84             StringBuilder sb = NetWork.postStringFromUrl(url, map);
 85             if(null == sb){
 86                 return null;
 87             }
 88             return new Gson().fromJson(sb.toString(), method.getReturnType());
 89
 90         }
 91
 92
 93         return null;
 94     }
 95
 96
 97     public Object getProxy() {
 98         return proxy;
 99     }
100
101     public void setProxy(Object proxy) {
102         this.proxy = proxy;
103     }
104 }

View Code

里面的代码还没有好好的重构,所以,看起来会更直白,该类实现了InvocationHandler,很明显的动态代理。

我们通过NetInvoHandler的getWorker静态方法,来获取一个指定Class的Worker实现类的代理对象,由于实际应用时,Worker接口应该会很多,为了不重复生成相同Worker实现类的代理对象,所以这里在生成一个后,保存起来,确保一个Worker只生成一个代理对象,一个NetInvoHandler。

这里有个地方需要注意一下,以前使用的动态代理,需要一个RealSubject,也就是真实对象,是Worker的实现类。这样,在invoke方法中就可以调用真实对象的对应方法了,但是现在,进行网络请求,我们没有去写一个类然后实现PersonWorker接口,因为没有必要,我们完全可以在invoke方法中去执行相同的网络请求。

请想下,之所以需要框架的存在 不就是为了把一些模板的东西给简化掉么?现在的网络请求这些步骤就是一些模板话的东西,我们需要的就是调用方法,自动进行网络请求(框架做的事),然后返回给我结果。所以网络请求这一步,写在invoke方法中即可。

而不是所谓的编写一个类,实现PersonWorker接口,在这个实现类中进行网络请求,然后在invoke方法中调用真实对象的对应该方法。

因此,在Proxy.newProxyInstance的interfaces中填写需要实现的接口,也就是现在的PersonWorker。

 

接下来看下invoke中做的事情,首先根据方法增加的注解来识别是GET请求还是POST请求。然后各自执行请求(因为我请求的执行,写在NetWork中了,这里直接返回了请求结果字符串StringBuilder sb)。

接下来,使用Gson这个霸气的工具,一键从json解析封装成RetMessage对象。(所以,这里是需要Gson库的支持,大家网上下载gson.jar,或者使用maven)

当然,要使用Gson一键解析封装的前提是服务器端的编写需要保存一致性,下面是我服务器端测试的代码:

 1 @RequestMapping("/findPersons")
 2     public void findPersons(HttpServletRequest request, HttpServletResponse response,
 3                                 @RequestParam("aa") String aa,
 4                                 @RequestParam("bb") String bb,
 5                                 @RequestParam("cc") String cc) throws IOException{
 6         System.out.println("aa: " + aa + ", bb: " + bb + ", cc: " + cc);
 7         RetMessage<Person> rm = new RetMessage<Person>();
 8
 9         rm.setResultCode(0);
10
11         List<Person> persons = new ArrayList<Person>();
12         for(int i = 0; i < 5; i++){
13             Person p = new Person();
14             p.setName("wangjie_" + i);
15             p.setAge(20 + i);
16             persons.add(p);
17         }
18
19         rm.setList(persons);
20
21         ServletUtil.obtinUTF8JsonWriter(response).write(rm.toJson());
22     }

服务器端返回结果时,也是封装在RetMessage类中,这个类服务器端和客户端是保持一致的,所以可以一键转换。

如果你在开发的过程中,RetMessage类中封装的东西不能满足你的需求,可以自己编写结果类,当然在Worker中声明方法中返回值就应该是你写的结果类了。

 

到此为止,讲解完毕

另:如果,你需要写一个查询User信息的网络请求,应该怎么写?

只需编写UserWorker接口,然后声明方法findUsers(),写上@AIGet或者@AIPost注解,写明url和请求参数。然后通过@AINetWorker注解注入userWorker,然后开启线程,调用userWorker的findUsers()方法即可。

当然UserWorker也可以不使用注解获得,而是调用“NetInvoHandler.getWorker(UserWorker.class)”获得!

 

 

时间: 2024-11-14 11:57:22

AndroidInject项目使用动态代理增加对网络请求的支持的相关文章

动态代理是构建Decorator和Adapter的方便工具

这个过程允许实现"截取"方法调用,重新路由它们或者动态地添加功能.本期文章中,Brian Goetz 介绍了几个用于动态代理的应用程序.请在本文伴随的 讨论论坛 上与作者和其他读者分享您对这篇文章的想法. 动态代理为实现许多常见设计模式(包括 Facade.http://www.aliyun.com/zixun/aggregation/16410.html">Bridge.Interceptor.Decorator.Proxy(包括远程和虚拟代理)和 Adapter 模

Android 几种网络请求的区别与联系

HttpUrlConnection 最开始学android的时候用的网络请求是HttpUrlConnection,当时很多东西还不知道,但是在android 2.2及以下版本中HttpUrlConnection存在着一些bug,所以建议在android 2.3以后使用HttpUrlConnection,之前使用HttpClient. 在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择.而在Android 2.3版本及以后,HttpURLConnecti

Android开发中的几种网络请求方式详解_Android

Android应用经常会和服务器端交互,这就需要手机客户端发送网络请求,下面介绍四种常用网络请求方式,我这边是通过Android单元测试来完成这四种方法的,还不清楚Android的单元测试的同学们请看Android开发技巧总结中的Android单元测试的步骤一文. Java.NET包中的HttpURLConnection类 Get方式: // Get方式请求 public static void requestByGet() throws Exception { String path = "h

【SSH系列】静态代理&amp;amp;&amp;amp;动态代理

从设计模式说起 代理模式是二十三中设计模式中的一种,代理模式就是指由一个代理主题来操作真实的主题,真实的主题执行具体的业务操作,而代理主题负责其她相关业务,简而言之,代理模式可以由以下三个部分组成: a.抽象角色:通过接口或抽象类声明真实角色实现的业务方法. b.代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作. c.真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用.第一次接触代理模式的是在学习大话设计模式的时候,首先

Java:关于动态代理

问题描述 public Object invoke(Object proxy,Method method,Object[] args) throws Throwable { System.out.println("调用"+method.toString()+"之前"); Object o = method.invoke(proxyobj,args); System.out.println("调用"+method.toString()+"

JAVA核心层--反射--动态代理

本文发表于2010年,时间较早,部分问题解释不是十分准确,所以需要进一步了解,请参看2012年版本: java之架构基础-动态代理&cglib 要在JAVA技术上突破普通的层面,并拥有一翻设计理念的高度,除了要有很好的设计思维之外,反射在适当的使用下,将会把框架做得非常清晰,并且代码编写也非常简便. 在面向对象的编程中,我们为什么要忌讳去大量使用if else switch语句,因为这样写是将逻辑硬编码了,JAVA的思想就是将其配置化,一旦可配置化后,就逐渐可管理化,并随着产品的成熟逐步实现自动

使用JAVA中的动态代理实现数据库连接池

动态|数据|数据库|数据库连接 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈.我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度.很多的连接池都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前所有的应用服务器取数据库连接的方式都是这种方式实现的.但是另外一个共同的问题是,它们同时不允许使用者显式的调用Co

设计模式之动态代理(dynamic proxy)

1 动态代理与静态代理 我们从上一篇设计模式之代理模式一文中已经知道,在代理模式中代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互.代理的存在对于调用者来说是透明的,调用者看到的只是接口.这就是传统的代理模式静态代理的特点. 那么传统的静态代理模式有什么问题呢?如果需要代理的类只有一个,那么静态代理没什么问题,如果有很多类需要代理呢,用静态代理的话就需要为每一个类创建一个代理类,显然这么做太过繁琐也容易出错.为此,JDK 5引入的动态代理机制,允许开发人员在运行时刻动态的创建出代

Android插件化原理解析——Hook机制之动态代理

转发必注明出处:Hook机制之动态代理 使用代理机制进行API Hook进而达到方法增强是框架的常用手段,比如J2EE框架Spring通过动态代理优雅地实现了AOP编程,极大地提升了Web开发效率:同样,插件框架也广泛使用了代理机制来增强系统API从而达到插件化的目的.本文将带你了解基于动态代理的Hook机制. 阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的dynamic-proxy-hook模块.另外,插件框架原理解析系列文章见索引.