web restful
在上一篇文章中 ,我讨论了用于构建RESTful Web服务和动态Web应用程序的多层体系结构。 我在表示层中为Ajax / Google Web Toolkit(GWT)和来自外部客户端应用程序的调用提出了资源请求处理程序(RRH),并提出了用于处理来自浏览器的请求并生成输出以在其中显示的浏览器请求处理程序(BRH)。浏览器。 两个处理程序共享一个公共的业务逻辑层,然后与数据访问层进行交互。 在示例应用程序中,应用程序层是使用Java™代码构建的。 本文将Jersey框架用于RESTful Web服务。 用于MVC,导航和JDBC的Spring框架; 和MySQL作为数据库。 Eclipse用作IDE。 该示例应用程序将部署在Tomcat中。 该示例应用程序是一个简单的虚构应用程序,供国家大气研究中心(NCAR)管理员签约NCAR员工。
场景
在这种情况下,管理员使用浏览器界面来注册新的NCAR雇员。 在NCAR,有四个实验室,每个实验室有不同的部门:
- 计算与信息系统实验室
- 地球和太阳系统实验室
- 地球观测实验室
- 研究应用实验室
应用程序中的注册界面包括以下字段:
- 用户名
- 密码
- 姓
- 名字
- 电子邮件
- 员工所在的实验室和部门
在这些字段中,实验室字段和部门字段都是可选的选项菜单,并且用户名必须唯一。 如果已经使用了用户名,并且管理员尝试再次输入它,浏览器将显示警告,并且用户名字段将被清除。
划分选项菜单中的项目列表取决于在实验室选项菜单中选择的实验室。 首次打开接口时,除法字段被禁用。 管理员选择实验室后,将启用分区选项菜单,但仅包含所选实验室的分区。 管理员填写完信息并单击Submit之后,系统会将新用户添加到MySQL数据库中,并显示一条成功消息。
系统管理员需要能够运行批处理过程,以上传新用户以及下载已注册的用户。 批处理程序可以使用Ruby,Python,Pearl或Java代码实现。 我以Ruby为例。 浏览器界面或RESTful Web服务都不需要身份验证。
注意:虚构的应用程序尚未投入生产。 异常处理,日志记录,身份验证和数据验证(仅举几例)可能需要在现实世界中使用。
组成部分
表1列出了组件以及它们在文件夹结构中的组织方式。
表1.文件夹结构
层级 | 图层和脚本 | 文件位置 |
---|---|---|
客户层 | Ajax脚本 | WebRoot / js |
JSP页面 | WebRoot / WEB-INF / jsp | |
Ruby脚本 | 客户/Ruby | |
展示层 | 表示层-浏览器请求处理程序 | src / edu / ucar / cisl / ncarUsers / presentation / brh |
表示层-资源请求处理程序 | src / edu / ucar / cisl / ncarUsers / presentation / rrh | |
业务逻辑层 | src / edu / ucar / cisl / ncarUsers / bll | |
资料存取层 | src / edu / ucar / cisl / ncarUsers / dal | |
数据存储层 | MySql脚本 | db / setup.sql |
下载源代码并将其解压缩到您的C驱动器。 现在,您应该看到一个新文件夹C:\ ncarUsers。 在此文件夹下,您可以看到表1列出的完整文件夹结构。下载文件包括所有源代码以及MySQL Connector / J Driver 5.1,Jersey 1.0,Spring Framework 2.5.5和Spring Web Flow的所有必需库。 2.0.2,所有这些内容对于本演示来说就足够了,除非您需要试用较新的版本。
搭建环境
下载以下软件包,并根据各自网站上的安装指南进行安装。 (请参阅相关主题的链接。)
- Apache Tomcat 6.x
- MySQL 5.1
- 适用于Java EE开发人员的Eclipse IDE(我使用的是Eclipse Europa版本,但是较新的版本也可以使用。)
- Ruby
安装Ruby之后,运行命令gem install –remote
下载并安装json(图1)和rest-open-uri库。
图1. Ruby gen install json库
创建数据库
我在本文中使用MySQL数据库。 要创建MySQL服务器实例,请使用MySQL服务器实例配置向导。 实例的默认名称是MYSQL。 要启动MySQL命令行工具,请运行mysql –u root –p MYSQL
。 (您需要提供在上一步中用于root登录的密码。)接下来,在命令行工具中运行source c:/ncarUsers/db/setup.sql
以创建数据库(ncar_users),这是一个MySQL用户(教程) )和密码(教程),以及本文的表格(图2)。 该脚本还将数据插入实验室和部门表中。
图2.运行脚本文件setup.sql
在Eclipse中配置Tomcat服务器
要配置Tomcat服务器:
- 打开Eclipse并选择File > New > Other 。
- 从列表中选择服务器 。
图3.在Eclipse中配置Tomcat服务器-步骤1
- 单击下一步 。 在窗口中,选择Apache > Tomcat v6.0 Server。
图4.在Eclipse中配置Tomcat服务器-步骤2
- 单击下一步 。 在下一个窗口中,单击浏览...,然后选择Tomcat的安装位置。
图5.在Eclipse中配置Tomcat服务器-步骤3
- 点击完成 。
在Eclipse中创建Web项目ncarUsers
要创建Web项目:
- 选择文件 > 新建 > 项目… 。 打开网页 。
- 单击动态Web项目 。
图6.在Eclipse中创建Web项目ncarUsers-步骤1
- 单击下一步 。 在新窗口中,在项目名称字段中输入
ncarUsers
。图7.在Eclipse中创建Web项目ncarUsers-步骤2
- 单击下一步 。
- 在“项目构面”窗口中再次单击“ 下一步” 。
- 在“ Web模块”窗口的“内容目录”字段中,将WebContent更改为WebRoot。
- 点击完成 。
图8.在Eclipse中创建Web项目ncarUsers-步骤3
从文章下载中导入文件
要导入文件:
- 在Project Explorer中,右键单击ncarUsers,然后选择Import> Import… 。
- 在Import窗口中,单击General> File System (图9)。
图9.将文章下载导入ncarUsers项目-步骤1
- 单击下一步 。
- 在“文件系统”窗口中,单击“ 浏览...” ,然后选择C:\ ncarUsers 。
- 选中ucarUsers的复选框(图10)。
图10.将文章下载导入ncarUsers项目-步骤2
导入后,Project Explorer应该如图11所示。
图11.项目导入的结果
如果您想跳过以下实现域对象,数据访问层(DAL),业务逻辑层(BLL),表示层(包括浏览器请求处理程序和资源请求处理程序)以及客户端应用程序的部分,则可以跳过到从Eclipse运行应用程序一节。
实现领域对象
域对象为应用程序问题域建模。 我实现了三个域对象:用户(清单1),实验室(清单2)和部门(清单3)。
清单1. edu.ucar.cisl.ncarUsers.domain.User
1. package edu.ucar.cisl.ncarUsers.domain;
2. import java.io.Serializable;
3. public class User implements Serializable {
4. protected int ID;
5. protected String userName;
6. protected String password;
7. protected String firstName;
8. protected String lastName;
9. protected String email;
10. protected int lab;
11. protected int division;
12. ... //getters and setters
13. }
清单2. edu.ucar.cisl.ncarUsers.domain.Lab
1. package edu.ucar.cisl.ncarUsers.domain;
2. import java.io.Serializable;
3. public class Lab implements Serializable {
4. protected int ID;
5. protected String shortName;
6. protected String name;
7. protected String description;
8. ... //getters and setters
9. }
清单3. edu.ucar.cisl.ncarUsers.domain.Division
1. package edu.ucar.cisl.ncarUsers.domain;
2. import java.io.Serializable;
3. public class Division implements Serializable {
4. protected int ID;
5. protected String shortName;
6. protected String name;
7. protected String description;
8. protected int labID;
9. ... //getters and setters
10. }
实施数据访问层
在数据访问层(DAL)中,我创建了三个数据访问对象: UserDAO
, LabDAO
和DivisionDAO
。 数据访问对象可能与域对象匹配,也可能不匹配。 清单4显示了UserDAO接口,清单5显示了其实现,其中使用Spring JDBC框架执行插入/更新(第21行)和查询(第30行)。 内部类已实现查询(第31-44行),以将返回的ResultSet
对象映射到User对象。 LabDAO
和DivisionDAO
的实现方式相同。
清单4. edu.ucar.cisl.ncarUsers.dal.UseDAO
1. package edu.ucar.cisl.ncarUsers.dal;
2. ...//imports
3. public interface UserDAO
4. {
5. public User getUser(String s);
6. public void addUser(User user);
7. public ArrayList<User> getAllUsers();
8. }
清单5. edu.ucar.cisl.ncarUsers.dal.UserDAOJDBCImpl
1. package edu.ucar.cisl.ncarUsers.dal;
2. ...//imports
3. public class UserDAOJDBCImpl extends SimpleJdbcDaoSupport
implements UserDAO {
4. public getUser(String s) {
5. String criteria="USERNAME = '" + s + "'";
6. ArrayList<User> users=getUsers(criteria);
7. if (users.size() > 0)
8. return users.get(0);
9. else
10. return null;
11. }
12. public void addUser(User user) {
13. Object objs[] = new Object[7];
14. objs[0] = user.getUserName();
15. objs[1] = user.getPassword();
16. objs[2] = user.getEmail();
17. objs[3] = user.getFirstName();
18. objs[4] = user.getLastName();
19. objs[5] = user.getLab();
20. objs[6] = user.getDivision();
21. this.getJdbcTemplate().update("insert into USER (USERNAME,
PASSWORD, EMAIL, FIRST_NAME, LAST_NAME, LAB, DIVISION )
values (?, ?, ?, ?, ?, ?, ?)", objs);
22. }
23. public ArrayList<User> getAllUsers(){
24. return getUsers(null);
25. }
26. protected ArrayList<User> getUsers(String criteria) {
27. String query="select ID, USERNAME, PASSWORD, EMAIL,
FIRST_NAME, LAST_NAME, LAB, DIVISION from USER";
28. if (criteria != null && criteria.trim().length() > 0)
29. query= query + " Where " + criteria;
30. Collection users = this.getJdbcTemplate().query(query,
31. new RowMapper() {
32. public Object mapRow(ResultSet rs, int rowNum) throws
SQLException {
33. User user = new User();
34. user.setID(rs.getInt("ID"));
35. user.setUserName(rs.getString("USERNAME"));
36. user.setPassword(rs.getString("PASSWORD"));
37. user.setEmail(rs.getString("EMAIL"));
38. user.setFirstName(rs.getString("FIRST_NAME"));
39. user.setLastName(rs.getString("LAST_NAME"));
40. user.setLab(rs.getInt("LAB"));
41. user.setDivision(rs.getInt("DIVISION"));
42. return user;
43. }
44. });
45. ArrayList<User> results= new ArrayList <User>();
46. Iterator it=users.iterator();
47. while (it.hasNext())
48. results.add((User)it.next());
49. return results;
50. }
51. }
实施业务逻辑层
业务逻辑层(BLL)是业务规则集中的地方。 该层还处理来自表示层的请求,并与DAL交互以从后端检索数据并请求DAL执行数据持久性。 我实现了三个管理器类:每个域对象一个。 清单6和7展示了UserManager接口及其实现。 LabManager和DivisionManager的实现与UserManager非常相似。
清单6. edu.ucar.cisl.ncarUsers.bll.UserManager
1. package edu.ucar.cisl.ncarUsers.bll;
2. ...//imports
3. public interface UserManager {
4. public User getUser(String userName);
5. public void addUser(User user);
6. public ArrayList<User> getAllUsers();
7. }
清单7. edu.ucar.cisl.ncarUsers.bll.UserManagerImpl
1. package edu.ucar.cisl.ncarUsers.bll;
2. ...//imports
3. public class UserManagerImpl implements UserManager {
4. protected UserDAO userDao;
5. public User getUser(String userName) {
6. return userDao.getUser(userName);
7. }
8. public void addUser(User user) {
9. userDao.addUser(user);
10. }
11. public UserDAO getUserDao() {
12. return userDao;
13. }
14. public void setUserDao(UserDAO userDao) {
15. this.userDao = userDao;
16. }
17. public ArrayList<User> getAllUsers() {
18. return userDao.getAllUsers();
19. }
20. }
实施表示层
浏览器请求处理程序
NCAR管理员需要使用浏览器界面才能在Web上添加用户。 我使用Spring MVC和Spring Web Flow框架来实现浏览器请求处理程序。 在Spring Web Flow上已经发布了许多文章和教程,您可以在参考资料中找到一些文章和教程。
清单8配置了Spring MVC servlet。 除了来自Ajax的那些请求之外,该servlet将处理来自浏览器的所有请求。 好的做法是,所有此类请求的URI始终以/ brh开头,而来自RESTful Web服务客户端程序(包括Ajax客户端)的所有请求的URL始终以/ rrh开头。
清单8.在/Web-Inf/web.xml中使用Spring MVC和Spring Web Flow的Servlet定义
1.<servlet>
2. <servlet-name>ncarUsers</servlet-name>
3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
4. <init-param>
5. <param-name>contextConfigLocation</param-name>
6. <param-value>/WEB-INF/ncarUsers-spring-config.xml</param-value>
7. </init-param>
8.</servlet>
9.<servlet-mapping>
10. <servlet-name>ncarUsers</servlet-name>
11. <url-pattern>*.htm</url-pattern>
12.</servlet-mapping>
清单9配置Spring Web Flow。 它将视图名称映射到JavaServer Page(JSP)文件(第6-9行),并注册在流配置文件中定义的流(第11-13行)。
清单9. /WEB-INF/ncarUsers-spring-config.xml
1.<?xml version="1.0" encoding="UTF-8"?>
2.<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
3. <bean name="/flow.htm"
class="org.springframework.webflow.executor.mvc.FlowController">
4. <property name="flowExecutor" ref="flowExecutor"/>
5. </bean>
6. <bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
7. <property name="prefix" value="/WEB-INF/jsp/"/>
8. <property name="suffix" value=".jsp"/>
9. </bean>
10. <flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
11. <flow:registry id="flowRegistry">
12. <flow:location path="/WEB-INF/flows/**-flow.xml"/>
13. </flow:registry>
14.</beans>
我实现了一个表单类(清单10)和一个表单动作类(清单11)以简化“添加用户”页面流程。 表单类包含要在浏览器界面中显示的数据,并存储NCAR管理员填写的数据,如addUser.jsp(清单12)所示。 Spring标记库用于将表单数据与HTML表单中的字段绑定。 表单动作类包含表单动作的行为。
清单10. edu.ucar.cisl.ncarUsers.presentation.brh.AddUserForm
1. package edu.ucar.cisl.ncarUsers.presentation.brh;
2. ...//imports
3. public class AddUserForm implements Serializable {
4. protected ArrayList<Lab> labs;
5. protected User user;
6. public AddUserForm() {
7. }
8. ... //getters and setters
9. }
清单11. edu.ucar.cisl.ncarUsers.presentation.brh.AddUserFormAction
1. package edu.ucar.cisl.ncarUsers.presentation.brh;
2. ...//imports
3. public class AddUserFormAction extends FormAction {
4. protected UserManager userManager;
5. protected LabManager labManager;
6. public AddUserFormAction() {
7. userManager = null;
8. }
9. public Event initForm(RequestContext context) throws Exception {
10. AddUserForm form = (AddUserForm) getFormObject(context);
11. form.setLabs(this.labManager.getLabs());
12. form.setUser(new User());
13. return success();
14. }
15. public Event submit(RequestContext context) throws Exception {
16. AddUserForm form = (AddUserForm) getFormObject(context);
17. User user = form.getUser();
18. userManager.addUser(user);
19. return success();
20. }
21. public Event addNewUser(RequestContext context) throws Exception {
22. initForm(context);
23. return success();
24. }
25. ... //getters and setters
26. }
清单12. /WEB-INF/jsp/addUser.jsp
1.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2.<%@ page language="java"%>
3.<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
4.<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
5.<html>
6.<head>
7.<title>NCAR New User Registration</title>
8.<script language="JavaScript" src="js/addUserAjax.js"></script>
9.</head>
10.<body>
11.<h1>NCAR New User Registration</h1>
12.<form:form commandName="addUserForm" method="post"
action="flow.htm">
13.<input type="hidden" name="_flowExecutionKey"
value="${flowExecutionKey}">
14.<table>
15.<tr>
16. <td>Username:</td>
17. <td align="left">
18. <form:input path="user.userName" id="userName"
onblur="validateUsername();" />
19. </td>
20.</tr>
21.<tr>
22. <td>Password:</td>
23. <td align="left">
24. <form:input path="user.password" id="password" />
25. </td>
26.</tr>
27.<tr>
28. <td> </td>
29.</tr>
30.<tr>
31. <td>First Name:</td>
32. <td align="left">
33. <form:input path="user.firstName" id="email" />
34. </td>
35.</tr>
36.<tr>
37. <td>Last Name:</td>
38. <td align="left">
39. <form:input path="user.lastName" id="email" />
40. </td>
41.</tr>
42.<tr>
43. <td>Email:</td>
44. <td align="left">
45. <form:input path="user.email" id="email" />
46. </td>
47.</tr>
48.<tr>
49. <td>Lab:</td>
50. <td align="left">
51. <form:select id="lab" path="user.lab" onclick="updateDivisions();">
52. <form:option value="0" label="--Please Select--" />
53. <form:options items="${addUserForm.labs}" itemValue="ID"
itemLabel="name" />
54. </form:select>
55. </td>
56.</tr>
57.<tr>
58. <td>Division:</td>
59. <td align="left">
60. <form:select id="division" path="user.division" disabled="true">
61. </form:select>
62. </td>
63.</tr>
64.<tr>
65. <td> </td>
66.</tr>
67.<tr>
68. <td colspan="2" align="center">
69. <input type="submit" name="_eventId_submit" value="Submit">
70. </td>
71.</tr>
72.</table>
73.</form:form>
74.</body>
75.</html>
清单13中配置了表单操作类AddUserFormAction
。它使用了表单类(第3行)以及userManager
和labManager
类(第6,7行)。 这两个管理器类都在Spring配置文件applicationContext.xml中配置,稍后将对其进行讨论。
清单13. /WEB-INF/flows/addUser-beans.xml
1. <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
2. <bean id="addUserFormAction"
class="edu.ucar.cisl.ncarUsers.presentation.brh.AddUserFormAction">
3. <property name="formObjectName" value="addUserForm"/>
4. <property name="formObjectClass"
value="edu.ucar.cisl.ncarUsers.presentation.brh.AddUserForm"/>
5. <property name="formObjectScope" value="FLOW"/>
6. <property name="userManager" ref="userManager"/>
7. <property name="labManager" ref="labManager"/>
8. </bean>
9. </beans>
清单14定义了状态和转换流中状态的动作。
清单14. /WEB-INF/flows/addUser-flow.xml
1. <?xml version="1.0" encoding="UTF-8"?>
2. <flow xmlns=http://www.springframework.org/schema/webflow
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
3. <start-state idref="addUser"/>
4. <view-state id="addUser" view="addUser">
5. <render-actions>
6. <action bean="addUserFormAction" method="initForm"/>
7. </render-actions>
8. <transition on="submit" to="submit">
9. <action bean="addUserFormAction" method="bind"/>
</transition>
10. </view-state>
11. <view-state id="summary" view="summary">
12. <transition on="addNewUser" to="addNewUser" />
13. </view-state>
14. <action-state id="submit">
15. <action bean="addUserFormAction" method="submit"/>
<transition on="success" to="summary"/>
16. </action-state>
17. <action-state id="addNewUser">
18. <action bean="addUserFormAction" method="addNewUser"/>
19. <transition on="success" to="addUser"/>
20. </action-state>
21. <import resource="addUser-beans.xml"/>
22. </flow>
资源请求处理程序
资源类决定将什么作为RESTful Web服务公开给客户端应用程序。 Jersey使在RRH中轻松实现RESTful Web服务变得容易。 它使用批注将资源类与URI映射,并将HTTP请求中的标准HTTP方法映射到资源类中的方法。 要使用Jersey,需要在web.xml文件中配置一个特殊的servlet(清单15)。 初始化Servlet时,它将对edu.ucar.cisl.ncarUsers.presentation.rrh包中的类进行爬网,以查找所有资源类并将它们映射到带注释的URI。 RESTful Web服务的所有请求均以/ rrh开头,并将由该servlet处理。
清单15.在/Web-Inf/web.xml中使用Jersey的Servlet定义
1. <servlet>
2. <servlet-name>rrh</servlet-name>
3. <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
4. <init-param>
5. <param-name>com.sun.jersey.config.property.packages</param-name>
6. <param-value>edu.ucar.cisl.ncarUsers.presentation.rrh</param-value>
7. </init-param>
8. </servlet>
9. <servlet-mapping>
10. <servlet-name>rrh</servlet-name>
11. <url-pattern>/rrh/*</url-pattern>
12. </servlet-mapping>
已经实现了三种资源类: UsersResource
(清单16), UserResource
(清单17)和DivisionResource
(清单18)。 在清单16中,第3行的类定义之前的@Path
注释将UsersResource
类映射到URI / users。 第21、22行的getUsersAsJsonArray
方法之前的@GET
和@Produces(application/json)
注释表示此方法将处理HTTP GET
请求,其响应内容类型为JSON。 第@PUT
行上的方法putUsers
之前的注释@PUT
和@Consumes("text/plain")
表示该方法将处理HTTP PUT
请求,并且输入HTTP正文中的内容类型应为纯文本。 在这种情况下,它是一种平面文件格式,每行只有一个用户。 每个用户的属性用竖线(|)分隔。
清单16. edu.ucar.cisl.ncarUsers.presentation.rrh.UsersResource
1. package edu.ucar.cisl.ncarUsers.presentation.rrh;
2. ...//imports
3. @Path("/users/")
4. public class UsersResource {
5. protected @Context UriInfo uriInfo;
6. protected UserManager userManager;
7. protected DivisionManager divisionManager;
8. protected LabManager labManager;
9. public UsersResource() {
10. userManager = (UserManager)
11. BeanFactory.getInstance().getBean("userManager");
12. divisionManager = (DivisionManager)
13. BeanFactory.getInstance().getBean("divisionManager");
14. labManager = (LabManager)
15. BeanFactory.getInstance().getBean("labManager");
16. }
17. @Path("{username}/")
18. public UserResource getUser(@PathParam("username") String userName) {
19. return new UserResource(uriInfo, userManager, userName);
20. }
21. @GET
22. @Produces("application/json")
23. public JSONArray getUsersAsJsonArray() throws JSONException {
24. ArrayList<User> users = this.userManager.getAllUsers();
25. JSONArray usersArray = new JSONArray();
26. for (User user : users) {
27. JSONObject obj = new JSONObject();
28. obj.put("USERNAME", user.getUserName());
29. obj.put("PASSWORD", user.getPassword());
30. obj.put("FIRST_NAME", user.getFirstName());
31. obj.put("LAST_NAME", user.getLastName());
32. obj.put("EMAIL", user.getEmail());
33. String labName=labManager.getLabName(user.getLab());
34. obj.put("LAB", labName);
35. String divisionName= divisionManager.getDivisionName(user.getDivision());
36. obj.put("DIVISION", divisionName);
37. usersArray.put(obj);
38. }
39. return usersArray;
40. }
41. @PUT
42. @Consumes("text/plain")
43. public Response putUsers(String input) throws IOException {
44. Reader reader=new StringReader(input);
45. BufferedReader br=new BufferedReader(reader);
46. while (true) {
47. String line=br.readLine();
48. if (line == null)
49. break;
50. processUser(line);
51. }
52. return Response.created(uriInfo.getAbsolutePath()).build();
53. }
54. /********************************
If the user exists, update it. Otherwise, create a new one
@param input
*/
55. protected void processUser(String input)
56. {
57. StringTokenizer token=new StringTokenizer(input, "|");
58. String userName=token.nextToken();
59. String password=token.nextToken();
60. String firstName=token.nextToken();
61. String lastName=token.nextToken();
62. String email=token.nextToken();
63. String labName=token.nextToken();
64. String divisionName=token.nextToken();
65. int lab=this.labManager.getLabID(labName);
66. int division=this.divisionManager.getDivisionID(divisionName);
67. User user=this.userManager.getUser(userName);
68. if (user == null)
69. user=new User();
70. user.setUserName(userName);
71. user.setPassword(password);
72. user.setFirstName(firstName);
73. user.setLastName(lastName);
74. user.setEmail(email);
75. user.setLab(lab);
76. user.setDivision(division);
77. this.userManager.addUser(user);
78. }
79. }
在清单16中,第17行的getUser
方法之前的注释@Path("{username}/")
表示/ users /之后以及下一个正斜杠(如果请求URI中存在@Path("{username}/")
之前的字符串,它将用作getUser
方法将返回变量username
值和子资源类UserResource
的实例。 然后,Jersey调用UserResource
类中的方法,并为相应的HTTP方法添加注释。 在Listing17,所述getUser
方法,标注了@GET
和Produces("application/json")
将由泽西在HTTP请求中的GET方法来在一个JSON格式(线12-19)返回用户数据被调用。
清单17. edu.ucar.cisl.ncarUsers.presentation.rrh.UserResource
1. package edu.ucar.cisl.ncarUsers.presentation.rrh;
2. ...//imports
3. public class UserResource {
4. protected String userName;
5. protected UriInfo uriInfo;
6. protected UserManager userManager;
7. public UserResource(UriInfo uriInfo, UserManager userManager, String userName) {
8. this.uriInfo = uriInfo;
9. this.userName = userName;
10. this.userManager = userManager;
11. }
12. @GET
13. @Produces("application/json")
14. public JSONObject getUser() throws JSONException {
15. JSONObject obj = new JSONObject();
16. User user = this.userManager.getUser(userName);
17. if (user != null)
18. obj.put("userName", user.getUserName()).put("email", user.getEmail());
return obj;
19. }
20. }
清单18中的Resource类DivisionsResource
以非常相似的方式实现。 该类在第3行用URI路径/ divisions /进行注释getDivisions
方法以@GET
和@ProduceName
注释,返回一个JSON数组的除法(第10-23行)。
清单18. edu.ucar.cisl.ncarUsers.presentation.rrh.DivisionsResource
1. package edu.ucar.cisl.ncarUsers.presentation.rrh;
2. ...//imports
3. @Path("/divisions/")
4. public class DivisionsResource {
5. protected DivisionManager divisionManager;
6. public DivisionsResource() {
7. divisionManager = (DivisionManager)
8. BeanFactory.getInstance().getBean("divisionManager");
9. }
10. @GET
11. @Produces("application/json")
12. public JSONArray getDivisions(@QueryParam("labID") String labID)
13. throws JSONException {
14. int id = Integer.parseInt(labID);
15. ArrayList<Division> divisions = this.divisionManager.getDivisions(id);
16. JSONArray divisionsArray = new JSONArray();
17. for (Division division : divisions) {
18. JSONObject obj = new JSONObject();
19. obj.put("ID", division.getID()).put("name", division.getName());
20. divisionsArray.put(obj);
21. }
22. return divisionsArray;
23. }
24. }
客户应用
阿贾克斯
Ajax充当RESTFul Web服务的客户端。 他们一起工作以帮助创建类似于桌面的丰富且响应Swift的浏览器界面。 在示例应用程序中,我在两个地方使用过Ajax:清单12中的第18行检查数据库中是否已经存在用户名,并且异步请求给定实验室的分区列表并更新除法选项菜单而不刷新第51行上的页面。
清单19中列出了JavaScript。第2到13行中的validateUsername
函数设置XMLHttpRequest并将其发送到RESTful Web服务,以在浏览器中获取给定用户名的用户数据。 第14至27行的usernameCallback
函数是一个回调函数,用于处理RESTful Web服务服务器的响应。 如果响应中包含用户数据,则表明具有给定用户名的用户已经存在。 将显示一条警告消息,并且浏览器中的用户名字段将被清除。
第28到39行的updateDivisions
函数将请求发送到RESTful Web服务,以获取实验室NCAR管理员在实验室选项菜单中选择的部门。 第40到55行的回调函数updateDivisionsCallback
处理响应,并在Division选项菜单中显示返回的部门名称。
清单19. js / addUserAjax.js
1. var req;
2. function validateUsername() {
3. var username = document.getElementById("userName");
4. var url = "rrh/users/" + escape(username.value);
5. if (window.XMLHttpRequest) {
6. req = new XMLHttpRequest();
7. } else if (window.ActiveXObject) {
8. req = new ActiveXObject("Microsoft.XMLHTTP");
9. }
10. req.open("Get", url, true);
11. req.onreadystatechange = usernameCallback;
12. req.send(null);
13. }
14. function usernameCallback() {
15. if (req.readyState == 4 && if (req.status == 200) {
16. var jsonData = req.responseText;
17. var myJSONObject = eval("(" + jsonData + ")");
18. var un = myJSONObject.userName;
19. var username = document.getElementById("userName");
20. if (username.value == un) {
21. alert("Warning: " + username.value +
22. " exists already. Choose another username.");
23. username.value = "";
24. username.focus();
25. }
26. }
27. }
28. function updateDivisions() {
29. var labSel = document.getElementById("lab");
30. var url = "rrh/divisions/?labID=" + escape(labSel.value);
31. if (window.XMLHttpRequest) {
32. req = new XMLHttpRequest();
33. } else if (window.ActiveXObject) {
34. req = new ActiveXObject("Microsoft.XMLHTTP");
35. }
36. req.open("Get", url, true);
37. req.onreadystatechange = updateDivisionsCallback;
38. req.send(null);
39. }
40. function updateDivisionsCallback() {
41. if (req.readyState == 4)&& req.status == 200) {
42. var jsonData = req.responseText;
43. var divisionsData = eval("(" + jsonData + ")");
44. var divisionSel = document.getElementById("division");
45. var length = divisionSel.length;
46. for (var b = 0; b < length; b++) {
47. divisionSel.options[b] = null;
48. }
49. for (var a = 0; a < divisionsData.length; a++) {
50. divisionSel.options[a] = new
51. Option(divisionsData[a].name, divisionsData[a].ID);
52. }
53. divisionSel.disabled = "";
54. }
55. }
Ruby脚本
RESTful Web服务的客户端可以用Perl,Ruby,Python,C,C#或Java代码等语言轻松实现。 在本文中,我以Ruby为例。 清单20显示了Ruby脚本,该脚本从RESTful Web服务下载用户数据并保存每个用户数据,其属性之间用竖线(|)隔开。 清单21中的Ruby脚本将用户的数据从文件上传到服务器。
清单20. client / downloadUsersData.rb
54. #!/usr/bin/ruby
55. require 'rubygems'
56. require 'json'
57. require 'open-uri'
58. $KCODE = 'UTF8'
59. def download(filename)
60. file=File.new(filename, 'w')
61. base_uri = 'http://localhost:8080/ncarUsers/rrh/users/'
62. # Make the HTTP request and read the response entity-body as a JSON
63. # document.
64. json = open(base_uri).read
65. # Parse the JSON document into a Ruby data structure.
66. json = JSON.parse(json)
67. # Iterate over the data structure...
68. json.each { |r| file.puts r['USERNAME'] + '|' + r['PASSWORD'] + '|' +
r['FIRST_NAME'] + '|' + r['LAST_NAME'] +
69. '|' + r['EMAIL'] + '|' + r['LAB'] + '|' + r['DIVISION']; }
70. end
71. # Main program.
72. unless ARGV[0]
73. puts "Usage: #{$0} [file name]"
74. exit
75. end
76. download(ARGV[0])
清单21. client / uploadUsersData.rb
1. #!/usr/bin/ruby
2. require 'rubygems'
3. require 'rest-open-uri'
4. require 'uri'
5. require 'cgi'
6. def uploadUsers(content)
7. base_uri = 'http://localhost:8080/ncarUsers/rrh/users'
8. begin
9. response = open(base_uri, :method => :put, 'Content-Type' =>
"text/plain", :body => content)
10. rescue OpenURI::HTTPError => e
11. response_code = e.io.status[0].to_i
12. puts response_code
13. if response_code != "200"
a. puts "Sorry, Can't post the users"
14. else
a. raise e
15. end
16. end
17. end
18. def upload(filename)
19. File.open(filename) do |file|
20. content = file.read
21. uploadUsers(content)
22. end
23. end
24. # Main program.
25. unless ARGV[0]
26. puts "Usage: #{$0} [file name]"
27. exit
28. end
29. upload(ARGV[0])
放在一起
Spring框架用于将数据访问层,业务逻辑层和表示层中的组件联系在一起。 它使用控制反转(IoC)来外部化组件依赖项的创建和管理。 清单22显示了定义组件及其依赖关系的Spring配置文件。 它还配置数据存储和事务管理器。
清单22. applicationContext.xml
1. <beans xmlns=http://www.springframework.org/schema/beans
2. xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
3. xmlns:tx=http://www.springframework.org/schema/tx
4. xsi:schemaLocation="
5. http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
7. http://www.springframework.org/schema/tx
8. http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
9. <tx:annotation-driven/>
10. <bean id="dataSource"
11. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
12. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
13. <property name="url" value="jdbc:mysql://localhost:3306/ncar_users"/>
14. <property name="username" value="tutorial"/>
15. <property name="password" value="tutorial"/>
16. </bean>
17. <bean id="transactionManager"
18. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
19. <property name="dataSource" ref="dataSource"/>
20. </bean>
21. <bean id="userManager"
22. class="edu.ucar.cisl.ncarUsers.bll.UserManagerImpl">
23. <property name="userDao"><ref local="userDao"/></property>
24. </bean>
25. <bean id="labManager"
26. class="edu.ucar.cisl.ncarUsers.bll.LabManagerImpl">
27. <property name="labDao"><ref local="labDao"/></property>
28. </bean>
29. <bean id="divisionManager"
30. class="edu.ucar.cisl.ncarUsers.bll.DivisionManagerImpl">
31. <property name="divisionDao"><ref local="divisionDao"/></property>
32. </bean>
33. <bean id="userDao" class="edu.ucar.cisl.ncarUsers.dal.UserDAOJDBCImpl">
34. <property name="dataSource"><ref local="dataSource"/></property>
35. </bean>
36. <bean id="labDao" class="edu.ucar.cisl.ncarUsers.dal.LabDAOJDBCImpl">
37. <property name="dataSource"><ref local="dataSource"/></property>
38. </bean>
39. <bean id="divisionDao"
40. class="edu.ucar.cisl.ncarUsers.dal.DivisionDAOJDBCImpl">
41. <property name="dataSource"><ref local="dataSource"/></property>
42. </bean>
43. </beans>
在Web.xml文件中配置了上下文加载器侦听器,以在启动Web应用程序时加载applicationContext.xml文件(清单23)。
清单23. web.xml中的上下文加载器侦听器配置,用于加载applicationContext.xml
1. <context-param>
2. <param-name>contextConfigLocation</param-name>
3. <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
4. </context-param>
5. <listener>
6. <listener-class>
org.springframework.web.context.ContextLoaderListener</listener-class>
7. </listener>
从Eclipse运行应用程序
要从Eclipse运行示例应用程序:
- 右键单击项目浏览器中的ncarUsers项目,然后选择“运行方式” > “在服务器上运行”或“在服务器上 调试”(如果要在调试模式下运行它)。
- 在localhost中选择localhost> Tomcat v6.0 Server 。
图12.在Eclipse中运行Tomcat
- 点击完成 。 这将打开“服务器”选项卡,并显示示例应用程序已部署到Tomcat,并且服务器正在运行。 您可以切换到“控制台”选项卡以查看Tomcat服务器生成的消息。
- 打开浏览器并导航到:http:// localhost:8080 / ncarUsers。 单击链接注册新用户应用程序 。
图13. NCAR新用户注册浏览器界面
您会注意到Lab选项菜单显示--Please Select-- ,并且Division选项菜单被禁用。 在每个文本字段中输入内容,然后选择一个实验室。 现在,“分区”选项菜单包含所选实验室的分区。 接下来,选择一个部门。
- 点击提交 。 创建新用户,并显示“注册确认”页面。
图14. NCAR New Signup Confirmation页面
- 单击添加新用户 。
- 在“ NCAR新用户注册”页面上,在“用户名”字段中键入相同的用户名,然后移至下一个字段。 将弹出一个窗口,警告用户名已被使用,用户名字段将被清除。
- 创建几个用户后,打开命令提示符。 运行
downloadUsers.rb
Ruby脚本下载用户的数据(图15)。 您可以将下载的文件用作模板来添加一些新用户。 然后使用uploadUsers.rb
将新用户上传到应用程序服务器。图15.运行downloadUsers.rb脚本以下载用户的数据
幕后发生了什么?
当您打开“ 注册新用户”应用程序时,表示层中的浏览器请求处理程序将处理来自Web界面的请求。 在所有字段中输入数据并提交后,浏览器请求处理程序中的AddUserFormAction
对象将接受Submit请求。 如图16所示,此对象将必需的数据传递到BLL,然后BLL请求数据访问层将数据保存到MySQL数据库。 然后将确认页面显示给浏览器。
图16.添加新的用户序列图
键入用户名后,Ajax脚本将调用RESTful Web服务。 这次,资源请求处理程序层中的UsersResource
和UserResource
对象处理请求。 如果数据库中已经存在具有给定用户名的用户,则将JSON数据结构返回到浏览器。 然后,Ajax脚本显示警告消息并清除用户名字段。
当您选择实验室选项菜单字段时,Ajax脚本将在资源请求处理程序的DivisionsResource中调用GET Web服务,该服务将返回所选实验室的分区数组。 图17显示了Ajax脚本发送RESTful Web服务请求以获取给定实验室的部门并将其显示在Division选项菜单中之后,各层之间的请求顺序。
图17.获取除法序列图
用来上传和下载用户数据的Ruby脚本也是RESTful Web服务的客户端。 Ruby脚本downloadUsers.rb
向服务器发送HTTP GET
请求,该请求以JSON返回用户的数据,而uploadUsers.rb
向服务器发送包含HTTP正文中包含用户数据的HTTP PUT
请求。 数据格式为每行一个用户。 在每行内,每个用户的属性由竖线(|)分隔。
像浏览器请求处理程序一样,资源请求处理程序提供了一个接口,尽管它指向不同的客户端,并且请求业务逻辑层处理该处理。 反过来,业务逻辑层请求数据访问层处理数据持久性。
结论
越来越需要现代Web应用程序提供丰富的接口以及RESTful Web服务,以便客户端可以自动执行该过程。 本文介绍了如何使用文章“用于构建RESTful Web服务的多层体系结构”(请参阅参考资料 )中讨论的多层体系结构来构建动态Web应用程序和RESTful Web服务。 它还描述了Ajax和RESTful Web服务如何一起工作以创建类似于桌面的富响应性界面。 它使用Eclipse,Jersey,Spring MVC框架,Spring Web Flow,Spring JBDC框架和MySQL。 Ruby脚本用作RESTful Web服务的客户端。
根据国家科学基金会与大学大气研究公司的合作协议,该研究的实现是由国家科学基金会提供了部分支持。 国家大气研究中心由国家科学基金会赞助。 另外,我还要感谢NCAR的Markus Stobbs,他为选择示例应用程序和编辑本文提供了建议。
翻译自: https://www.ibm.com/developerworks/web/library/wa-aj-multitier2/index.html
web restful