前提要求
- 对SpringMVC有一定的使用经验
- 有RESTful的经验
概述
JAX-RS(Java API for RESTful Web Services)是Java语言领域RESTful风格的Web服务器的标准规范。直至JavaEE 6 通过JCP组织的JSR311将REST在Java语言领域标准化。JSR311其中一个实现是GlassFish项目的Jersey1.0,在时隔5年后JavaEE7包含了JSR339,即JAX-RS2.0,JAX-RS2.0在前面的版本基础上加上了很多实用功能,比如REST客户端API的定义,异步REST等。
JAX-RS的版本对应是实现的Jersey版本信息,如下表:
JAX-RS标准 | JAX-RS名称 | JAX-RS实现 | JDK版本 |
---|---|---|---|
311 | JAX-RS1.0 | Jersey 1.0 | Java 6 |
339 | JAX-RS 2.0 | Jersey 2.0 | Java 7 |
Spring MVC,其也实现REST,但并没有对JAX-RS标准做实现。若之前有使用Spring MVC的经验,用Jersey起来用容易上手。
入门
Jersey包括三个部分:
- 核心服务器(Core Server):经过提供JAX-RS中标准化的注解和API标准化,能够用直观的方式开发RESTful Web服务。
- 要做RESTful Web服务,就要添加这个依赖。
- 核心客户端(Core Client):Jersey客户端API可以帮助开发者与RESTful服务轻松通讯。
- 如果要做测试,写单元测试,通过Jersey的API去测试,你做的web服务能不能正常通信,能不能正常返回之类的,那就需要添加这个依赖。我们现在目前用不到,我们一般是使用浏览器或者postman等工具去测试
- 集成(Integration):Jersey还提供能够轻松与Spring集成。
这里入门仅仅使用了核心服务器,没有使用Spring,在Tomcat服务容器环境运行。
项目准备
新建Maven项目,打包方式为war,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>org.example</groupId>
<artifactId>jersey01-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.23.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Tomcat Maven插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
编写资源类
提供对外访问的资源的方法(类似Spring MVC中的控制器)
package com.example.web.resource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
// 类似 Spring MVC 处理方法
@GET //表示这个资源是用GET请求方式访问
@Produces(MediaType.TEXT_PLAIN) //指定我们响应的数据格式是什么?这里响应的数据格式是纯文本
public String get(){
return "jersey resource";
}
}
编写资源配置类
作用告诉项目有哪些资源(类似SpringMVC中的配置)
package com.example.web.config;
import com.example.web.resource.PersonResource;
import org.glassfish.jersey.server.ResourceConfig;
// 配置类,配置这个项目有什么资源
public class ApiResourceConfig extends ResourceConfig {
public ApiResourceConfig(){
//注册资源
register(PersonResource.class);
}
}
配置ServletContainer
在web.xml配置ServletContainer(本质是一个Servlet,类似Spring MVC中的前端控制器DispatcherServlet),指定加载资源配置。
<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_4_0.xsd"
version="4.0">
<!-- 配置ServletContainer 类似Spring MVC前端控制器 -->
<servlet>
<servlet-name>jerseyServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.example.web.config.ApiResourceConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jerseyServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
启动测试
使用Tomcat Maven插件启动项目测试,访问路径:localhost/person
注解
Jersey里面提供很多注解,帮助开发者来定义资源的路径,请求方式等等。
@Path
用来指定资源的路径,可以贴资源类上,也可以贴资源类中的方法上。其支持路径参数,也支持路径表达式。一个@Path中的路径是否以“/”开头都没有区别,同样是否以“/”结尾也没有什么区别。
package com.example.web.resource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/{id}")
public String getPerson(@PathParam("id") Long id){
return "Get Person resource" + id + "!";
}
}
启动tomcat,访问路径localhost/person/1试下
package com.example.web.resource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/{username:[a-zA-Z]{5,8}}")
public String getExpression(@PathParam("username") String name){
return "Get Person resource expression " + name;
}
}
启动tomcat,访问路径:localhost/person/lony。但若访问的路径不匹配正则表达式,则访问会出现404。
请求方式注解
用来指定资源的请求,可以贴在资源类中的方法上。在JAX-RS标准定义了@GET,@PUT,@POST,@DELETE,@HEAD,@OPTIONS这些注解,类似与HTTP中的请求方法名。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@POST
@Path("/save")
@Produces(MediaType.TEXT_PLAIN)
public String save(){
return "save Person resouce";
}
}
启动tomcat,使用浏览器访问路径localhost/person/save,访问会报405,这里需要使用Postman用POST方式访问才可以。
@Produces
用来指定返回值给客户端的MIME媒体类型(响应的数据类型),可以贴在资源类上,也可以贴在资源类中的方法上。若同时都贴了,以访问的方法上为准。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
@Produces(MediaType.TEXT_PLAIN)
public class PersonResource {
@GET
@Path("/plain")
public String getTextPlain(){
return "Person Text Plain";
}
@GET
@Path("/html")
@Produces(MediaType.TEXT_HTML)
public String getHtml(){
return "Person HTML";
}
}
访问路径localhost/person/plain,响应头Content-Type:text/plain;访问路径:localhost/person/html,响应头Content-Type:text/html。
@Consumes
用来指定可由资源消耗的MIME媒体类型(请求的数据类型)。@Consumes既可以贴在资源类上,也可以贴资源类中的方法上。若同时都贴了,以访问的方法上为准。而且声明可以不只一种媒体类型。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@POST
@Path("/consumes")
@Consumes("text/plain")
public void postMessage(String message){
System.out.println(message);
}
}
访问路径localhost/person/consumes,请求方式为POST,指定请求头Content-Type:text/plain,服务器没有内容返回。而是把一个204状态码响应给客户端;如果请求头Content-Type不为text/plain,则会返回415(Unsupported Media Type)状态码。访问路径localhost/person/consumes,请求方式POST,指定请求头Content-Type:application/x-www-form-urlencoded,服务器把一个415状态码响应给客户端。
参数注解
@PathParam
用来获取URL请求中的路径参数(前面已演示过)。
@QueryParam
用于获取GET请求中的查询参数,同时也是配置@DefaultValue指定默认值。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET()
@Path("/queryParam")
public String queryParam(@QueryParam("name") String name, @QueryParam("age") int age){
return "name:"+name + ",age:" + age;
}
}
访问路径localhost/person/queryParam,发现形参name为null,形参age为0;访问路径localhost/person/queryParam?name=lony&age=18试下,发现形参name为lony,形参age为18。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET()
@Path("/queryParamDefaultValue")
public String queryParamDefaultValue(@DefaultValue("lucy") @QueryParam("name") String name,
@DefaultValue("20") @QueryParam("age") int age){
return "name:"+name + ",age:" + age;
}
}
访问路径localhost/person/queryParamDefaultValue,发现形参name为lucy,形参age为20
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@DELETE
@Path("/delete")
public void delete(List<Long> ids){
System.out.println(ids);
}
}
访问路径localhost/person/delete,请求方式DELETE,不管是否携带参数,ids打印都为null。但是上面方法形参ids贴@QueryParam(“ids”)注解之后,访问路径localhost/person/delete,请求方式DELETE,ids打印为[];访问路径:localhost/person/delete?ids=1&ids=2,请求方式DELETE,ids打印为[1,2]。
@MatrixParam
用来获取路径上矩阵变量格式的参数,比如请求路径是这样的localhost/person;id=1;name=Lucy,Ben
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET
@Path("/matrix")
public String matrix(@MatrixParam("id") Long id, @MatrixParam("name") List<String> names){
return id + names.toString();
}
}
访问路径localhost/person/matrix;id=1;name=Lucy,Bean,请求方式GET,测试一下。
@FormParam
用来从POST请求的表单参数(请求的MIME媒体类型为application/x-www-form-urlencoded)中获取数据。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@POST
@Path("/from")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void save(@FormParam("name") String name, @FormParam("age") int age){
System.out.println(name + ":" + age);
}
}
访问路径localhost/person/from,请求方式POST,携带表单数据测试一下。
@HeaderParam & @CookieParam
@HeaderParam用来获取请求头,@CookieParam用来获取Cookie。
package com.example.web.resource;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET()
@Path("/headAndCookie")
public String getHeadAndCookid(@HeaderParam("HOST") String host,
@CookieParam("name") String name){
return host + ":" + name;
}
}
访问路径localhost/person/headAndCookie,请求方式GET,携带Cookie测试以下。
@BeanParam
当请求参数不少时,好比客户端提交一个保存Person的POST请求,请求中包含较多Person信息,这时能够用@BeanParam。
package com.example.web.domain;
import lombok.Data;
import javax.ws.rs.FormParam;
@Data
public class Person {
@FormParam("id")
private Long id;
@FormParam("name")
private String name;
@FormParam("age")
private int age;
}
package com.example.web.resource;
import com.example.web.domain.Person;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/beanParam")
public void savePerson(@BeanParam Person person){
System.out.println(person);
}
}
访问localhost/person/beanParam,请求方法POST,携带表单数据测试一下。
@BeanParam可以包含所有的注入参数的注解:@PathParam、@QueryParam、@MatrixParam、HeaderParam、@CookieParam、@FormParam。
@Context
一般可以用于获得一个Java类型的请求或响应的上下文,比如获取ServletConfig、ServletContext、HttpServletRequest、HttpServletResponse和HttpHeaders。
在pom.xml添加如下依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
package com.example.web.resource;
import com.example.web.domain.Person;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@Context
private ServletContext servletContext;
@GET
@Path("/getHttpInfo")
public void getHttpInfo(@Context HttpServletRequest request, @Context HttpServletResponse response){
System.out.println(this);
System.out.println(servletContext);
System.out.println(request);
System.out.println(response);
}
}
访问路径localhost/person/getHttpInfo,请求方式GET测试一下。
生命周期注解
上面的代码演示发现,每次请求打印的this都是不同的。但是Jersey提供如下注解让开发者修改资源实例的生命周期。这些注解是贴在资源类上的。
- @RequestScoped(默认注解),每个新的请求都会创建一个新实例用于处理请求(同一请求的实例不会被重复创建)
- @PerLookup,每次请求都会创建新实例(即使处理的是同一个请求)。
- @Singleton,每一个资源类只创建一个实例。
JSON
Jersey支持MOXy-JSON,JSON-P,Jackson,Jettison处理JSON。我们这里选择Jaskson。
添加依赖
在pom.xml添加如下依赖:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.23.2</version>
</dependency>
配置转换器
在资源配置类中注册JSON转换器
package com.example.web.config;
import com.example.web.resource.PersonResource;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.server.ResourceConfig;
// 配置类,配置这个项目有什么资源
public class ApiResourceConfig extends ResourceConfig {
public ApiResourceConfig(){
//注册资源
register(PersonResource.class);
// 注解转换器
register(JacksonJsonProvider.class);
}
}
实体类
用来封装数据
package com.example.web.domain;
import lombok.Data;
import javax.ws.rs.FormParam;
@Data
public class Student {
private Long id;
private String name;
private int age;
}
资源类
package com.example.web.resource;
import com.example.web.domain.Person;
import com.example.web.domain.Student;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.List;
// 类似 Spring MVC控制器
@Path("/person")
public class PersonResource {
@GET
@Path("/jsonReq")
@Produces("application/json")
public Student getStudent(){ //响应JSON数据
Student student = new Student();
student.setId(12L);
student.setName("哈哈");
student.setAge(23);
return student;
}
@POST
@Path("/jsonRes")
@Consumes(MediaType.APPLICATION_JSON)
public void getStudentJson(Student student){ //接收JSON数据
System.out.println(student);
}
}
访问路径localhost/person/jsonReq,请求方式GET测试一下。
访问路径localhost/person/jsonRes,请求方式POST,携带JSON数据测试一下。
REST接口最佳实践
package com.example.resource;
import com.example.domain.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
/**
* Response类,提供方法设置状态,响应头,响应数据
*/
@Path("/persons")
@Component
public class PersonResource {
@Autowired
private ApplicationContext ctx;
/**
* 删除功能
* 请求方式 DELETE
* 路径/persons/{id}
* 状态204 响应体中没有数据
* @return
*/
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") String id){
return Response.noContent() //设置状态,设置响应头 location
.build();
}
/**
* 更新功能
* 请求方式 PUT
* 路径/persons
* 参数 application/json 在请求体中
* 状态204 响应体中没有数据
* @return
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response update(Person person){
return Response.noContent() //设置状态,设置响应头 location
.build();
}
/**
* 新增功能
* 请求方式POST
* 路径/persons
* 参数 application/json 在请求体中
* 状态201
* 响应数据类型 application/json
* 响应体中的数据就是person数据
* @return
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response save(Person person, @Context UriInfo uriInfo){
URI uri = uriInfo.getAbsolutePathBuilder().path(person.getId() + "").build();
return Response.created(uri) //设置状态,设置响应头 location
.entity(person)
.build();
}
/**
* 分页查询功能
* 请求方式GET
* 路径/persons
* 状态200
* 响应数据类型 application/json
* 响应体中的数据就是多个person数据
* 响应头,设置后端有多少条数据 X-Total-Count
* @return
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response page(){
Person person1 = new Person();
person1.setId(1L);
person1.setName("zhangsan");
person1.setAge(8);
Person person2 = new Person();
person2.setId(2L);
person2.setName("lisi");
person2.setAge(82);
List<Person> people = Arrays.asList(person1, person2);
int count = 20;
return Response.ok(people)
.header("total-count",count)
.build();//设置状态码200,设置响应体中数据
}
/**
* 查看详情功能
* 请求方式GET
* 路径/persons/id值
* 状态200
* 响应数据类型 application/json
* 响应体中的数据就是person数据
* @param id
* @return
*/
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response person(@PathParam("id") Long id){
Person person = new Person();
person.setId(id);
person.setName("zhangsan");
person.setAge(8);
return Response.ok(person).build();//设置状态码200,设置响应体中数据
}
}