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接口总结的主要内容,如果未能解决你的问题,请参考以下文章

使用JAX-RS创建RESTful Web Service

web service013——发布restful风格的webservice,并使用客户端接收接收(基于RESTful的jax-rs使用的是http协议,可以传输json数据或xml数据)

restful基础

为啥使用 JAX-RS / Jersey?

对于 Java 中的 RESTful 服务,JAX-RS 是不是比 Swing、Grails 或 Play 等 MVC 框架更好?

使用拦截器和注入在 JAX-RS 中进行身份验证/授权