使用 Apache Wink 和 Ajax 构建富 Java Web 应用程序
2010-06-08 00:00:00 来源:WEB开发网本文将介绍 Apache Wink,演示如何安装它,并附带一个简单的管理任务列表的 RESTful Web 服务。本文的示例基于 Apache Wink 发行版携带的 Bookmarks 示例。尽管如此,本文中的示例使用了不同的消息格式,可以使用 Asynchronous JavaScript + XML (Ajax) 从 Web 页面轻松调用,从而丰富了用户体验。
REST 和 Ajax
RESTful Web 服务通过使用某种 URL 结构公开,并提供了一个简化的接口来对 Web 服务进行创建、读取、更新和删除(CRUD)操作。RESTful Web 服务可以使用并提供各种不同格式的消息,这些消息都能被 Multipurpose Internet Mail Extensions(MIME) 类型识别,包括 JavaScript Object Notation(JSON)、XML 以及其他二进制数据。
RESTful Web 服务的简单性使得使用 Web 客户端技术(如 Ajax)进行访问变得更容易。使用 JSON 作为消息格式的能力又进一步简化了交互。
REST
与 SOAP 不同,RESTful Web 服务并不一定需要 XML 文档作为消息载体。并没有标准的 XML Schema Definition(XSD) 描述消息格式。这使得 RESTful Web 服务可以像访问 URL 时返回的文档一样简单。实际上,Web 页面确实符合 RESTful Web 服务的配置文件。
由于消息格式并不需要使用 XML,因此几乎支持所有类型的消息内容。例如,可以将普通文本提交给 URL 以对一个 RESTful Web 服务调用 POST 方法。也可以使用其他简单消息格式,例如 JSON。Apache Wink 能够匹配 HTTP 操作中识别的 MIME 类型和使用并提供给定 MIME 类型的服务方法。
RESTful Web 服务使用不同的 HTTP 操作来公开服务,而这些服务执行不同的操作。尽管本身没有确定的标准,但有些原则定义了对特定的任务使用哪些 HTTP 操作。请看表 1。
表 1. REST 操作和 URL 示例
HTTP 操作 | URL 样例 | 用途 |
GET | http://localhost:8080/Task/rest/tasks | 列举出服务搜索到的所有任务 |
GET | http://localhost:8080/Task/rest/tasks/1 | 获得 ID 为 1 的任务 |
POST | http://localhost:8080/Task/rest/tasks | 根据提交的数据创建一个新任务 |
PUT | http://localhost:8080/Task/rest/tasks/1 | 使用数据请求更新 ID 为 1 的指定任务 |
DELETE | http://localhost:8080/Task/rest/tasks/1 | 删除 ID 为 1 的任务 |
Ajax
在过去几年中,Ajax 已成为一项相当普遍的 Web 技术,它提供了相对丰富的用户体验并允许标准 Web 页面更有效地使用数据。有了 Ajax,Web 页面能够处理数据而不必刷新浏览器中的整个页面,这就给用户提供了更加流畅的交互。
Ajax 只是在 Web 页面中使用了 Web 浏览器可访问的 JavaScript 和 XML 对象执行操作。使用了 Ajax 的 JavaScript 函数可以访问 URL 并能处理其响应。这种交互对于从 Ajax 调用 RESTful Web 服务非常理想,JavaScript 方法就像访问普通 Web 页面一样访问 RESTful Web 服务 URL 并解释结果。
本文中的示例使用 jQuery JavaScript 库通过 Ajax 访问 RESTful Web 服务。
安装 Wink
Apache Wink 二进制版本包含有库文件,这些库文件需要包含到动态 Web 应用程序项目。Apache Wink 的安装实际上就是下载二进制版本并将库导入到新建的动态 Web 项目,然后使用 Apache Wink 库中的类来注释 Java 类和方法。
为了构造文中的 RESTful Web 服务,需要服务器库。Apache Wink 还包含客户端库,可用来从 Java 代码中调用 RESTful Web 服务,但在本项目中不需要。
首先下载 Apache Wink 二进制版本到您的电脑中。解压缩库到某一目录以便能将需要的库导入到您新建的动态 Web 项目中。
Wink 服务示例
按照以下步骤在 Eclipse 中构建 Web 服务示例:
打开 Eclipse 并从菜单中选择 New > Project。从 New Project 屏幕中,选择 Web 下方的 Dynamic Web Project 并单击 Next。
输入项目名称并选择目标服务器运行时。本文例子使用的是 IBM® WebSphere® Application Server Community Edition (Community Edition) v2.1 服务器运行时。如果在您的 Eclipse 实例中尚未安装,单击 New 添加它(请看图 1)。选择目标运行时后单击 Next。
图 1. 创建动态 Web 项目
单击 Next 保持默认源和输出文件夹。
输入上下文根目录(例如,Tasks)并将 WebContent 作为内容目录。选择 Generate web.xml deployment descriptor 并单击 Next 或 Finish。
如果您使用的是 Community Edition v2.1 运行时,下一个窗口包含被放入 Geronimo 部署计划文件(请看图 2)的信息。输入组 ID(例如,com.example.group)、工件 ID(如 tasks)以及版本号(如 1.0.0)。完成后单击 Finish。
图 2. 输入 Geronimo 部署计划信息
现在已创建项目,按照以下步骤向项目中导入 Apache Wink 库和其他依赖项:
单击项目,从上下文菜单中选择 Import。选择 File System 并单击 Next。
导航至解压缩 Apache Wink 库的位置并打开 dist 文件夹。在 dist 文件夹中导入以下 Java Archive (JAR) 文件:
wink-1.0-incubating.jar
wink-common-1.0-incubating.jar
wink-server-1.0-incubating.jar
导入 Apache Wink 库以后,从二进制版本的 lib 文件夹中导入以下依赖项:
activation-1.1.jar
commons-lang-2.3.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.4.jar
jsr311-api-1.0.jar
slf4j-api-1.5.8.jar
slf4j-simple-1.5.8.jar
stax-api-1.0-2.jar
最后,需要导入包含 JSON 提供者的 JAR 文件,因为示例服务产生 JSON 格式的数据。将 Apache Wink 发行版的 ext/wink-json-provider 目录下的 JAR 文件包含进来:wink-json-provider-1.0-incubating.jar 和 json-20080701.jar。
现在已导入所需的库,基本可以编写第一个 RESTful Web 服务了。但首先,更新 web.xml 文件并从 Apache Wink 将 RestServlet 包含进来,它负责将请求路由到正确的资源。
请看清单 1 中的 web.xml 示例。
清单 1. web.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>Tasks</display-name>
<description>REST services for managing tasks.</description>
<servlet>
<servlet-name>restService</servlet-name>
<servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>
<init-param>
<param-name>applicationConfigLocation</param-name>
<param-value>/WEB-INF/resources</param-value>
</init-param>
<init-param>
<param-name>propertiesLocation</param-name>
<param-value>/WEB-INF/tasks.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>restService</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
web.xml 文件包含一些参数用来初始化 servlet 类,该类是 org.apache.wink.server.internal.servlet.RestServlet。第一个是 applicationConfigLocation,这是一个文件名称,该文件包含用于在服务中执行工作的资源的完全限定类名。
第二个是 propertiesLocation 参数,它包含有文件名称,这些文件中有供 RestServlet 类使用的属性。
web.xml 文件中的 propertiesLocation 指向 WEB-INF/tasks.properties 位置。在 WEB-INF 目录创建一个名叫 tasks.properties 的文件并包含进清单 2 中的设置。
清单 2. tasks.properties 文件
wink.defaultUrisRelative=false
现在 web.xml 文件已被修改为加载 RestServlet,现在可以创建一个资源,当使用匹配的 URL 时将被 servlet 调用。
为了创建资源,只要创建一个名为 TasksResource 的 Java 类。添加类注释并创建方法,以处理使用注释的不同 REST 操作,如清单 3 所示。
清单 3. 不具备实现的 TasksResource 类
package com.nathanagood.services;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.wink.server.utils.LinkBuilders;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@Path("tasks")
public class TasksResource {
private static final String TASK = "tasks";
private static final String ITEM_PATH = "{tasks}";
private static final String ENCODING = "UTF-8";
@GET
@Produces( { MediaType.APPLICATION_JSON })
public JSONArray getTasks() {
// TODO: Add implementation
return null;
}
@Path(ITEM_PATH)
@GET
@Produces( { MediaType.APPLICATION_JSON })
public JSONObject getTask(@Context LinkBuilders link, @Context UriInfo uri,
@PathParam(TASK) String taskId) {
// TODO: Add implementation
return null;
}
@POST
@Consumes( { MediaType.APPLICATION_FORM_URLENCODED })
@Produces( { MediaType.APPLICATION_JSON })
public JSONObject createTask(MultivaluedMap<String, String> formData,
@Context UriInfo uriInfo, @Context LinkBuilders linksBuilders) {
// TODO: Add implementation
return null;
}
@Path(ITEM_PATH)
@PUT
@Consumes( { MediaType.APPLICATION_JSON })
@Produces( { MediaType.APPLICATION_JSON })
public JSONObject updateTask(JSONObject task, @Context UriInfo uriInfo,
@Context LinkBuilders linksBuilders) {
// TODO: Add implementation
return null;
}
@Path(ITEM_PATH)
@DELETE
@Produces( { MediaType.APPLICATION_JSON })
public JSONObject deleteTask(@Context LinkBuilders link, @Context UriInfo uri,
@PathParam(TASK) String taskId) {
// TODO: Add implementation
return null;
}
}
GET、POST、PUT 和 DELETE Java 注释直接映射到用于客户端访问服务的 HTTP 操作类型。Apache Wink servlet 根据 HTTP 操作将请求路由到正确的方法。除了使用 HTTP 操作以外,Apache Wink 还使用 HTTP 请求的 MIME 类型将请求映射到正确的方法。Consumes 和 Produces 注释提供了一种方法来定义由这些方法处理的 MIME 类型。请注意它们都返回 JSON MIME 类型(MediaType.APPLICATION_JSON)。POST 方法使用 MediaType.APPLICATION_FORM_URLENCODED 类型,这在后面将很重要,因为用于提交到服务 URL 的 Ajax 方法使用该内容类型。
添加实现
目前为止,方法大部分都是空的。为简洁起见,本文将演示如何实现这两个方法,它们分别处理 GET 操作和 POST 操作。getTasks() 和 getTask() 方法分别允许您获取任务列表和根据 ID 获取单个任务。createTask() 方法能让您用 Ajax 客户端在数据存储中新建一个任务。
在向资源添加实现代码前,创建一些新的类来提供足够实现以测试函数。Task 类包含存储在任务列表中的任务的信息(请看清单 4)。您可以通过使用 Eclipse 的从字段创建存取器(accessor)和构造函数的特性来创建这些类。
清单 4. Task 类
package com.nathanagood.services;
public class Task {
private boolean complete;
private String notes;
private String dueDate;
private String id;
private String name;
public Task(String id, String name, String notes, String dueDate) {
super();
this.id = id;
this.name = name;
this.notes = notes;
this.dueDate = dueDate;
}
public String getNotes() {
return notes;
}
public String getDueDate() {
return dueDate;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public boolean isComplete() {
return complete;
}
public void setComplete(boolean complete) {
this.complete = complete;
}
public void setNotes(String description) {
this.notes = description;
}
public void setDueDate(String dueDate) {
this.dueDate = dueDate;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
创建 TaskManager 类,该类负责获取并存储 Task 对象,如清单 5 所示。
清单 5. TaskManager 类
package com.nathanagood.services;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class TaskManager {
private static TaskManager instance = new TaskManager();
private static final Map<String, Task> tasks = Collections
.synchronizedMap(new HashMap<String, Task>());
/* Initialize some tasks with hard-coded values */
static {
tasks.put("1", new Task("1", "Walk the dog", "Remember the leash", "Today"));
tasks.put("2", new Task("2", "Go to the store",
"Buy milk and eggs", "Apr 1, 2010"));
};
private TaskManager() {
}
public static TaskManager getInstance() {
return instance;
}
public Collection<Task> getTasks() {
return Collections.unmodifiableCollection(tasks.values());
}
public Task getTask(String id) {
return tasks.get(id);
}
public void addTask(String id, Task task)
{
tasks.put(id, task);
}
}
现在已有了可用的对象,向 TaskResource 类的 getTasks()、getTask() 和 createTask() 方法添加实现代码,如清单 6 所示。
清单 6. TaskResource 方法实现
// imports snipped
@Path("tasks")
public class TasksResource {
private static final String TASK = "tasks";
private static final String ITEM_PATH = "{tasks}";
private static final String ENCODING = "UTF-8";
private Task createTask(MultivaluedMap<String, String>
formData) throws UnsupportedEncodingException {
String id = formData.getFirst("id");
String name = URLDecoder.decode(formData.getFirst("name"), ENCODING);
String dueDate = URLDecoder.decode(formData.getFirst("dueDate"), ENCODING);
String notes = URLDecoder.decode(formData.getFirst("notes"), ENCODING);
boolean complete = Boolean.parseBoolean(formData.getFirst("complete"));
Task task = new Task(id, name, notes, dueDate);
task.setComplete(complete);
return task;
}
private JSONObject createJSONObject(Task task) throws JSONException {
JSONObject obj = new JSONObject();
obj.put("name", task.getName());
obj.put("notes", task.getNotes());
obj.put("dueDate", task.getDueDate());
obj.put("complete", task.isComplete());
obj.put("id", task.getId());
return obj;
}
@GET
@Produces( { MediaType.APPLICATION_JSON })
public JSONArray getTasks() {
JSONArray result = new JSONArray();
Collection<Task> tasks = TaskManager.getInstance().getTasks();
for (Task task : tasks) {
try {
result.put(createJSONObject(task));
} catch (JSONException e) {
e.printStackTrace();
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
return result;
}
@Path(ITEM_PATH)
@GET
@Produces( { MediaType.APPLICATION_JSON })
public JSONObject getTask(@Context LinkBuilders link, @Context UriInfo uri,
@PathParam(TASK) String taskId) {
Task task = TaskManager.getInstance().getTask(taskId);
if (task == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
JSONObject result;
try {
result = createJSONObject(task);
} catch (JSONException e) {
e.printStackTrace();
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
return result;
}
@POST
@Consumes( { MediaType.APPLICATION_FORM_URLENCODED })
@Produces( { MediaType.APPLICATION_JSON })
public JSONObject createTask(MultivaluedMap<String, String> formData,
@Context UriInfo uriInfo, @Context LinkBuilders linksBuilders) {
Task newTask;
JSONObject obj;
try {
newTask = createTask(formData);
TaskManager.getInstance().addTask(newTask.getId(), newTask);
obj = createJSONObject(newTask);
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
return obj;
}
// snipped
}
此处的方法使用了先前创建的两个类 — TaskManager 和 Task。TaskManager 类中有一个 Task 对象的 Java Collection。在实际的生产场景中,这个内部 collection 将由一个从数据库加载数据并转换成 Task 对象的服务所替代。
添加这些方法后,REST 服务基本就可以运行了。添加 TaskResource 类的完全限定 Java 类名到 WEB-INF/resources 文件中(请看清单 7)。
清单 7. WEB-INF/resources 文件
com.nathanagood.services.TasksResource
为了测试服务,首先将项目导出为 Web Archive (WAR) 文件,它随后被部署到 Community Edition 实例。 单击 File > Export 并选择 Web\WAR File。选择文件的目标位置,单击 Finish,如图 3 所示。
图 3. 导出 WAR 文件
现在将 WAR 文件部署到 Community Edition 中。部署应用程序最简单的方法是通过 Administrative Console。
通过 Community Edition 实例的 URL 访问 Administrative Console(例如,http://localhost:8080/),单击 Administrative Console(见图 4)。登录控制台。如果您未改变默认的用户名和密码,用 system 用户名和 manager 密码登录。
图 4. Administrative Console 链接
登录后,单击 Deploy New Applications 链接(请看图 5)。
图 5. Deploy New Applications 链接
单击 Browse 选择从项目导出的 WAR 文件,单击 Install。如果 WAR 文件安装成功,将会看到图 6 的信息。
图 6. 成功部署消息
现在 Web 应用程序已成功部署,可以在 Web 浏览器访问如下 URL 并看到任务的 JSON 表示:http://localhost:8080/Tasks/rest/tasks。要查看表示为 JSON 的单个任务,展开任务的 ID(例如,http://localhost:8080/Tasks/rest/tasks/1)。只要访问浏览器 URL 就可测试服务的能力是 RESTful Web 服务的另一强大之处。
从 http://localhost:8080/Tasks/rest/tasks URL 返回的 JSON 如清单 8 所示。
清单 8. JSON 输出示例
[
{
"complete": false,
"dueDate": "Apr 1, 2010",
"id": "2",
"name": "Go to the store",
"notes": "Buy milk and eggs"
},
{
"complete": false,
"dueDate": "Today",
"id": "1",
"name": "Walk the dog",
"notes": "Remember the leash"
}
]
Ajax 客户端
现在已确认 RESTful Web 服务已就绪并运行正常,可以从 Web 页面使用 Ajax 显示数据。为了使 Ajax 调用易于实现,使用库来处理 Ajax 调用的细节,例如 jQuery JavaScript 库。
在 WebContent 目录中创建一个名叫 scripts 的目录。下载 jQuery 库,它是一个单独的 JavaScript 文件,将其导入到 WebContent/scripts 目录的动态 Web 项目中。
进行 Ajax 调用
jQuery 库包含一个专门从 URL 获取 JSON 数据的方法。它可以完美地调用 RESTful Web 服务 GET 方法(请看清单 9)。
清单 9. jQuery.getJSON 函数
$.getJSON(url, [data], [callback]);
jQuery 库还包含 post 方法,它可以用于将数据提交给 RESTful Web 服务。jQuery 库以 application/x-www-form-urlencoded MIME 类型提交数据,这就是 createTask() 方法使用 MediaType.APPLICATION_FORM_URLENCODED 类型的原因。清单 10 显示了该调用的例子。
清单 10. jQuery.post 函数
$.post(url, [data], [callback], [type]);
在 Web 页面中使用的两个调用会调用 RESTful Web 服务并在页面中显示结果。
Web 页面示例
在 WebContent 目录中创建一个 index.html 文件,如清单 11 所示。Web 页面包含对 jQuery 脚本的引用以及发出 Ajax 调用的函数。
清单 11. 完整的 index.html 文件
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Insert title here</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
//<![CDATA[
var url = 'http://172.27.35.137:8080/Tasks/rest/tasks';
function getTasks() {
$.getJSON(url, function(data) {
$.each(data, function(i, item) {
$('#taskList').append(
'<p><a href="#" >' + item.name + '</a></p>');
});
});
}
function getTask(id) {
$.getJSON(url + '/' + id, function(data) {
$('#singleTask').html(
'<p>' + data.notes + ' (Due ' + data.dueDate + ') - '
+ (data.complete ? 'Complete' : 'Incomplete')
+ '</p>');
});
}
function addTask() {
var taskId = document.getElementById('taskId').value;
var taskName = document.getElementById('name').value;
var taskNotes = document.getElementById('notes').value;
var taskDue = document.getElementById('due').value;
var taskComplete = document.getElementById('complete').checked;
$.post(url, {
name : taskName,
id : taskId,
dueDate : taskDue,
notes : taskNotes,
complete : taskComplete
});
$('#taskList').html('');
getTasks();
}
$(document).ready(function() {
getTasks();
});
//]]>
</script>
</head>
<body>
<h4>Tasks:</h4>
<div id="taskList"></div>
<h4>Task detail:</h4>
<div id="singleTask"></div>
<form>
<fieldset>
<p><label for="taskId">ID<br />
<input type="text" id="taskId" name="taskId" /></label></p>
<p><label for="name">Name<br />
<input type="text" id="name" name="name" /></label></p>
<p><label for="notes">Notes<br />
<input type="text" id="notes" name="notes" /></label></p>
<p><label for="due">Due<br />
<input type="text" id="due" name="due" /></label></p>
<p><label for="complete">Complete<br />
<input type="checkbox" id="complete" name="complete" /></label></p>
<p><a href="#" onclick=
addTask();;
>Add Task</a></p>
</fieldset>
</form>
</body>
</html>
该 index.html 文件包含一些 JavaScript 函数,可以从 RESTful Web 服务取得数据并以链接形式显示每个任务。当单击链接时,JavaScript 函数会显示选中任务的细节。
添加 jQuery 库和新的 index.html 文件后,与之前一样按照以下步骤重新将项目导出为 WAR 文件。通过 Administrative Console 再次部署 WAR 文件。在 WAR 文件重新部署后,可以通过以下 URL 访问示例 Web 页面:http://localhost:8080/Tasks。
该 Web 页面(图 7 中显示)可以测试从 Ajax 到 Apache Wink 中实现的新 RESTful Web 服务的调用。单击链接查看任务细节,通过输入信息并单击 Add Task 链接来添加新任务。
图 7. 呈现的 index.html Web 页面
结束语
Apache Wink 是一种能够使您使用 Java 编程语言轻松编写 RESTful Web 服务的框架。它能够使用并生成很多不同的 MIME 内容类型,包括普通文本、XML 和 JSON。
在 Java 类中使用简单的类和方法注释,就能构建 RESTful Web 服务,这些服务可以公开给很多不同的客户端,包括 Ajax。在 Web 页面中使用 Ajax 可以在刷新页面时带来更流畅的用户体验。
本文示例源代码或素材下载
- ››使用脚本恢复WinXP系统的用户登录密码
- ››使用phpMyadmin创建数据库及独立数据库帐号
- ››使用Zend Framework框架中的Zend_Mail模块发送邮件...
- ››Apache添加mod_aspdotnet.so支持ASP.NET配置指南
- ››使用cout标准输出如何控制小数点后位数
- ››使用nofollow标签做SEO的技巧
- ››使用 WebSphere Message Broker 的 WebSphere Tra...
- ››使用SQL Server事件探查器做应用程序的性能分析
- ››使用SQL Server事件探查器分析死锁原因
- ››Apache中改变php.ini的路径
- ››使用纯文本文件打造WCF服务
- ››使用 Dojo 开发定制 Business Space 小部件,第 4...
更多精彩
赞助商链接