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

  我们可以看到,类和接口方法均有注解,这些注解是必须写上去的,因为这些注解指定了一些很关键的信息,客户端请求的时候必须依赖这些注解才能最终找到请求的接口,然后通过接口获取数据并返回。RestService.java上面的注解@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.java用于加载各种资源和所需工具,代码如下:

  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.java用于包装返回给客户端的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.java中,实现代码如下:
  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.java的代码如下:

  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.java的代码如下:

  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.java用于网络状况的判断,实现代码如下:

  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   ②https://github.com/monkey1992/JerseyClient

  服务端:①http://download.csdn.net/detail/owoxiangxin12/9492571   ②https://github.com/monkey1992/JerseyServer

 

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

  1. 使用Jersey和Jetty开发RESTful Web service

    Jersey RESTful 框架是开源的RESTful框架, 实现了JAX-RS (JSR 311 & JSR 339) 规范,是JAX-RS的参考实现,并且提供了更多的特性和工具,简化了R ...

  2. 使用JAX-RS创建RESTful Web Service

    guice resteasy http://www.cnblogs.com/ydxblog/p/7891224.html http://blog.csdn.net/withiter/article/d ...

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

    基于jersey和Apache Tomcat构建Restful Web服务(一) 现如今,RESTful架构已然成为了最流行的一种互联网软件架构,它结构清晰.符合标准.易于理解.扩展方便,所以得到越来 ...

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

    作者: Yi Ming Huang, 软件工程师, IBM Dong Fei Wu, 软件工程师, IBM Qing Guo, 软件工程师, IBM 出处: http://www.ibm.com/de ...

  5. JAX-RS 方式的 RESTful Web Service 开发

    JAX-RS 方式的 RESTful Web Service 开发 ——基于 CXF+Spring 的实现 Web Service 目前在风格上有两大类,一个是基于 SOAP 协议,一个是完全遵循 H ...

  6. 【转】 Build a RESTful Web service using Jersey and Apache Tomcat 2009

    Build a RESTful Web service using Jersey and Apache Tomcat Yi Ming Huang with Dong Fei Wu, Qing GuoP ...

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

    转载自:http://blog.cnbang.net/tech/1813/不知不觉做iOS客户端开发已经半年多了,了解到iOS客户端开发与Web前端开发的一些异同,写一下. 版本升级.用户角度上看,客 ...

  8. 基于jersey和Apache Tomcat构建Restful Web服务(二)

    基于jersey和Apache Tomcat构建Restful Web服务(二) 上篇博客介绍了REST以及Jersey并使用其搭建了一个简单的“Hello World”,那么本次呢,再来点有趣的东西 ...

  9. 【转】基于CXF Java 搭建Web Service (Restful Web Service与基于SOAP的Web Service混合方案)

    转载:http://www.cnblogs.com/windwithlife/archive/2013/03/03/2942157.html 一,选择一个合适的,Web开发环境: 我选择的是Eclip ...

随机推荐

  1. 2018ACM山东省赛 Games(dp取数)

    Games Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Problem Description Alice and Bob ...

  2. uoj#340. 【清华集训2017】小 Y 和恐怖的奴隶主(矩阵加速)

    传送门 uoj上的数据太毒了--也可能是我人傻常数大的缘故-- 三种血量的奴隶主加起来不超过\(8\)个,可以枚举每种血量的奴隶主个数,那么总的状态数只有\(165\)种,设\(dp_{t,i,j,k ...

  3. Macbook 修复Office Excel 异常问题

    manbook 版本的office excel 在一次崩溃后,每次打开excel 文件都会弹出以下烦人的错误告警,并且每次都会重新打开很多过去保存过的excel 文件. “在应用程序意外退出之前,Ex ...

  4. eclipse 通过svn导入maven工程

    http://blog.csdn.net/zdnlp/article/details/7238194

  5. shell脚本编程入门

    Linux的Shell种类众多,这里我们关注的重点是Bash. 基本语法 环境变量$PATH IO重定向:  以<改变标准输入 如:tr -d '\r' < dos-file.txt 以& ...

  6. thinkphp5使用前置后置操作

    下面举个例子,前置删除的例子   模型事件只可以在调用模型的方法才能生效,使用查询构造器通过Db类操作是无效的   控制器中实例化类   $cate=model('cate'); $cate-> ...

  7. Linux上传下载工具 lrzsz

  8. Spring @Import 注解

    @Import  导入某个bean 文件 @Configuration @Import({User.class,MyImportSelector.class,MyImportBeanDefinitio ...

  9. Token认证登录以及权限控制

    IdentityServer4实现Token认证登录以及权限控制   相关知识点 不再对IdentityServer4做相关介绍,博客园上已经有人出了相关的系列文章,不了解的可以看一下: 蟋蟀大神的: ...

  10. NET Core源代码通过Autofac实现依赖注入

    查看.NET Core源代码通过Autofac实现依赖注入到Controller属性   阅读目录 一.前言 二.使用Autofac 三.最后 回到目录 一.前言 在之前的文章[ASP.NET Cor ...