JAX-RS (Java API for XML-Restful Web Services) 介绍
JAX-RS 是基于Servlet构建的。
描述请求处理的过程:
client request----->JAX-RS servlet----->JAX-RS application (e.g., predictions3)
Tomcat/Jetty中部署 JAX-RS 的web.xml 如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--prediction3-->
<!--对请求进行拦截,并转发给JAX-RS 应用-->
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!--prediction3 Over-->
</web-app>
用来配置 JAX-RS servlet "拦截" 请求 然后 将这个请求 dispatches(转发) 到 jax-rs 应用中。
是否将应用打包成 WAR文件
打包 : 将所有用到的jar(库)放到 WAR文件中。
有点是没有版本问题困扰(可能某个jar文件需要特定版本,而war文件已经将所有的jar打包了,所以可以发送给任何人,而无需在配置依赖就可以直接放到tomcat下运行),缺点是文件体积就变大了
不打包:则需要将jar(库)都放到TOMCAT_HOME/lib ,然后重启tomcat。
优点是多个应用可以共享jar库
JAX-RS实现
RI表示jersey, 是Metro project的部分,见: https://jersey.java.net (version 2.x).
RESTful路由
由 Rails framework 所倡导并流行, 现在被广泛的模仿。
不同http请求操作
// GET /predictions /* Read all predictions */
public void doGet(...)
// POST /predictions /* Create a new prediction for data in the HTTP request body */
public void doPost(...)
// GET /predictions/27 /* Read prediction with id = 27 */
public void doGet(...)
// DELETE /predictions/27 /* Delete prediction with id = 27 */
public void doDelete(...)
在如JAX-RS 和 Restlet等框架中, REST-style routes 可以通过注解(Annotation)来指定
注解可以指定Http的动作,即响应的http请求类型(e.g., GET) 和 名词,即客户端请求的url地址(e.g., URIs such as /predictions)
注解也可以指定响应的 MIME type (e.g., application/json versus text/xml)
@Produces({MediaType.APPLICATION_JSON}) /* a JAX-RS example */
public Response getJson()
注解可以指定一个正则表达式来验证参数
下面是伪代码的示例(JAX-RS实例中的很短):
GET /predictions/{id: [0-9]+} ###id必须是整数,如 12
JAX-RS 来构建 predictions3 service 的代码组成
Prediction.java:
被注解的javaBean类,使其可以自动生成xml
@XmlElement 注解 被放在 属性的getter方法上,如: who, what, id 三个属性的getter方法
重写toString() 以支持对 get请求 做出 text/plain类型的响应
PredictionsList.java:
被注解的javaBean类,同时也是作为主要的工具类。
## @XmlElement 注解 被添加到getPredictions() 方法上,用于自动生成xml。
## thread-safe(线程安全)
使用 java.util.concurrent.CopyOnWriteArrayList存储 predictions
使用线程安全的AtomicInteger 确保每一个Prediction实例对象都有唯一的id。
## 重写toString() 以支持 生成 text/plain(纯文本) 的predictions列表
PredictionsRS.java:
大量的使用@GET, @Path 和@Produces 注解来表示JAX-RS service中的 REST-style 的资源
支持 RESTful 路由风格的所有的CRUD(增删查改)操作。
默认生成xml,也可以是 JSON 和纯文本来响应 GET 请求。支持GET 来获取prediction列表或单个prediction记录
RestfulPrediction.java:
通过继承 JAX-RS 中的 javax.ws.rs.core.Application类,并重写getClasses() 来注册这个PredictionRS资源到JAX-RS的上下文(context)中
注意JAX-RS中的 "拦截器" servlet “拦截”与PredictionRS 资源相关的请求。
JAX-RS 的注解
下面是JAX-RS中比较重要的注解类:
Table 1. JAX-RS annotations
Annotation Description
@ApplicationPath(your_application_path) 这个注解是放在你的Application类上的。设置整个应用的路径
@PATH(your_path) 这个类是放在你的javabean类上的。设置路径为 基本URL+/your_path。这个基本URL是基于你应用的路径和web.xml中servlet的URL pattern的。
@POST 用在方法上,表示这个方法会响应HTTP POST请求
@GET 用在方法上,表示这个方法会响应HTTP GET请求
@PUT 用在方法上,表示这个方法会响应HTTP PUT请求
@DELETE 用在方法上,表示这个方法会响应HTTP DELETE 请求
@Produces(MediaType.TEXT_PLAIN[, more-types]) 用在方法上,表示这个方法会返回何种类的响应。例如,如果指定为 ("text/plain") ,则返回纯文本的响应。也可指定 "application/xml" or "application/json"等其他类型。
@Consumes(type[, more-types]) 用在方法上,表示这个方法接收含有特定MIME type的http请求
@PathParam 用在方法上,从URL中获取值注入为该方法的一个参数。例如,/predictions/1 ,则我们就可以通过这个注解,将1获取到,然后作为这个方法的参数,相当方便。
实例:
项目目录结构:
使用maven管理依赖
因为用到的库较多,而库又要依赖其他库,所以建议使用maven来管理,pom.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>JAX-RS_For_RestfulService</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20151123</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>1.19.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>
</project>
predictions.db:
存放记录的文本文件
被读取的文本文件,一条记录一行,每行的内容都有"!"分隔为两部分。
Cornelius Tillman!Managed holistic contingency will grow killer action-items.
Conner Kulas!Vision-oriented zero administration time-frame will generate back-end interfaces.
Loraine Ryan!Triple-buffered scalable function will productize visionary infomediaries.
Patricia Zulauf!Reactive radical knowledge base will aggregate extensible vortals.
River Wiza!Face to face client-server pricing structure will whiteboard robust communities.
Jarred Wehner!Future-proofed 5th generation protocol will strategize web-enabled networks.
Emily Roob!Cross-group fresh-thinking encoding will reinvent dot-com systems.
Elvis Ernser!Customizable assymetric database will visualize virtual action-items.
Kathryn Hilpert!User-centric non-volatile open architecture will iterate world-class vortals.
Tanner Dietrich!Enhanced zero tolerance system engine will evolve turn-key deliverables.
Linnie Funk!Distributed dynamic moratorium will iterate magnetic e-commerce.
Emery Ward!Synergistic demand-driven functionalities will visualize compelling vortals.
Craig Leuschke!Robust intermediate extranet will facilitate best-of-breed synergies.
Shayna Lehner!Digitized optimal conglomeration will exploit proactive relationships.
Hollis McCullough!Universal fault-tolerant architecture will synthesize bleeding-edge channels.
Mina Hayes!Cloned assymetric intranet will enable innovative functionalities.
River Friesen!Decentralized 24/7 hub will target robust web-readiness.
Carmel Becker!Synergistic disintermediate policy will expedite back-end experiences.
Bartholome Walsh!Triple-buffered didactic emulation will visualize integrated channels.
Russel Robel!Configurable intangible alliance will scale sexy ROI.
Charlene Mertz!Triple-buffered neutral collaboration will incubate B2B deliverables.
Letitia Pacocha!User-centric multi-state success will transform proactive convergence.
Lottie Marks!Open-source multi-tasking time-frame will monetize rich partnerships.
Kaden Crona!Optional static definition will unleash dynamic e-tailers.
Everardo Lind!De-engineered systematic emulation will deploy out-of-the-box partnerships.
Lilyan Thompson!Synergistic 24/7 website will transition 24/7 methodologies.
Alessia O'Connell!Reactive value-added middleware will engineer next-generation partnerships.
Reymundo Champlin!Self-enabling reciprocal synergy will generate seamless portals.
Immanuel Bergstrom!Assimilated intermediate superstructure will drive vertical methodologies.
Dahlia Robel!Proactive demand-driven open architecture will innovate impactful networks.
Deven Blanda!Balanced clear-thinking utilisation will expedite collaborative initiatives.
Hiram Gulgowski!Versatile tangible application will maximize rich e-business.
javabean类,使用如:
@XmlRootElement(name = "prediction")
@XmlElement
等注解来指定生成的xml元素。
package predictions3;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Created by AlexY on 2016/6/30.
*/
//指定生成的xml的根元素
@XmlRootElement(name = "prediction")
public class Prediction implements Comparable<Prediction> {
private String who; // 人
private String what; //他的prediction
private int id; //作为搜索的key
@XmlElement
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@XmlElement
public String getWhat() {
return what;
}
public void setWhat(String what) {
this.what = what;
}
@XmlElement
public String getWho() {
return who;
}
public void setWho(String who) {
this.who = who;
}
@Override
public int compareTo(Prediction o) {
return this.id - o.id;
}
// 重写toString方法,这样可以直接以纯文本输出(text/plain)
@Override
public String toString() {
return String.format("%2d:",id)+who+"==>"+what+ "\n";
}
}
PredictionsList :
javabean类,返回整个Predicliton的列表
package predictions3;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by AlexY on 2016/6/30.
*/
@XmlRootElement(name = "predictionList")
public class PredictionsList {
private List<Prediction> preds;
private AtomicInteger predId;
public PredictionsList() {
// 为了线程安全
preds = new CopyOnWriteArrayList<>();
predId = new AtomicInteger();
}
//有个name属性,可以用来修改xml中的元素的名字,(默认值就是程序从getter方法获取的)
@XmlElement
//指定封装这个xml元素的元素,就是在外面在套一层标签
@XmlElementWrapper(name = "predictions")
public List<Prediction> getPredictions() {
return preds;
}
public void setPredictions(List<Prediction> preds) {
this.preds = preds;
}
@Override
public String toString() {
// TODO: 2016/6/30 可以用stringbuffer优化
String s = "";
for (Prediction p : preds){
s += p.toString();
}
return s;
}
public Prediction find(int id){
Prediction pred = null;
// 从list中搜索
// 注意:因为现在list还很短,所以可以使用线性查找,
// 如果当list变成一个很大的有序列表,则最好用 二分查找
for ( Prediction p : preds){
if (p.getId() == id){
pred = p;
break;
}
}
return pred;
}
public int add(String who,String what){
int id = predId.incrementAndGet();
Prediction p = new Prediction();
p.setWho(who);
p.setWhat(what);
p.setId(id);
preds.add(p);
return id;
}
PredictionsRS:
对http请求进行响应,返回对应的类型的请求。
是最重要的一个类。
package predictions3;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletContext;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Created by AlexY on 2016/6/30.
*/
@Path("/")
public class PredictionsRS {
@Context
private ServletContext sctx; //依赖注入
private static PredictionsList plist; // 在populate()方法中设置
public PredictionsRS() {
}
@GET
@Path("/xml")
@Produces({MediaType.APPLICATION_XML})
public Response getXml() {
checkContext();
return Response.ok(plist, "application/xml").build();
}
@GET
@Path("/xml/{id:\\d+}")
@Produces({MediaType.APPLICATION_XML}) //也可以直接用"application/xml"
public Response getXml(@PathParam("id") int id) {
checkContext();
return toRequestedType(id, "application/xml");
}
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/json")
public Response getJson() {
checkContext();
return Response.ok(toJson(plist), "application/json").build();
}
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/json/{id:\\d+}")
public Response getJson(@PathParam("id") int id) {
checkContext();
return toRequestedType(id, "application/json");
}
@GET
@Path("/plain")
@Produces({MediaType.TEXT_PLAIN})
public String getPlain() {
checkContext();
return plist.toString();
}
// 创建新的记录
@POST
@Produces({MediaType.TEXT_PLAIN})
@Path("/create")
public Response create(@FormParam("who") String who, @FormParam("what") String what) {
checkContext();
String msg = null;
// who和what两个字段都必须有
if (null == who || null == what) {
msg = "Property 'who' or 'what' is missing.\n";
return Response.status(Response.Status.BAD_REQUEST).
entity(msg).
type(MediaType.TEXT_PLAIN)
.build();
}
// 创建新的Prediction,并返回它的id
int id = addPrediction(who,what);
msg = "Prediction " + id + " created: (who = " + who + " what = " + what + ").\n";
return Response.ok(msg, "text/plain").build();
}
@PUT
@Produces({MediaType.TEXT_PLAIN})
@Path("/update")
public Response update(@FormParam("id") int id, @FormParam("who") String who, @FormParam("what") String what){
checkContext();
String msg = null;
if ( null == who && null == what){
msg = "Neither who nor what is given: nothing to edit.\n";
}
// 这里id为int ,所以默认肯定是0,我们PredictionsList中没有0的索引
Prediction p = plist.find(id);
if ( null == p){
msg = "There is no prediction with ID " + id + "\n";
}
// 如果有错误消息,则返回
if ( null != msg){
return Response.status(Response.Status.BAD_REQUEST).entity(msg).type(MediaType.TEXT_PLAIN).build();
}
// 更新记录
if ( null != who){
p.setWho(who);
}
if ( null != what){
p.setWhat(what);
}
msg = "Prediction " + id + " has been updated.\n";
return Response.ok(msg,"text/plain").build();
}
@DELETE
@Produces({MediaType.TEXT_PLAIN})
@Path("/delete/{id:\\d+}")
public Response delete(@PathParam("id") int id){
checkContext();
String msg = null;
Prediction p = plist.find(id);
if (null == p){
msg = "There is no prediction with ID " + id + ". Cannot delete.\n";
return Response.status(Response.Status.BAD_REQUEST).entity(msg).type(MediaType.TEXT_PLAIN).build();
}
// 查找到记录,并删除
plist.getPredictions().remove(p);
msg = "Prediction " + id + " deleted.\n";
return Response.ok(msg, "text/plain").build();
}
// 生成Http error 响应 或 指定type的 Ok响应(即 http code 200)
private Response toRequestedType(int id, String type) {
Prediction pred = plist.find(id);
if (null == pred) {
String msg = id + " is a bad ID.\n";
return Response.status(Response.Status.BAD_REQUEST).
entity(msg).
type(MediaType.TEXT_PLAIN).
build();
} else if (type.contains("json")) {
return Response.ok(toJson(pred), type).build();
} else {
return Response.ok(pred, type).build();
}
}
// 工具方法,检查plist是否为null,如果为null,则调用populate()方法,
// 从predictions.db中读取记录
private void checkContext() {
if (plist == null) {
populate();
}
}
// 从文件中读取记录到PredictionsList中
private void populate() {
plist = new PredictionsList();
String filename = "WEB-INF/data/predictions.db";
InputStream in = sctx.getResourceAsStream(filename);
if ( null != in){
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String record = null;
try {
while (null != (record = reader.readLine())) {
System.out.println("record:"+record);
String[] parts = record.split("!");
addPrediction(parts[0], parts[1]);
}
} catch (IOException e) {
throw new RuntimeException("I/O failed!");
}
}
}
// 添加一个新的prediction到list中
private int addPrediction(String who, String what) {
int id = plist.add(who, what);
return id;
}
// 将Prediction转换为 json文档
private String toJson(Prediction prediction){
String json = "If you see this, there's a problem.";
if ( null != prediction){
// 使用Gson库
// Gson gson = new Gson();
// json = gson.toJson(prediction).toString();
try {
// 使用jackson库
json = new ObjectMapper().writeValueAsString(prediction);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return json;
}
// 将PredictionsList转换为 json文档
private String toJson(PredictionsList plist) {
String json = "If you see this, there's a problem.";
try {
json = new ObjectMapper().writeValueAsString(plist);
}
catch(Exception e) { }
return json;
}
}
RestfulPrediction :
继承了Application类,
通过@ApplicationPath 注解指定整个应用的路径。
//指定资源的url路由
@ApplicationPath("/resourcesP")
public class RestfulPrediction extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> set = new HashSet<>();
// 将资源类的class文件添加进去
set.add(PredictionsRS.class);
return set;
}
}
测试:
http://localhost:8080/resourcesP/xml