用Jersey为Android客户端开发Restful Web Service

  平时在做Android客户端的时候经常要与服务器之间通信,客户端通过服务端提供的接口获取数据,然后再展示在客户端的界面上,作为Android开发者,我们平时更多的是关注客户端的开发,而对服务端开发的关注相对较少,但是如果我们要自己一个人设计并开发一套完整的系统,我们就必须同时具备客户端和服务端的开发知识,而且我认为同时掌握客户端和服务端的开发技术还是很有用处的,不仅有助于提升我们的架构知识和能力,而且还……你懂得!身边一些做WEB开发的朋友很多都说为客户端开发接口和单纯地做WEB项目并没有很大的区别,不管后台语言用的是JAVA,PHP,还是C#等,用这些语言为客户端开发接口的时候有很多相似之处,比如说服务端请求路径的分发以及请求处理方式等。这几天出于好奇,想为自己的客户端开发Web Service接口,所以就在网上搜集了一些资料,发现有很多WEB框架可以很轻松地为移动端开发服务器接口,比如说Spring MVC,CXF,AXIS,RESTEasy等等,但是我最终选用了Jersey来完成服务端的开发。

  首先,我们来大致说一下Jersey是什么。按照Jersey官网(https://jersey.java.net/)的介绍,Jersey是在Java开发中,为了简化RESTful Web Service以及客户端开发而设计的一套开源的,标准的,很优秀的,使用起来非常方便的JAX-RS API(即Java API for RESTful Web Services),Jersey支持JAX-RS API,并且是JAX-RS (JSR 311 & JSR 339) 的参考实现,值得注意的是,Jersey不仅仅是JAX-RS的参考实现,同时,它在JAX-RS工具包的基础上做了大量的扩展,增加了许多额外的特性和工具,并且都提供了丰富的说明文档以便开发者进一步对Jessey进行扩展以满足自己的实际开发需求。

  为了方便大家更好地理解JAX-RS和RESTful,在这里我对这两个名词稍加说明。

  JAX-RS按照百度百科上面的说明,即Java API for RESTful Web Services,是一个Java 编程语言的应用程序接口,支持按照表述性状态转移(REST)架构风格创建Web服务。JAX-RS使用了Java SE5引入的Java标注来简化Web服务的客户端和服务端的开发和部署。JAX-RS提供了一些标注将一个资源类,一个POJO Java类,封装为Web资源。JAX-RS包含了一系列的标注,包括:

  @Path,标注资源类或者方法的相对路径

  @GET,@PUT,@POST,@DELETE,标注方法是HTTP请求的类型。

  @Produces,标注返回的MIME媒体类型

  @Consumes,标注可接受请求的MIME媒体类型

  @PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam,分别标注方法的参数来自于HTTP请求的不同位置,例如@PathParam来自于URL的路径,@QueryParam来自于URL的查询参数,@HeaderParam来自于HTTP请求的头信息,@CookieParam来自于HTTP请求的Cookie。

  RESTful是一种软件架构风格,而不是软件设计标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。关于RESTful的详细信息请参考:http://baike.baidu.com/link?url=z4989VJ3yaALLF9Xs7yc-j7h9QjKjDLGMz12F-ORdWpNjawQE6oOVLVXdnCh4OW1Pla7DJBpy6NlhwuzI1LUAK。

  好了,废话不多说,接下来马上开始我们的实战阶段,我将通过写一个服务端项目和一个Android客户端项目来详细说明是如何使用Jersey实现为移动端开发Web Service的。

  第一步,简单介绍一下项目的需求,我们将开发一个APP登录模块,登录之后进入首页,首页显示欢迎语和登录用户的名字,首页中有一个Button,通过点击Button获取到服务器中保存的所有用户的信息并通过TextView展示,需求就是这么简单,逻辑也不会很复杂。我希望就是通过这么一个简单的模型让对服务端接口开发还不是很熟悉的朋友了解到通过Jersey如何实现服务端的开发,以及客户端和服务端是如何交互。

  第二步,介绍一下使用到的开发工具,服务端我用的是MyEclipse 2015,客户端使用的是Android Studio。

  第三步,说明一下客户端和服务端通信数据传输格式为JSON,客户端在解析服务端返回的json时使用了谷歌自家的工具Gson。对于简单的JSON,我们使用普通的JsonArray和JsonObject可以很方便的解析,没必要使用Gson等解析工具,但是在解析一些非常复杂的json的时候,Gson等解析工具的优势就会体现出来了,Gosn能够很方便的将json转换为实体Bean,也能很方便地将实体Bean转换为json,这对于做WEB API开发非常有帮助,省去了不少解析的麻烦,因此我个人向大家推荐学习一下Gson的使用。此外,客户端采用HttpURLConnection向服务端发送请求,请求方式为POST,请求内容的类型(Content-Type)为application/x-www-form-urlencoded,并设置请求连接服务器的超时时间为5秒。

  第四步,服务端项目编写:

  服务端需要使用到很多jar包,包括Jersey官方提供的众多jar包,以及注册json转化器需要使用到的JackSon项目jar包,JackSon的作用基本和Gson类似,用于java中java bean和json或者xml之间的转换。Jersey官方的jar包的下载地址:https://jersey.java.net/download.html。我在项目中使用的是Jesey2.x版本,大家在使用的时候下载最新版的就可以,当然也可以直接拷贝项目中的所有jar包到自己的项目中。JackSon的官网貌似现在不能下载jar包了,但是应该有其它一些渠道可以下载,大家在使用的时候可以直接拷贝我丢在项目中的jar包。

首先,我们需要创建一个动态Web工程,即Dynamic Web Project,具体创建步骤如下:

File-New-Other

选择Dynamic Web Project,点击next

  给项目取名,其它默认,点击finish。

  工程创建好之后,我们看一下项目完成之后的整体结构,如图:

  其中,核心代码在RestService.class中,所有的接口都写在这里面,我直接把RestService.class的代码贴出来,如下:

  1 package com.jerseyserver.service;
  2
  3
  4 import java.util.ArrayList;
  5 import java.util.HashMap;
  6
  7 import javax.ws.rs.FormParam;
  8 import javax.ws.rs.GET;
  9 import javax.ws.rs.POST;
 10 import javax.ws.rs.Path;
 11 import javax.ws.rs.Produces;
 12 import javax.ws.rs.core.GenericEntity;
 13 import javax.ws.rs.core.MediaType;
 14 import javax.ws.rs.core.Response;
 15
 16 import com.fasterxml.jackson.core.JsonProcessingException;
 17 import com.fasterxml.jackson.databind.ObjectMapper;
 18 import com.jerseyserver.entity.ResponseDTO;
 19 import com.jerseyserver.entity.User;
 20 import com.jerseyserver.util.TextUtil;
 21
 22
 23 @Path("/restService")
 24 public class RestService {
 25     @POST
 26     @Path("/getUserText")
 27     @Produces(MediaType.TEXT_PLAIN)
 28     /**
 29      * 测试返回text文本媒体类型数据的方式
 30      * @return "Hello,World!"
 31      */
 32     public String getUserText() {
 33         return "Hello,World!";
 34     }
 35
 36     @GET
 37     @Path("/getUserXml")
 38     @Produces(MediaType.APPLICATION_XML)
 39     /**
 40      * 测试返回xml媒体类型数据的方式
 41      * @return User object
 42      */
 43     public User getUserXml() {
 44         User user = new User();
 45         user.setName("snail");
 46         user.setAge("22");
 47         user.setSex("male");
 48         return user;
 49     }
 50
 51     @GET
 52     @Path("/getUserJson")
 53     @Produces(MediaType.APPLICATION_JSON)
 54     /**
 55      * 测试返回json媒体类型数据的方式
 56      * @return User object
 57      */
 58     public User getUserJson() {
 59         User user = new User();
 60         user.setName("snail");
 61         user.setAge("22");
 62         user.setSex("male");
 63         return user;
 64     }
 65
 66     @POST
 67     @Path("/getUserInfo")
 68     @Produces(MediaType.APPLICATION_JSON)
 69     /**
 70      * 测试带请求参数时返回json媒体类型数据的方式
 71      * @param username
 72      * @return
 73      */
 74     public User getUserInfo(@FormParam("username") String username) {
 75         if (username == null || "".equals(username)) {
 76             return null;
 77         }
 78         return getUserByName(username);
 79     }
 80
 81     /**
 82      * 通过用户名获取用户
 83      * @param username
 84      * @return User object
 85      */
 86     private User getUserByName(String username) {
 87         HashMap<String, User> map = initAllUsers();
 88         return map.get(username);
 89     }
 90
 91     /**
 92      * 获取所有用户的map
 93      * @return 所有用户的map
 94      */
 95     private HashMap<String, User> initAllUsers() {
 96         HashMap<String, User> map = new HashMap<>();
 97
 98         User user1 = new User();
 99         user1.setName("Jack");
100         user1.setPasswd("Jack");
101         user1.setAge(18 + "");
102         user1.setSex("男");
103         map.put(user1.getName(), user1);
104
105         User user2 = new User();
106         user2.setName("Alice");
107         user2.setPasswd("Alice");
108         user2.setAge(18 + "");
109         user2.setSex("女");
110         map.put(user2.getName(), user2);
111
112         User user3 = new User();
113         user3.setName("Allen");
114         user3.setPasswd("Allen");
115         user3.setAge(20 + "");
116         user3.setSex("女");
117         map.put(user3.getName(), user3);
118
119         return map;
120     }
121
122     @POST
123     @Path("/login")
124     @Produces(MediaType.APPLICATION_JSON)
125     /**
126      * 用户登录
127      * @param username 用户名
128      * @param password 密码
129      * @return Response object
130      */
131     public Response login(@FormParam("username") String username, @FormParam("password") String password) {
132         if (TextUtil.isEmpty(username) || TextUtil.isEmpty(password)) {
133             return null;
134         }
135         User user =  checkUser(username, password);
136         if (user == null) {
137             return null;
138         }
139
140         ObjectMapper mapper = new ObjectMapper();
141
142         GenericEntity<String> payloadEntity;
143         try {
144             payloadEntity = new GenericEntity<String>(
145                     mapper.writeValueAsString(new ResponseDTO(200, "ok", user))) {
146             };
147             return Response.ok(payloadEntity).build();
148         } catch (JsonProcessingException e) {
149             e.printStackTrace();
150             return Response
151                     .ok(" {\"status\": 404,\n\"message\": \"error\",\n\"response\": \"\"}")
152                     .build();
153         }
154     }
155
156     /**
157      * 验证用户是否存在
158      * @param username
159      * @param password
160      * @return User object
161      */
162     private User checkUser(String username, String password) {
163         HashMap<String, User> map = initAllUsers();
164         User user =  map.get(username);
165         if (user != null) {
166             String passwd = user.getPasswd();
167             if (password.equals(passwd)) {
168                 return user;
169             }
170         }
171         return null;
172     }
173
174     @POST
175     @Path("/getAllUsers")
176     @Produces(MediaType.APPLICATION_JSON)
177     /**
178      * 获取所有用户的集合
179      * @return Response object
180      */
181     public Response getAllUsers() {
182         ArrayList<User> list = new ArrayList<User>();
183         User user1 = new User();
184         user1.setName("Jack");
185         user1.setPasswd("Jack");
186         user1.setAge(18 + "");
187         user1.setSex("男");
188         list.add(user1);
189
190         User user2 = new User();
191         user2.setName("Alice");
192         user2.setPasswd("Alice");
193         user2.setAge(18 + "");
194         user2.setSex("女");
195         list.add(user2);
196
197         User user3 = new User();
198         user3.setName("Allen");
199         user3.setPasswd("Allen");
200         user3.setAge(20 + "");
201         user3.setSex("女");
202         list.add(user3);
203
204         ObjectMapper mapper = new ObjectMapper();
205
206         GenericEntity<String> payloadEntity;
207         try {
208             payloadEntity = new GenericEntity<String>(
209                     mapper.writeValueAsString(new ResponseDTO(200, "ok", list))) {
210             };
211             return Response.ok(payloadEntity).build();
212         } catch (JsonProcessingException e) {
213             e.printStackTrace();
214             return Response
215                     .ok(" {\"status\": 404,\n\"message\": \"error\",\n\"response\": \"\"}")
216                     .build();
217         }
218     }
219 }

  我们可以看到,类和接口方法均有注解,这些注解是必须写上去的,因为这些注解指定了一些很关键的信息,客户端请求的时候必须依赖这些注解才能最终找到请求的接口,然后通过接口获取数据并返回。RestService.class上面的注解@Path("/restService")指定了Web Service接口的转发路径,每一个接口方法上面有三个注解,其中,@POST和@GET等表示网络请求方式,请求方式除了@POST和@GET之外,常见的还有put、delete等;@Path("/getUserText")、@Path("/getUserXml")、@Path("/getUserJson")表示的是请求的接口的路径,与类的@Path注解和项目目录一起拼接组成完整的请求路径,@Path注解后面的名字可以按照命名规范自己取;@Produces指定了请求的数据格式,一般为JSON和XML,当然还有TEXT文档等类型。这些注解都指定好之后,那么客户端就可以通过完整的请求路径来访问服务器了,完整的路径格式为:http://[IP地址]:[端口号]/[服务端工程名]/[路径映射]/[接口类路径]/[接口方法路径],例如,客户端要访问我们项目中的login接口,那么客户端请求的完整路径为:http://192.168.254.26:8080/JerseyServer/rest/restService/login,192.168.254.26是我电脑的局域网IP地址,8080为访问apache的端口号,rest是在web.xml中配置的路径映射名。web.xml的配置内容如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
 3 <web-app id="WebApp_ID">
 4     <!-- 同项目名 -->
 5     <display-name>JerseyServer</display-name>
 6     <servlet>
 7         <!-- 为servlet取名 -->
 8         <servlet-name>mobile</servlet-name>
 9         <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
10         <init-param>
11             <param-name>javax.ws.rs.Application</param-name>
12             <!-- 配置自己的资源加载类,用于加载资源 -->
13             <param-value>com.jerseyserver.RestApplication</param-value>
14         </init-param>
15         <load-on-startup>1</load-on-startup>
16     </servlet>
17     <servlet-mapping>
18         <!-- 保持和servlet名一致 -->
19         <servlet-name>mobile</servlet-name>
20         <!-- 分发根路径,用来做路径映射-->
21         <url-pattern>/rest/*</url-pattern>
22     </servlet-mapping>
23     <welcome-file-list>
24         <welcome-file>index.html</welcome-file>
25         <welcome-file>index.htm</welcome-file>
26         <welcome-file>index.jsp</welcome-file>
27         <welcome-file>default.html</welcome-file>
28         <welcome-file>default.htm</welcome-file>
29         <welcome-file>default.jsp</welcome-file>
30     </welcome-file-list>
31 </web-app>

  web.xml中需要注意的项已经标注在了注释里面,其中有一个资源加载类RestApplication.class用于加载各种资源和所需工具,代码如下:

 1 package com.jerseyserver;
 2
 3 import org.glassfish.jersey.filter.LoggingFilter;
 4 import org.glassfish.jersey.server.ResourceConfig;
 5
 6 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
 7
 8 public class RestApplication extends ResourceConfig {
 9     public RestApplication() {
10         // 服务类所在的包路径
11         packages("com.jerseyserver.service");
12         // 注册JSON转换器
13         register(JacksonJsonProvider.class);
14         // 打印访问日志,便于跟踪调试,正式发布可清除
15         register(LoggingFilter.class);
16     }
17 }

  这里,主要指定了服务类的包路径,并用Jackson作为Json的解析转换工具,用glassfish的LoggingFilter打印日志。

  此外,项目中的User实体类代码如下:

 1 package com.jerseyserver.entity;
 2
 3 import javax.xml.bind.annotation.XmlRootElement;
 4
 5 @XmlRootElement
 6 public class User {
 7     private String name;
 8     private String passwd;
 9     private String age;
10     private String sex;
11     public String getName() {
12         return name;
13     }
14     public void setName(String name) {
15         this.name = name;
16     }
17     public String getPasswd() {
18         return passwd;
19     }
20     public void setPasswd(String passwd) {
21         this.passwd = passwd;
22     }
23     public String getAge() {
24         return age;
25     }
26     public void setAge(String age) {
27         this.age = age;
28     }
29     public String getSex() {
30         return sex;
31     }
32     public void setSex(String sex) {
33         this.sex = sex;
34     }
35 }

  注意,需要用@XmlRootElement进行注释才能正常传递实体数据。

  ResponseDTO.class用于包装返回给客户端的JSON数据,包括返回状态码,返回状态值以及实体数据,代码如下:

 1 package com.jerseyserver.entity;
 2
 3 public class ResponseDTO {
 4     private int status;
 5     private String message;
 6     private Object response;
 7
 8     public ResponseDTO(int status, String message, Object response) {
 9         super();
10         this.status = status;
11         this.message = message;
12         this.response = response;
13     }
14
15     public int getStatus() {
16         return status;
17     }
18
19     public void setStatus(int status) {
20         this.status = status;
21     }
22
23     public String getMessage() {
24         return message;
25     }
26
27     public void setMessage(String message) {
28         this.message = message;
29     }
30
31     public Object getResponse() {
32         return response;
33     }
34
35     public void setResponse(Object response) {
36         this.response = response;
37     }
38 }

  好了,到此服务端就搞定了,接下来让我们简单看看android端的实现。客户端的项目结构如下:

  客户端与服务器连接的部分以及各个接口都在WebService.class中,实现代码如下:

  1 package com.jy.jerseyclient.service;
  2
  3 import android.text.TextUtils;
  4 import android.util.Log;
  5
  6 import com.jy.jerseyclient.entity.Response;
  7 import com.jy.jerseyclient.utils.JsonUtil;
  8
  9 import java.io.ByteArrayOutputStream;
 10 import java.io.InputStream;
 11 import java.io.OutputStream;
 12 import java.net.HttpURLConnection;
 13 import java.net.URL;
 14 import java.net.URLEncoder;
 15 import java.util.HashMap;
 16 import java.util.Map;
 17
 18 /**
 19  * Created by xy on 2016/4/10.
 20  */
 21 public class WebService {
 22     private final static String TAG = "WebService";
 23     /**服务器接口根路径*/
 24     private static final String WEB_ROOT = "http://192.168.254.26:8080/JerseyServer/rest/restService/";
 25     /**登录*/
 26     private static final String LOGIN = "login";
 27     /**获取所有用户信息*/
 28     private static final String GET_ALL_USERS = "getAllUsers";
 29
 30     /**
 31      * 登录
 32      * @param username 用户名
 33      * @param password 用户密码
 34      * @return 包含用户信息的Response对象
 35      */
 36     public static Response login(String username, String password) {
 37         String path = WEB_ROOT + LOGIN;
 38         Map<String, String> map = new HashMap<>();
 39         map.put("username", username);
 40         map.put("password", password);
 41         InputStream is = connection(path, map);
 42         if (is != null) {
 43             String content = getStringFromIS(is);
 44             if (content != null) {
 45                 return parseResponse(content);
 46             } else {
 47                 Log.e(TAG, "contentS == null");
 48             }
 49         } else {
 50             Log.e(TAG, "is == null");
 51         }
 52         return null;
 53     }
 54
 55     /**
 56      * 获取所有用户信息
 57      * @return 包含所有用户信息的Response对象
 58      */
 59     public static Response getAllUsers() {
 60         String path = WEB_ROOT + GET_ALL_USERS;
 61         InputStream is = connection(path, null);
 62         if (is != null) {
 63             String content = getStringFromIS(is);
 64             if (content != null) {
 65                 return parseResponse(content);
 66             } else {
 67                 Log.e(TAG, "contentS == null");
 68             }
 69         } else {
 70             Log.e(TAG, "is == null");
 71         }
 72         return null;
 73     }
 74
 75     /**
 76      * 解析服务器返回的JSON数据
 77      * @param content JSON数据
 78      * @return Response对象
 79      */
 80     private static Response parseResponse(String content) {
 81         Log.e(TAG, "state======" + content);
 82         if (TextUtils.isEmpty(content)) {
 83             return null;
 84         }
 85         return JsonUtil.getEntity(content, Response.class);
 86     }
 87
 88     /**
 89      * 得到服务器返回的输入流数据
 90      * @param path 请求路径
 91      * @param map 包含密文的map集合
 92      * @return 服务器返回的数据
 93      */
 94     private static InputStream connection(String path, Map<String, String> map) {
 95         try {
 96             String pathUrl = path;
 97             URL url = new URL(pathUrl);
 98             HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
 99             StringBuffer sb = new StringBuffer();
100             if (map != null) {
101                 if (!map.isEmpty()) {
102                     for (Map.Entry<String, String> entry : map.entrySet()) {
103                         sb.append(entry.getKey()).append(‘=‘).append(URLEncoder.encode(entry.getValue(), "UTF-8")).append(‘&‘);
104                     }
105                     sb.deleteCharAt(sb.length() - 1);
106                 }
107             }
108             byte[] entityData = sb.toString().getBytes();
109             httpConn.setDoOutput(true);
110             httpConn.setDoInput(true);
111             httpConn.setUseCaches(false);
112             httpConn.setRequestMethod("POST");
113             //设置请求服务器连接的超时时间
114             httpConn.setConnectTimeout(5 * 1000);
115             //设置服务器返回数据的超时时间
116             //httpConn.setReadTimeout(30 * 1000);
117             httpConn.setRequestProperty("Content-length", "" + entityData.length);
118             httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
119             OutputStream outStream = httpConn.getOutputStream();
120             outStream.write(entityData);
121             outStream.flush();
122             outStream.close();
123             int responseCode = httpConn.getResponseCode();
124             if (HttpURLConnection.HTTP_OK == responseCode) {
125                 InputStream is = httpConn.getInputStream();
126                 return is;
127             }
128         } catch (Exception ex) {
129             ex.printStackTrace();
130             return null;
131         }
132         return null;
133     }
134
135     /**
136      * 将服务器返回的输入流转换为字符串
137      * @param is 服务器返回的输入流
138      * @return 输入流转换之后的字符串
139      */
140     public static String getStringFromIS(InputStream is) {
141         byte[] buffer = new byte[1024];
142         ByteArrayOutputStream os = new ByteArrayOutputStream();
143         try {
144             int len = -1;
145             while ((len = is.read(buffer)) != -1) {
146                 os.write(buffer, 0, len);
147             }
148             os.close();
149             is.close();
150         } catch (Exception e) {
151             e.printStackTrace();
152         }
153         String reString = new String(os.toByteArray());
154
155         Log.e(TAG, "geStringFromIS reString======" + reString);
156
157         return reString;
158     }
159 }

  其中,使用HttpURLConnection向服务器发送请求数据,用GSON来解析JSON数据,在解析JSON的时候,为了解析的方便,我对GSON进行了进一步封装,以便在接口中对所有的实体和JSON进行统一的转换,其实现代码如下:

 1 package com.jy.jerseyclient.utils;
 2
 3 import android.util.Log;
 4
 5 import com.google.gson.Gson;
 6 import com.google.gson.JsonArray;
 7 import com.google.gson.JsonElement;
 8 import com.google.gson.JsonParser;
 9
10 import java.util.ArrayList;
11 import java.util.List;
12
13 /**
14  * Created by xy on 2015/12/29.
15  */
16 public class JsonUtil {
17     private final static String TAG = "JsonUtil";
18     /**
19      * 将对象转换为json字符串
20      * @param obj   对象,可以是实体对象,List对象,Map对象等
21      * @return   json字符串
22      */
23     public static String toJson(Object obj) {
24         if (obj == null) {
25             throw new IllegalArgumentException("illegal argument");
26         }
27
28         return new Gson().toJson(obj);
29     }
30
31     /**
32      * 将json字符串转换为实体对象
33      * @param jsonString   json字符串
34      * @param cls   实体类
35      * @param <T>  泛型参数
36      * @return   实体对象
37      */
38     public static <T> T getEntity(String jsonString, final Class<T> cls) {
39         T t;
40         try {
41             Gson gson = new Gson();
42             t = gson.fromJson(jsonString, cls);
43         } catch (Exception e) {
44             e.printStackTrace();
45             Log.e(TAG, jsonString + " 无法转换为 " + cls.getSimpleName() + " 对象");
46             return null;
47         }
48
49         return t;
50     }
51
52     /**
53      * 将json字符串转换为List对象
54      * @param jsonString   json字符串
55      * @param cls   实体类
56      * @param <T>   泛型参数
57      * @return   实体List对象
58      */
59     public static <T> List<T> getEntityList(String jsonString, final Class<T> cls) {
60         List<T> list = new ArrayList<T>();
61
62         JsonArray array = new JsonParser().parse(jsonString).getAsJsonArray();
63
64         for (final JsonElement elem : array) {
65             list.add(new Gson().fromJson(elem, cls));
66         }
67
68         return list;
69     }
70 }

  同服务端一样,客户端也用一个Response类封装实体对象的信息。

  Response.class的代码如下:

 1 package com.jy.jerseyclient.entity;
 2
 3 import java.io.Serializable;
 4
 5 /**
 6  * Description:
 7  * Author: xy
 8  * Date: 2016/4/12 14:49
 9  */
10 public class Response implements Serializable {
11     private static final long serialVersionUID = 8980216391057926016L;
12     public int status;
13     public String message;
14     public Object response;
15
16     public int getStatus() {
17         return status;
18     }
19
20     public void setStatus(int status) {
21         this.status = status;
22     }
23
24     public String getMessage() {
25         return message;
26     }
27
28     public void setMessage(String message) {
29         this.message = message;
30     }
31
32     public Object getResponse() {
33         return response;
34     }
35
36     public void setResponse(Object response) {
37         this.response = response;
38     }
39 }

  User.class的代码如下:

 1 package com.jy.jerseyclient.entity;
 2
 3 import java.io.Serializable;
 4
 5 /**
 6  * Created by 123 on 2016/4/10.
 7  */
 8 public class User implements Serializable {
 9     private static final long serialVersionUID = -1044671445753823751L;
10     private String name;
11     private String passwd;
12     private String sex;
13     private String age;
14
15     public String getName() {
16         return name;
17     }
18
19     public void setName(String name) {
20         this.name = name;
21     }
22
23     public String getPasswd() {
24         return passwd;
25     }
26
27     public void setPasswd(String passwd) {
28         this.passwd = passwd;
29     }
30
31     public String getSex() {
32         return sex;
33     }
34
35     public void setSex(String sex) {
36         this.sex = sex;
37     }
38
39     public String getAge() {
40         return age;
41     }
42
43     public void setAge(String age) {
44         this.age = age;
45     }
46
47     @Override
48     public String toString() {
49         return "User{" +
50                 "name=‘" + name + ‘\‘‘ +
51                 ", passwd=‘" + passwd + ‘\‘‘ +
52                 ", sex=‘" + sex + ‘\‘‘ +
53                 ", age=‘" + age + ‘\‘‘ +
54                 ‘}‘;
55     }
56 }

  NetworkUtil.class用于网络状况的判断,实现代码如下:

 1 package com.jy.jerseyclient.utils;
 2
 3 import android.content.Context;
 4 import android.net.ConnectivityManager;
 5
 6 /**
 7  * 网络相关工具类
 8  * @author xy
 9  *
10  */
11 public class NetworkUtil {
12     /**
13      * 判断是否有可用网络
14      * @param context
15      * @return
16      */
17     public static boolean isNetWorkOpened(Context context) {
18         ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
19         if (connManager.getActiveNetworkInfo() != null) {
20             return connManager.getActiveNetworkInfo().isAvailable();
21         }
22         return false;
23     }
24 }

  好了,剩下的就是登陆界面和主界面了,Activity的布局和逻辑都非常简单,我直接把代码贴上。

LoginActivty:

  1 package com.jy.jerseyclient;
  2
  3 import android.content.Intent;
  4 import android.os.AsyncTask;
  5 import android.os.Bundle;
  6 import android.support.v7.app.AppCompatActivity;
  7 import android.text.TextUtils;
  8 import android.util.Log;
  9 import android.view.View;
 10 import android.widget.Button;
 11 import android.widget.EditText;
 12 import android.widget.Toast;
 13
 14 import com.jy.jerseyclient.entity.Response;
 15 import com.jy.jerseyclient.entity.User;
 16 import com.jy.jerseyclient.service.WebService;
 17 import com.jy.jerseyclient.utils.JsonUtil;
 18 import com.jy.jerseyclient.utils.NetworkUtil;
 19
 20 public class LoginActivity extends AppCompatActivity {
 21     private final static String TAG = "LoginActivity";
 22     private EditText edt_username;
 23     private EditText edt_password;
 24     private Button btn_login;
 25
 26     @Override
 27     protected void onCreate(Bundle savedInstanceState) {
 28         super.onCreate(savedInstanceState);
 29         setContentView(R.layout.activity_login);
 30
 31         initView();
 32         initEvent();
 33     }
 34
 35     private void initView() {
 36         edt_username = (EditText) findViewById(R.id.edt_username);
 37         edt_password = (EditText) findViewById(R.id.edt_password);
 38         btn_login = (Button) findViewById(R.id.btn_login);
 39     }
 40
 41     private void initEvent() {
 42         btn_login.setOnClickListener(new View.OnClickListener() {
 43             @Override
 44             public void onClick(View v) {
 45                 final String username = edt_username.getText().toString();
 46                 if (TextUtils.isEmpty(username)) {
 47                     Toast.makeText(LoginActivity.this,
 48                             "user name must not be empty", Toast.LENGTH_SHORT).show();
 49                     return;
 50                 }
 51                 final String password = edt_password.getText().toString();
 52                 if (TextUtils.isEmpty(password)) {
 53                     Toast.makeText(LoginActivity.this,
 54                             "password must not be empty", Toast.LENGTH_SHORT).show();
 55                     return;
 56                 }
 57                 if (NetworkUtil.isNetWorkOpened(LoginActivity.this)) {
 58                     new AsyncTask<String, Integer, Response>() {
 59                         @Override
 60                         protected Response doInBackground(String... params) {
 61                             Response response = WebService.login(username, password);
 62                             return response;
 63                         }
 64
 65                         @Override
 66                         protected void onPostExecute(Response response) {
 67                             if (response == null) {
 68                                 Toast.makeText(LoginActivity.this, "login failed,response is null",
 69                                         Toast.LENGTH_SHORT).show();
 70                             } else if (200 == response.getStatus()) {
 71                                 Log.e(TAG, "user======" + response.toString());
 72                                 Object obj = response.getResponse();
 73                                 if (obj == null) {
 74                                     Toast.makeText(LoginActivity.this,
 75                                             "login failed,the response field is null",
 76                                             Toast.LENGTH_SHORT).show();
 77                                 } else {
 78                                     User user = JsonUtil.getEntity(obj.toString(), User.class);
 79                                     if (user == null) {
 80                                         Toast.makeText(LoginActivity.this, "login failed,illegal json",
 81                                                 Toast.LENGTH_SHORT).show();
 82                                     } else {
 83                                         Toast.makeText(LoginActivity.this, "login succeed",
 84                                                 Toast.LENGTH_SHORT).show();
 85                                         Intent intent = new Intent(LoginActivity.this, MainActivity.class);
 86                                         intent.putExtra("user", user);
 87                                         startActivity(intent);
 88                                         finish();
 89                                     }
 90                                 }
 91                             } else {
 92                                 Toast.makeText(LoginActivity.this,
 93                                         "login failed," + response.getMessage(),
 94                                         Toast.LENGTH_SHORT).show();
 95                             }
 96                             super.onPostExecute(response);
 97                         }
 98                     }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
 99                 } else {
100                     Toast.makeText(LoginActivity.this,
101                             "network is not available", Toast.LENGTH_SHORT).show();
102                 }
103             }
104         });
105     }
106 }

  LoginActivty的布局文件:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context=".MainActivity"
 8     android:orientation="vertical"
 9     android:background="#FFFFFF"
10     android:gravity="center" >
11
12     <RelativeLayout
13         android:layout_width="match_parent"
14         android:layout_height="wrap_content" >
15
16         <TextView
17             android:id="@+id/tv_username"
18             android:layout_width="wrap_content"
19             android:layout_height="wrap_content"
20             android:text="用户名"
21             android:textSize="18sp"
22             android:layout_alignParentLeft="true" />
23
24         <EditText
25             android:id="@+id/edt_username"
26             android:layout_width="match_parent"
27             android:layout_height="wrap_content"
28             android:layout_alignParentRight="true"
29             android:layout_toRightOf="@id/tv_username"
30             android:textSize="18sp" />
31
32     </RelativeLayout>
33
34     <RelativeLayout
35         android:layout_width="match_parent"
36         android:layout_height="wrap_content"
37         android:layout_marginTop="20dp" >
38
39         <TextView
40             android:id="@+id/tv_password"
41             android:layout_width="wrap_content"
42             android:layout_height="wrap_content"
43             android:text="密 码"
44             android:textSize="18sp"
45             android:layout_alignParentLeft="true" />
46
47         <EditText
48             android:id="@+id/edt_password"
49             android:layout_width="match_parent"
50             android:layout_height="wrap_content"
51             android:layout_alignParentRight="true"
52             android:layout_toRightOf="@id/tv_password"
53             android:inputType="textPassword"
54             android:textSize="18sp" />
55     </RelativeLayout>
56
57     <Button
58         android:id="@+id/btn_login"
59         android:layout_width="match_parent"
60         android:layout_height="wrap_content"
61         android:text="登录"
62         android:textSize="18sp"
63         android:gravity="center"
64         android:layout_marginTop="50dp" />
65 </LinearLayout>

  MainActivity:

  1 package com.jy.jerseyclient;
  2
  3 import android.content.Intent;
  4 import android.os.AsyncTask;
  5 import android.os.Bundle;
  6 import android.support.v7.app.AppCompatActivity;
  7 import android.util.Log;
  8 import android.view.View;
  9 import android.widget.Button;
 10 import android.widget.TextView;
 11 import android.widget.Toast;
 12
 13 import com.jy.jerseyclient.entity.Response;
 14 import com.jy.jerseyclient.entity.User;
 15 import com.jy.jerseyclient.service.WebService;
 16 import com.jy.jerseyclient.utils.JsonUtil;
 17 import com.jy.jerseyclient.utils.NetworkUtil;
 18
 19 import java.util.ArrayList;
 20
 21 public class MainActivity extends AppCompatActivity {
 22     private final static String TAG = "MainActivity";
 23     private TextView tv_welcome;
 24     private Button btn_show_all_users;
 25
 26     @Override
 27     protected void onCreate(Bundle savedInstanceState) {
 28         super.onCreate(savedInstanceState);
 29         setContentView(R.layout.activity_main);
 30
 31         initView();
 32         initEvent();
 33         initData();
 34     }
 35
 36     private void initView() {
 37         tv_welcome = (TextView) findViewById(R.id.tv_welcome);
 38         btn_show_all_users = (Button) findViewById(R.id.btn_show_all_users);
 39     }
 40
 41     private void initEvent() {
 42         btn_show_all_users.setOnClickListener(new View.OnClickListener() {
 43             @Override
 44             public void onClick(View v) {
 45                 if (NetworkUtil.isNetWorkOpened(MainActivity.this)) {
 46                     new AsyncTask<String, Integer, Response>() {
 47                         @Override
 48                         protected Response doInBackground(String... params) {
 49                             Response response = WebService.getAllUsers();
 50                             return response;
 51                         }
 52
 53                         @Override
 54                         protected void onPostExecute(Response response) {
 55                             if (response == null) {
 56                                 Toast.makeText(MainActivity.this,
 57                                         "request failed,response is null",
 58                                         Toast.LENGTH_SHORT).show();
 59                             } else if (200 == response.getStatus()) {
 60                                 Log.e(TAG, "user======" + response.toString());
 61                                 Object obj = response.getResponse();
 62                                 if (obj == null) {
 63                                     Toast.makeText(MainActivity.this,
 64                                             "request failed,the response field is null",
 65                                             Toast.LENGTH_SHORT).show();
 66                                 } else {
 67                                     ArrayList<User> users = (ArrayList<User>)
 68                                             JsonUtil.getEntityList(obj.toString(), User.class);
 69                                     if (users == null) {
 70                                         Toast.makeText(MainActivity.this,
 71                                                 "request failed,illegal json",
 72                                                 Toast.LENGTH_SHORT).show();
 73                                     } else {
 74                                         StringBuilder allUserInfo = new StringBuilder();
 75                                         for (User u : users) {
 76                                             allUserInfo.append(u.getName() + ":" + u.getSex() + ","
 77                                                     + u.getAge() + "\n");
 78                                         }
 79                                         tv_welcome.setText(allUserInfo);
 80                                     }
 81                                 }
 82                             } else {
 83                                 Toast.makeText(MainActivity.this,
 84                                         "request failed," + response.getMessage(),
 85                                         Toast.LENGTH_SHORT).show();
 86                             }
 87                             super.onPostExecute(response);
 88                         }
 89                     }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
 90                 } else {
 91                     Toast.makeText(MainActivity.this,
 92                             "network is not available", Toast.LENGTH_SHORT).show();
 93                 }
 94             }
 95         });
 96     }
 97
 98     private void initData() {
 99         Intent intent = getIntent();
100         if (intent != null) {
101             User user = (User) intent.getExtras().getSerializable("user");
102             if (user != null) {
103                 tv_welcome.setText("Welcome," + user.getName());
104             }
105         }
106     }
107 }

  MainActivty的布局文件:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     android:fitsSystemWindows="true"
 8     tools:context=".MainActivity">
 9
10     <TextView
11         android:id="@+id/tv_welcome"
12         android:layout_width="match_parent"
13         android:layout_height="wrap_content"
14         android:text="欢迎您"
15         android:textSize="18sp" />
16
17     <Button
18         android:id="@+id/btn_show_all_users"
19         android:layout_width="match_parent"
20         android:layout_height="wrap_content"
21         android:text="显示所有用户"
22         android:textSize="18sp"
23         android:layout_below="@id/tv_welcome"
24         android:layout_marginTop="20dp" />
25
26 </RelativeLayout>

  最后,别忘了配置清单文件,如下所示:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.jy.jerseyclient" >
 4
 5     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 6     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 7     <uses-permission android:name="android.permission.INTERNET" />
 8     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 9
10     <application
11         android:allowBackup="true"
12         android:icon="@mipmap/ic_launcher"
13         android:label="@string/app_name"
14         android:supportsRtl="true"
15         android:theme="@style/AppTheme" >
16         <activity
17             android:name=".LoginActivity"
18             android:label="@string/app_name"
19             android:theme="@style/AppTheme.NoActionBar" >
20             <intent-filter>
21                 <action android:name="android.intent.action.MAIN" />
22
23                 <category android:name="android.intent.category.LAUNCHER" />
24             </intent-filter>
25         </activity>
26
27         <activity android:name=".MainActivity" />
28     </application>
29
30 </manifest>

  好了,到此客户端和服务端均已大功告成,整个项目其实就是用最简单的模型来说明怎样利用Jersey这个框架来为我们的移动端搭建后台接口,其中,难免有诸多纰漏之处,请大家多多批评指正,谢谢!

  最后,附上项目源码的下载链接:

  客户端:http://download.csdn.net/detail/owoxiangxin12/9492575

  服务端:http://download.csdn.net/detail/owoxiangxin12/9492571

时间: 2024-10-27 09:51:43

用Jersey为Android客户端开发Restful Web Service的相关文章

使用JAX-RS创建RESTful Web Service

http://blog.csdn.net/withiter/article/details/7349795 本章介绍REST架构.RESTful web service和JAX-RS(Java API for RESTful Web Service,JSR 311).JAX-RS的参考实现Jersey实现了对JSR 311中定义的注解的支持,使得使用Java编程语言开发RESTful web service变得简单.如果是使用GalssFish服务器,可以使用Update Tool安装Jerse

使用 Jersey 和 Apache Tomcat 构建 RESTful Web 服务

RESTful Web 服务简单介绍 REST 在 2000 年由 Roy Fielding 在博士论文中提出,他是 HTTP 规范 1.0 和 1.1 版的首席作者之中的一个. REST 中最重要的概念是资源(resources),使用全球 ID(通常使用 URI)标识. client应用程序使用 HTTP 方法(GET/ POST/ PUT/ DELETE)操作资源或资源集.RESTful Web 服务是使用 HTTP 和 REST 原理实现的 Web 服务.通常,RESTful Web 服

Maven+jersey快速构建RESTful Web service集成mongodb-短小而精悍-值得拥有

源码下载地址:http://pan.baidu.com/s/1gdIN4fp 转载请注明原著地址:http://blog.csdn.net/tianyijavaoracle/article/details/41708217 Jersey是JAX-RS(JSR311)开源参考实现用于构建RESTful Web service.此外Jersey还提供一些额外的API和扩展机制,所以开发人员能够按照自己的需要对Jersey进行扩展 理论的东西在这里我就不多说了!这个实例是实现了REST的三个基本get

基于jersey和Apache Tomcat构建Restful Web服务(一)

基于jersey和Apache Tomcat构建Restful Web服务(一) 现如今,RESTful架构已然成为了最流行的一种互联网软件架构,它结构清晰.符合标准.易于理解.扩展方便,所以得到越来越多网站的采用.那么问题来了,它是什么呢? 起源 REST(Representational state transfer)在 2000 年由 Roy Fielding 在博士论文中提出,他是 HTTP 规范 1.0 和 1.1 版的首席作者之一. REST 中最重要的概念是资源(resources

JAX-RS 方式的 RESTful Web Service 开发

JAX-RS 方式的 RESTful Web Service 开发 ——基于 CXF+Spring 的实现 Web Service 目前在风格上有两大类,一个是基于 SOAP 协议,一个是完全遵循 HTTP 协议规范的 RESTful 风格. SOAP 方式的 web service 已经很成熟了,应用也很广,已经成为 Web Service 的工业标准.不过 RESTful Web Service 现在势头越来越猛,特别是其灵活性以及与 Ajax 的完美结合,让人爱不释手,很有必要了解一下.

上门洗车APP --- Android客户端开发 之 网络框架封装(二)

上门洗车APP --- Android客户端开发 之 网络框架封装(二) 前几篇博文中给大家介绍了一下APP中的基本业务及开发本项目使用的网络架构: 上门洗车APP --- Android客户端开发 前言及业务简介 上门洗车APP --- Android客户端开发 之 网络框架封装介绍(一) 本篇接着给大家分享网络框架封装,相信感兴趣的朋友已经对上篇博文中的一些开源项目有了些许了解,这里继续为大家介绍关于GenericDataManager 通用网络管理类中的 dataRequest 方法 和

上门洗车APP --- Android客户端开发 前言及业务简介

上门洗车APP --- Android客户端开发 前言及业务简介 最近有些小累,私自接了一个项目,利用空余时间在开发,也比较乏力,时间和精力上有时候分配不过来,毕竟公司的事情要忙,只能自己抽时间来完成了,男人嘛,累点好,舒服是留给死人的(套用了一句逼格的话,o_O"~). 项目目前还在开发中,接口的调试以及业务的分析,框架的搭建,客户端这边已进行的差不多了,明天端午节,先祝大家节日快乐,汗~,看了下时间,貌似博客写完发表已经是端午节了,好吧,咕嘟咕嘟...... 大晚上的也是睡不着,写写博客,总

CSDN Android客户端开发(二):详解如何基于Java用Jsoup爬虫HTML数据

本文参考链接详细介绍如何使用Jsoup包抓取HTML数据,是一个纯java工程,并将其打包成jar包.希望了解如何用java语言爬虫网页的可以看下. 杂家前文就又介绍用HTTP访问百度主页得到html的string字符串,但html的文本数据如果不经过处理就是个文本字符串没有任何效果的.所谓的浏览器就是负责将文本的html"翻译"成看到的界面.在前文有介绍,这个csdn的客户端app分首页.业界.移动.研发.程序员.云计算五大类.以业界为例,http://news.csdn.net/ 

iOS客户端开发与Web前端开发

转载自:http://blog.cnbang.net/tech/1813/不知不觉做iOS客户端开发已经半年多了,了解到iOS客户端开发与Web前端开发的一些异同,写一下. 版本升级.用户角度上看,客户端升级必须让用户手动下载整个新的安装包覆盖安装,而web的升级无需用户做任何事情.开发角度上看,如果客户端有个小bug需要紧急修复,需要修复完后打包一个完成的安装包,给一个版本号,发布给用户升级.而web只需要修改后台的某些文件,然后传到自己的服务器,用多快速的迭代开发方式都没问题.web没有版本