博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AndroidInject项目使用动态代理增加对网络请求的支持
阅读量:6583 次
发布时间:2019-06-24

本文共 10195 字,大约阅读时间需要 33 分钟。

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

来自天天博客:

 

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

今天新增功能如下:

1. 增加@AIScreenSize注解,作用于属性,用于注入当前设备的屏幕大小(宽高)

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

 

主要执行代码如下:

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

1 @AINetWorker2 private PersonWorker personWorker;

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

1 new Thread(new Runnable() {2     @Override3     public void run() {4 //        RetMessage
retMsg = personWorker.getPersonsForGet("a1", "b1", "c1");5 // RetMessage
retMsg = personWorker.getPersonsForGet2(new Params().add("aa", "a1").add("bb", "b1").add("cc", "c1"));6 RetMessage
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
{ private int resultCode; // 结果码,必须包含 private List
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.com13  * Date: 14-2-714  * Time: 下午1:4415  */16 public interface PersonWorker {17     @AIGet("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons?aa=#{a3}&bb=#{b3}&cc=#{c3}")18     public RetMessage
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
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
getPersonsForGet2(Params params);25 26 @AIPost("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")27 public RetMessage
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  * 注入NetWorker3  * @param field4  * @throws Exception5  */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
, NetInvoHandler> invoHandlers = new HashMap
, NetInvoHandler>(); 26 27 private Object proxy; // 代理对象 28 29 public synchronized static
T getWorker(Class
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
map = new HashMap
(); 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
rm = new RetMessage
(); 8 9 rm.setResultCode(0);10 11 List
persons = new ArrayList
();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)”获得!

 

 

你可能感兴趣的文章
美元反转或引发A股中长期调整
查看>>
[转载] New Concept English 1——Lesson 14 What colour's your…?
查看>>
PHP Array数组
查看>>
Generalization and Zeros
查看>>
kthread_run【转】
查看>>
利用linux信号机制调试段错误(Segment fault)【转】
查看>>
【POJ 3104】Drying
查看>>
UVa/数组与字符串习题集
查看>>
Subscribe的第四个参数用法
查看>>
BZOJ 1008 越狱
查看>>
linux中groupadd、groupmod、groupdel、newgrp命令
查看>>
Serializable 作用
查看>>
图片延迟 jquery lazyload.js
查看>>
P1333 瑞瑞的木棍 [并查集][欧拉路径]
查看>>
windows系统下,电脑存在文件无法删除,强制删除目录下所有文件
查看>>
SIT测试 和 UAT测试
查看>>
shell脚本从文件夹中递归提取文件
查看>>
VC最常用操作程序20项列举(转)
查看>>
Http幂等性
查看>>
[ ObjectListView ] - ListView的增强控件 - 前言 (翻译)
查看>>