Java基于JAX-RS开发Restful接口总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基于JAX-RS开发Restful接口总结相关的知识,希望对你有一定的参考价值。
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。
下面结合上面的几个常用的注解来进行说明。
一、导入依赖
maven:
<dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> <version>2.0</version> </dependency>
gradle:
compile "javax.ws.rs:javax.ws.rs-api:2.0"
二、编写测试代码
下面在类上和方法上都加了@Path注解,这是一个请求路径映射的最简配置(类和方法上的@Path路径都只是使用一个"/"斜杠表示):
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.GET; import javax.ws.rs.Path; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @GET @Path("/") public String testMethod() { LOGGER.info("===TestController#test==="); return "TestController#test"; } }
三、启动web容器,在浏览器中访问 "http://localhost:8080/"
上面的代码中,由于在类和方法的@path注解的value值都只是一个"/"斜杠,所以直接访问"http://localhost:8080"就能将请求映射到TestController类的testMethod()方法中。
四:@Path注解的几个测试
如果在类上不使用@Path注解,而仅在方法上使用@Path注解会怎么样呢?
这次,将类上的@path注解去掉了,启动web容器,再次访问"http://localhost:8080/‘:
相反,如果仅在类上使用@Path注解,而在方法上没有使用@Path注解,这显然是没有任何意义的,因为我们使用@Path注解的目的就是将请求映射到某个方法中。
总结:在类和方法都要有@Path注解,缺一不可,否则,请求就无法映射到类的方法中。
在类上使用@Path("/class"),在方法上使用@Path("/test01")这样的方式:
@Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @GET @Path("/") public String testMethod() { LOGGER.info("===TestController#test==="); return "TestController#test"; } @GET @Path("/test01") public String test01() { LOGGER.info("===TestController#test01==="); return "TestController#test01"; } }
启动web容器,在浏览器中访问"http://localhost:8080/class":
在浏览器中访问:http://localhost:8080/class/test01
@POST注解测试:
图中test02()方法上使用了@POST注解,如果在浏览器中直接访问"http://localhost:8080/class/test02",会报405:
因为请求方式不对,所以会返回状态码405。下面使用火狐浏览器的Poster插件来发送POST请求:
点击POST按钮,向服务器发送post请求,响应结果如下:
给test03()方法加上HttpServletRequest参数:
经测试,POST请求是可以发送成功的,但是request的值为null:
给HttpServletRequest参数加上@Context注解就好了:
下面测试一下HttpServletRequest的请求头:
/** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @POST @Path("/test03") public String test03(@Context HttpServletRequest request) { if (request != null) { Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); LOGGER.info(name + " = " + value); } } return "TestController#test03"; } }
使用poster插件发送请求时在请求头中传一个参数,比如name=zhangsan:
[17/04/23 12:46:08:142][com.zhaopin.api.test.TestController-test03] host = localhost:8080 [17/04/23 12:46:08:143][com.zhaopin.api.test.TestController-test03] user-agent = Mozilla/5.0 (Windows NT 6.1; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0 [17/04/23 12:46:08:144][com.zhaopin.api.test.TestController-test03] accept = */* [17/04/23 12:46:08:144][com.zhaopin.api.test.TestController-test03] accept-language = zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 [17/04/23 12:46:08:144][com.zhaopin.api.test.TestController-test03] accept-encoding = gzip, deflate [17/04/23 12:46:08:145][com.zhaopin.api.test.TestController-test03] name = zhangsan [17/04/23 12:46:08:145][com.zhaopin.api.test.TestController-test03] connection = keep-alive [17/04/23 12:46:08:145][com.zhaopin.api.test.TestController-test03] content-length = 0
也可以使用request.getHeader("xxx")方法来获取请求头中的信息:
测试获取请求体(RequestBody):
(1)下面的test04(String name)方法有一个name参数,并且该参数没有加任何注解:
下面使用火狐浏览器的poster插件发送请求:
看控制台上的打印结果,说明服务器拿到了这个参数:
在上面使用poster插件发送请求时的Content-Type是text/xml,换成别的会怎么样呢?
打印结果:
将Content-Type换成application/json试试:
依然能够拿到数据:
如果我们使用多个参数呢?比如,下面的test05()方法有两个参数,如果启动服务器在浏览器访问的话,会响应500状态码:
说明服务器出现异常。怎么办呢?
可以使用json来解决,就是请求体传递的是一个json格式的字符串,比如:
然后test05(String jsonString)方法使用一个String类型的参数来接收,就像下面这样:
拿到了json格式的字符串,我们就可以对将这个json解析成对象了。
比如,先创建一个Person对象如下:
/** * Created by SYJ on 2017/4/23. */ public class Person { private String name; private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Person{" + "name=‘" + name + ‘\\‘‘ + ", address=‘" + address + ‘\\‘‘ + ‘}‘; } }
然后使用fastjson将json格式的字符串解析成Person对象:
当然,也可以直接使用一个Person对象作为参数来接收json格式的数据:
注意在请求的时候,Content Type必须是"application/json"格式(否则会响应415,不支持的Media Type媒体类型)。
下面看一下,如果在上面的请求的时候如果Content Type不是application/json的情况:
后台打印输出的错误信息:
四月 23, 2017 3:31:40 下午 org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor aroundReadFrom 严重: MessageBodyReader not found for media type=text/xml, type=class com.zhaopin.api.test.Person, genericType=class com.zhaopin.api.test.Person.
这说明如果方法的参数是一个对象的话,要求请求的Content Type必须是"application/json",并且请求体必须json格式的数据,否则无法完成属性的映射。
再来看一个问题,如果请求的json数据中,多传了一个Person对象中没有的字段会怎么样呢?
响应的状态码为400:
意思是说,在Person类中没有找到"mail"这个属性,没有标记为忽略。什么意思呢?
我们需要在Person类上加上一个注解@JsonIgnoreProperties,并指定ignoreUnknown的值为true(默认为false),来忽略Person中没有的字段:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * Created by SYJ on 2017/4/23. */ @JsonIgnoreProperties(ignoreUnknown = true) public class Person { private String name; private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Person{" + "name=‘" + name + ‘\\‘‘ + ", address=‘" + address + ‘\\‘‘ + ‘}‘; } }
这次再发送请求,就不会报400的错误了,后台接收到了数据,只是Person中没有的mail字段被忽略掉了:
@JsonIgnore:这个注解用来忽略某些字段,可以用在POJO类的字段或者Getter方法上,用在Setter方法时,和字段上效果一样。这个注解只能用在POJO存在的字段要忽略的情况。
@JsonIgnoreProperties(ignoreUnknown = true),将这个注解写在POJO类上之后,就会忽略类中不存在的字段。这个注解还可以指定要忽略的字段。使用方法如下:
@JsonIgnoreProperties({ "internalId", "secretKey" }),指定的字段不会被序列化和反序列化。
相反,如果json中少传一个字段呢?比如下面的请求中,只传了一个name属性,没有传address属性:
请求也成功了,只是address这个字段的值为null:
我们给Person类增加一个Integer类型的age属性:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * Created by SYJ on 2017/4/23. */ @JsonIgnoreProperties(ignoreUnknown = true) public class Person { private String name; private Integer age; private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Person{" + "name=‘" + name + ‘\\‘‘ + ", age=" + age + ", address=‘" + address + ‘\\‘‘ + ‘}‘; } }
请求的json中,age的值可以加引号,也可以不加引号,后台会自动识别为Integer类型:
但是如果你给age设置了一个不能转成整数的值,会报如下错误:
错误信息的意思是说,qq不是一个有效的整数,也无法转成整数。
下面看一下返回值,上面都是返回一个String类型,下面我们直接返回一个对象Person:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @POST @Path("/test08") public Person test08(Person person) { LOGGER.info(person.toString()); return person; } }
发送请求:
响应500了:
后台服务器报错信息:
四月 23, 2017 4:44:54 下午 org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
严重: MessageBodyWriter not found for media type=application/xml, type=class com.zhaopin.api.test.Person,
genericType=class com.zhaopin.api.test.Person.
因为默然返回的数据格式不是json,所以需要在方法上添加@Produces("application/json")注解来指定返回的数据格式:
再次请求,返回的Person对象被自动转成了json格式:
返回值如果是一个Map的话:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; import java.util.HashMap; import java.util.Map; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @POST @Path("/test08") @Produces("application/json") public Map test08(Person person) { LOGGER.info(person.toString()); HashMap<Object, Object> map = new HashMap<>(); map.put("A", "a"); map.put("B", "b"); return map; } }
请求:
响应:
说明,Map也是可以被自动转成json格式的。
如果返回值类型是一个List呢?
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; import java.util.ArrayList; import java.util.List; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @POST @Path("/test09") @Produces("application/json") public List<Object> test09(Person person) { LOGGER.info(person.toString()); List<Object> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); return list; } }
请求:
响应结果:
返回的List被转成了一个json数组形式,说明List也能被成功转成json格式。
如果方法接收一个List<Person>,返回一个List<Person>呢?
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; import java.util.List; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @POST @Path("/test10") @Produces("application/json") public List<Person> test10(List<Person> personList) { LOGGER.info(personList.toString()); return personList; } }
请求(Json数组中有一个Person的例子):
响应:
再请求(Json数组中有多个Person的例子):
响应:
下面我们将Person类搞复杂一点,给Person类增加一个List<Person>属性,表示一个人(Person)可以有多个朋友(朋友也是Person):
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; /** * Created by SYJ on 2017/4/23. */ @JsonIgnoreProperties(ignoreUnknown = true) public class Person { private String name; private Integer age; private String address; private List<Person> friends; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public List<Person> getFriends() { return friends; } public void setFriends(List<Person> friends) { this.friends = friends; } @Override public String toString() { return "Person{" + "name=‘" + name + ‘\\‘‘ + ", age=" + age + ", address=‘" + address + ‘\\‘‘ + ", friends=" + friends + ‘}‘; } }
方法的参数和返回值都是Person:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @POST @Path("/test11") @Produces("application/json") public Person test11(Person person) { LOGGER.info(person.toString()); return person; } }
请求的json数据如下:
发送请求:
响应:
将返回的json数据格式化后如下:
你可能注意到了,上面的json数据中有两个null值,我有两个朋友分别是小张和小王,但是小张和小王没有朋友。
使用@PathParam注解来匹配获取URL路径中的参数:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @GET @Path("/test12/{name}")public String test12(@PathParam("name")String name) { LOGGER.info("name=" + name); return name; } }
发送请求,在url路径中传递参数,如下图所示发送了一个字符串"刘备",这种方式只支持GET请求:
响应:
说明通过@PathParam注解能够获取到URL路径中传递过来的参数。
下面通过注解来获取URL路径中传递的一个名为"name"的请求参数,这种方式同样只支持GET请求:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @GET @Path("/test13") public String test13(@QueryParam("name")String name) { LOGGER.info("name=" + name); return name; } }
发送请求,注意在URL路径中传递参数使用的格式是"?key=value"的形式:
响应:
下面是从URL中获取传递的多个参数(String类型的name和Integer类型的age):
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.ws.rs.*; /** * Created by SYJ on 2017/4/23. */ @Component @Path("/class") public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @GET @Path("/test13") public String test13(@QueryParam("name")String name, @QueryParam("age")Integer age) { LOGGER.info("name=" + name + ",age=" + age); return "name=" + name + ",age=" + age; } }
发送GET请求,在URL中传递name和age两个参数:
得到的响应为:
如果没有传递name或者age:
请求也没有报错,得到的响应结果中,name和age的值都是null:
以上是关于Java基于JAX-RS开发Restful接口总结的主要内容,如果未能解决你的问题,请参考以下文章
web service013——发布restful风格的webservice,并使用客户端接收接收(基于RESTful的jax-rs使用的是http协议,可以传输json数据或xml数据)
对于 Java 中的 RESTful 服务,JAX-RS 是不是比 Swing、Grails 或 Play 等 MVC 框架更好?