zoukankan      html  css  js  c++  java
  • 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)”获得!

  • 相关阅读:
    敏捷不是XP(口水文)
    利用异或的特性解决,找出重复数的问题,应该是目前最优算法。
    开源和免费那些事儿(二)
    开源和免费那些事儿
    LINQ本质 外篇 JOIN补遗
    在北京求.NET开发职位,人已经到达北京
    软件是邪恶的
    最近遇到的两个面试题兼卖身广告
    谈谈信仰和银弹。
    继续高阶函数好玩有用的扩展(网吧行文)
  • 原文地址:https://www.cnblogs.com/tiantianbyconan/p/3540427.html
Copyright © 2011-2022 走看看