WebService——JAX-RS2(Jersey)传输格式和响应处理详解

Jersey

1、传输格式

1.1、基本类型

Java的基本类型又叫原生类型,包括4种整型(byte、short、int、long)、2种浮点类型(foat、double)、Unicode编码的字符(char)和布尔类型(boolean)。

Jersey支持全部的基本类型,还支持与之相关的引用类型。

@Path("type")
public class TypeResource {

    @POST
    @Path("b")
    public String postBytes(byte[] bs) {
        for (byte b : bs) {
            System.out.println(b);
        }
        return "byte[]:" + new String(bs);
    }
}

客户端测试:

@Test
public void testBytes() {
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("http://127.0.0.1:8080/ws/type");

    byte[] bytes = "hello".getBytes();
    Response response = webTarget.path("b")
            .request()
            .post(Entity.entity(bytes, MediaType.TEXT_PLAIN_TYPE));
    System.out.println(response.readEntity(String.class));
}
byte[]:hello

1.2、文件类型

Jersey支持传输File类型的数据,以方便客户端直接传递File类实例给服务器端。文件类型的请求,默认使用的媒体类型是Content-Type:text/html

@POST
@Path("f")
public File postFile(File f) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(f))) {
        String s;
        while ((s = br.readLine()) != null) {
            System.out.println(s);
        }
        return f;
    }
}

PostMan测试:
在这里插入图片描述

客户端测试:

@Test
public void testFile() throws IOException {
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("http://127.0.0.1:8080/ws/type");

    URL resource = getClass().getClassLoader().getResource("application.yml");
    File file = new File(resource.getFile());
    Response response = webTarget.path("f")
            .request()
            .post(Entity.entity(file, MediaType.TEXT_PLAIN_TYPE));
    File f = response.readEntity(File.class);
    try (BufferedReader br = new BufferedReader(new FileReader(f))) {
        String s;
        while ((s = br.readLine()) != null) {
            System.out.println(s);
        }
    }
}
server:
  port: 8080

1.3、InputStream类型

Jersey支持Java的两大读写模式,即字节流和字符流。

@POST
@Path("bio")
public String postStream(InputStream is) throws IOException {
    try (BufferedReader br = new BufferedReader(new InputStreamReader(is))){
        StringBuilder result = new StringBuilder();
        String s = null;
        while ((s = br.readLine()) != null) {
            result.append(s).append("\n");
            System.out.println(s);
        }
        return result.toString();
    }
}

PostMan测试:
在这里插入图片描述

客户端测试:

@Test
public void testStream() throws IOException {
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("http://127.0.0.1:8080/ws/type");

    URL resource = getClass().getClassLoader().getResource("application.yml");
    File file = new File(resource.getFile());
    Response response = webTarget.path("bio")
            .request()
            .post(Entity.entity(file, MediaType.TEXT_PLAIN_TYPE));
    String result = response.readEntity(String.class);
    System.out.println(result);
}
server:
  port: 8080

1.4、Reader类型

以字符流作为REST方法参数。

@POST
@Path("cio")
public String postChar(Reader r) throws IOException {
    try (BufferedReader br = new BufferedReader(r)) {
        StringBuilder result = new StringBuilder();
        String s = null;
        while ((s = br.readLine()) != null) {
            result.append(s).append("\n");
            System.out.println(s);
        }
        return result.toString();
    }
}

PostMan测试:
在这里插入图片描述

客户端测试:

@Test
public void testReader() throws IOException {
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("http://127.0.0.1:8080/ws/type");

    URL resource = getClass().getClassLoader().getResource("application.yml");
    File file = new File(resource.getFile());
    Response response = webTarget.path("cio")
            .request()
            .post(Entity.entity(file, MediaType.TEXT_PLAIN_TYPE));
    String result = response.readEntity(String.class);
    System.out.println(result);
}
server:
  port: 8080

1.5、XML类型

XML类型是使用最广泛的数据类型。Jersey对XML类型的数据处理,支持Java领域的两大标准:

  • JAXP(Java API for XML Processing,JSR-206)
  • JAXB(Java Architecture for XML Binding,JSR-222 )
1)、JAXP标准

JAXP包含了DOMSAXStAX3种解析XML的技术标准:

  • DOM是面向文档解析的技术,要求将XML数据全部加载到内存,映射为树和结点模型以实现解析。
  • SAX是事件驱动的流解析技术,通过监听注册事件,触发回调方法以实现解析。
  • StAX是拉式流解析技术,相对于SAX的事件驱动推送技术,拉式解析使得读取过程可以主动推进当前XML位置的指针而不是被动获得解析中的XML数据。

对应的,JAXP定义了3种标准类型的输入/输出接口:

  • 输入:
    • DOMSource
    • SAXSource
    • StreamSource
  • 输出:
    • DOMResult
    • SAXResult
    • StreamResult
DOM

支持输入输出类型分别为DOMSource和Document的请求。

@Path("xml")
public class XMLResource {

    /**
     * getDOMSource()和getDocument()方法使用DOM面相文档解析的技术,
     * 支持输入输出类型分别为DOMSource和Document的请求
     */
    @POST
    @Path("dom")
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    public DOMSource getDOMSource(javax.xml.transform.dom.DOMSource domSource) {
        //需要手动解析xml
        return domSource;
    }

    @POST
    @Path("doc")
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    public Document getDocument(org.w3c.dom.Document document) {
        //需要手动解析xml
        Node person = document.getElementsByTagName("person").item(0);
        Node id = document.getElementsByTagName("id").item(0);
        Node name = document.getElementsByTagName("name").item(0);
        Node age = document.getElementsByTagName("age").item(0);
        System.out.println(id.getTextContent());
        System.out.println(name.getTextContent());
        System.out.println(age.getTextContent());
        return document;
    }
}

在这里插入图片描述

SAX

支持输入输出类型为SAXSource的请求。

/**
 * getSAXSource()方法使用SAX是事件驱动的流解析技术
 * 支持输入输出类型为SAXSource的请求
 */
@POST
@Path("sax")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public SAXSource getSAXSource(javax.xml.transform.sax.SAXSource saxSource) {
    //需手动解析XML
    return saxSource;
}

在这里插入图片描述

StAX

支持输入输出类型为StreamSource的请求。

/**
 * getStreamSource()方法使用StAX拉式流解析技术
 * 支持输入输出类型为StreamSource的请求
 */
@POST
@Path("stream")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public StreamSource getStreamSource(javax.xml.transform.stream.StreamSource streamSource) {
    //需手动解析XML
    return streamSource;
}

在这里插入图片描述

2)、JAXB标准

JAXP的缺点是需要编码解析XML,这增加了开发成本,但对业务逻辑的实现并没有实质的贡献。

JAXB只需要在POJO中定义相关的注解,使其和XML的schema对应,无须对XML进行程序式解析,弥补了JAXP的这一缺点,因此推荐使用JAXB作为XML解析的技术。

JAXB通过序列化和反序列化实现了XML数据和POJO对象的自动转换过程。在运行时,JAXB通过编组(marshall)过程将POJO序列化成ML格式的数据,通过解编(unmarshall)过程将XML格式的数据反序列化为Java对象。

JAXB的注解位于javax.xml.bind.annotation包中,从理论上讲,JAXB解析XML的性能不如JAXP,但使用JAXB的开发效率很高。

Jersey支持使用JAXBElement作为REST方法参数的形式,也支持使用POJO作为REST方法参数的形式,参数使用上有三种形式:

  • JAXBElement
  • POJO
    • POJO字段作为子元素
    • POJO字段作为元素属性
JAXBElement
@POST
@Path("element")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Book getElementEntity(JAXBElement<Book> bookElement) {
    Book book = bookElement.getValue();
    System.out.println(book);
    return book;
}

实体:

@XmlRootElement
public class Book implements Serializable {

    private Long id;
    private String name;
    private Double price;

    public Book() {
    }

    public Book(Long id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @XmlElement(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @XmlElement(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElement(name = "price")
    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

在这里插入图片描述

POJO字段作为子元素
@POST
@Path("pojo1")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Person getPojo1(Person person) {
    System.out.println(person);
    return person;
}

实体:

@XmlRootElement
public class Person {

    private Long id;
    private String name;
    private Integer age;

    @XmlElement(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @XmlElement(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElement(name = "age")
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里插入图片描述

POJO字段作为元素属性
@POST
@Path("pojo2")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Person getPojo2(Person person) {
    System.out.println(person);
    return person;
}

实体:

@XmlRootElement
public class Person {

    private Long id;
    private String name;
    private Integer age;

    @XmlAttribute(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @XmlAttribute(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlAttribute(name = "age")
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里插入图片描述

XMLConstnts.FEATURE_SECURE_PROCESSING

Jersey默认设置了XMLConstants.FEATURE SECURE PROCESSING属性,当属性或者元素过多时,会报“well-formedness eror”这样的警告信息。如果业务逻辑确实需要设计一个繁琐的POJO,可以通过设置
MessageProperties.XML SECURITY_DISABLE参数值为TRUE来屏蔽。

@Component
@ApplicationPath("/ws")
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
		property(MessageProperties.XML_SECURITY_DISABLE, Boolean.TRUE);
    }
}

1.6、JSON类型

JSON类型已经成为Ajax技术中数据传输的实际标准。Jersey提供了4种处理JSON数据的媒体包。

3种解析流派:

  • 基于POJO的JSON绑定
  • 基于JAXB的JSON绑定
  • 以及低级的(逐字的)JSON解析和处理)

4种技术:

  • MOXyJackon的处理方式相同,它们都不支持以JSON对象方式解析JSON数据,而是以绑定方式解析。
  • Jettison支持以JSON对象方式解析JSON数据,同时支持JAXB方式的绑定。
  • JSON-P就只支持JSON对象方式解析这种方式了。
解析方式/JSON支持包MOXyJSON-PJacksonJettison
POJO-based JSON Binding
JAXB-based JSON Binding
Low-level JSON parsing & processing
1)、使用MOXy处理JSON

MOXy是EclipseLink项目的一个模块,MOXy实现了JSR222标准(JAXB2.2)和JSR235标准(SDO2.1.1)。同时,MOXy实现了JSR-353标准(Java API for Processing JSON1.0),以JAXB为基础来实现对JSR353的支持。

定义依赖

MOXy是Jersey默认的JSON解析方式,可以在项目中添加MOXy的依赖包来使用MOXy:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-moxy</artifactId>
</dependency>
定义Application

使用Servlet.3可以不定义web.xml配置。

MOXy的Feature接口实现类是MoxyJsonFeature,默认情况下,Jersey对其自动探测,无须在Applicaion类或其子类显式注册该类。如果不希望Jersey这种默认行为,可以通过设置如下属性来禁用自动探测:

  • CommonProperties.MOXY_JSON_FEATURE_DISABLE两端禁用
  • ServerProperties..MOXY_JSON_FEATURE_DISABLE服务器端禁用
  • ClientProperties.MOXY_JSON_FEATURE_DISABLE客户端禁用
@Component
@ApplicationPath("/api/*")
public class JsonResourceConfig extends ResourceConfig {

    public JsonResourceConfig() {
        //注册资源
        register(StudentResource.class);
        //取消MOXy自动探测
        // property(CommonProperties.MOXY_JSON_FEATURE_DISABLE, true);
    }
}
定义资源类
public class Student {

    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

如果EST应用处于多语言环境中,不要忘记统一开放接口的字符编码;如果统一开放接口同时供前端jsonp使用,不要忘记添加相关媒体类型,示例如下:

@Produces({"application/x-javascript;charset=UTF-8", "application/json;charset=UTF-8"})
@Path("stu")
//类上定义媒体类型后,方法中无需再定义
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class StudentResource {

    private static List<Student> list = new LinkedList<>();

    @POST
    public Student addStudent(Student student) {
        student.setId(System.currentTimeMillis());
        list.add(student);
        return student;
    }

    @GET
    public List<Student> listStudents() {
        System.out.println(list.size());
        return list;
    }
}
测试

在这里插入图片描述

在这里插入图片描述

客户端:

@Test
public void testMOXy() throws IOException {
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("http://127.0.0.1:8080/api");

    Student student = new Student();
    student.setName("jerry");
    student.setAge(17);

    Response response = webTarget.path("stu")
            .request()
            .post(Entity.entity(student, MediaType.APPLICATION_JSON));
    String result = response.readEntity(String.class);
    System.out.println(result);



    Client client2 = ClientBuilder.newClient();
    WebTarget webTarget2 = client.target("http://127.0.0.1:8080/api");
    Response response2 = webTarget.path("stu")
            .request()
            .get();
    String result2 = response2.readEntity(String.class);
    System.out.println(result2);
}

输出:

{"id":1703513139916,"name":"jerry","age":17}
[{"id":1703513139916,"name":"jerry","age":17}]
2)、使用JSON-P处理JSON

JSON-P的全称是Java API for JSON Processing(Java的JSON处理API),而不是JSON with padding(JSONP),两者只是名称相仿,用途大相径庭。JSON-P是JSR353标准
规范,用于统一Java处理JSON格式数据的API,其生产和消费的JSON数据以流的形式类似StAX处理XML,并为JSON数据建立Java对象模型,类似DOM。而JSONP是用于异步请求中传递脚本的回调函数来解决跨域问题。

定义依赖
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-processing</artifactId>
</dependency>
定义Application

使用JSON-P的应用,默认不需要在其Application中注册JsonProcessingFeature,除非使用了如下设置,依次用于在服务器和客户端两侧去活JSON-P功能、在服务器端去活
JSON-P功能、在客户端去活JSON-P功能:

  • CommonProperties.JSON_PROCESSING_FEATURE_DISABLE
  • ServerProperties.JSON_PROCESSING_FEATURE_DISABLE
  • ClientProperties.JSON_PROCESSING_FEATURE_DISABLE

JsonGenerator.PRETTY_PRINTING属性用于格式化JSON数据的输出,当属性值为TRUE时,MesageBodyReader和MessageBody Writer实例会对JSON数据进行额外处理,使得JSON数据可以格式化打印。该属性的设置在Application中:

@Component
@ApplicationPath("/api/*")
public class JsonResourceConfig extends ResourceConfig {

    public JsonResourceConfig() {
        //注册资源
        register(StudentResource.class);
    
        property(JsonGenerator.PRETTY_PRINTING, true);
    }
}
定义资源类
@Path("stu")
//类上定义媒体类型后,方法中无需再定义
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class StudentResource {

    private static List<Student> list = new LinkedList<>();

    @POST
    public Student addStudent(JsonObject jsonObject) {
        Student student = new Student();
        student.setId(System.currentTimeMillis());
        student.setName(jsonObject.getString("name"));
        student.setAge(jsonObject.getInt("age"));
        list.add(student);
        return student;
    }

    @GET
    public JsonArray listStudents() {
        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
        for (Student stu : list) {
            JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
            objectBuilder.add("id", stu.getId());
            objectBuilder.add("name", stu.getName());
            objectBuilder.add("age", stu.getAge());
            arrayBuilder.add(objectBuilder.build());
        }
        return arrayBuilder.build();
    }
}
测试

在这里插入图片描述

在这里插入图片描述

3)、使用Jackson处理JSON

Jackson是一种流行的JSON支持技术,Jackson提供了3种JSON解析方式:

  • 第一种是基于流式API的增量式解析/生成JSON的方式,读写JSON内容的过程是通过离散事件触发的,其底层基于StAX API读取JSON使用org.codehaus.jackson.JsonParser,写入JSON使用org.codehaus.jackson.JsonGenerator
  • 第二种是基于树型结构的内存模型,提供一种不变式的JsonNode内存树模型,类似DOM树。
  • 第三种是基于数据绑定的方式,org.codehaus.jackson.map.ObjectMapper解析,使用JAXB的注解。
定义依赖
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
</dependency>
定义Application

使用Jackson的应用,需要在其Application中注册JacksonFeature。同时,如果有必要根据不同的实体类做详细的解析,可以注册ContextResolver的实现类。

@Component
@ApplicationPath("/api/*")
public class JsonResourceConfig extends ResourceConfig {

    public JsonResourceConfig() {
        //注册资源
        register(StudentResource.class);
        register(JacksonFeature.class);
        //注册ContextResolver的实现类JsonContextProvider
        register(JsonContextProvider.class);
    }
}
定义POJO

本例定义了3种不同方式的POJO,以演示Jackson处理JSON的多种方式:

  • JsonStudent:仅用JAXB注解
  • JsonHybridStudent:混用JAXB注解和Jackson注解
  • JsonNoJaxbStudent:不使用注解
//仅用JAXB注解
@XmlRootElement
@XmlType(propOrder = {"id","name", "age"})
public class JsonStudent {

    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "JsonStudent{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//JAXB注解和Jackson注解混用
@XmlRootElement
public class JsonHybridStudent {

    @JsonProperty("id")
    private Long id;
    @JsonProperty("name")
    private String name;
    @JsonProperty("age")
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "JsonHybridStudent{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//不使用任何注解
public class JsonNoJaxbStudent {
    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "JsonNoJaxbStudent{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
定义资源类
@Path("stu")
//类上定义媒体类型后,方法中无需再定义
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class StudentResource {

    @Path("jaxb")
    @GET
    public JsonStudent getJsonStudent() {
        JsonStudent jsonStudent = new JsonStudent();
        jsonStudent.setId(1l);
        jsonStudent.setName("jaxb");
        jsonStudent.setAge(18);
        return jsonStudent;
    }

    @Path("hybird")
    @GET
    public JsonHybridStudent getJsonHybridStudent() {
        JsonHybridStudent jsonHybridStudent = new JsonHybridStudent();
        jsonHybridStudent.setId(1l);
        jsonHybridStudent.setName("hybird");
        jsonHybridStudent.setAge(18);
        return jsonHybridStudent;
    }

    @Path("nojaxb")
    @GET
    public JsonNoJaxbStudent getJsonNoJaxbStudent() {
        JsonNoJaxbStudent jsonNoJaxbStudent = new JsonNoJaxbStudent();
        jsonNoJaxbStudent.setId(1l);
        jsonNoJaxbStudent.setName("nojaxb");
        jsonNoJaxbStudent.setAge(18);
        return jsonNoJaxbStudent;
    }
}
上下文解析实现类

JsonContextProvider是ContextResolver(上下文解析器)的实现类,其作用是根据上下文提供的POO类型,分别提供两种解析方式。第一种是默认的方式,第二种是混合使用
Jackson和Jaxb。两种解析方式的示例代码如下。

@Provider
public class JsonContextProvider implements ContextResolver<ObjectMapper> {

    final ObjectMapper d;
    final ObjectMapper c;

    public JsonContextProvider() {
        this.d = createDefaultMapper();
        this.c = createCombinedMapper();
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        if (type == JsonHybridStudent.class) {
            System.out.println("hybird");
            return c;
        } else {
            System.out.println("other");
            return d;
        }
    }

    private static ObjectMapper createCombinedMapper() {
        ObjectMapper result = new ObjectMapper();
        result.setAnnotationIntrospectors(new JacksonAnnotationIntrospector(), new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()));
        return result;
    }

    private static ObjectMapper createDefaultMapper() {
        ObjectMapper result = new ObjectMapper();
        result.configure(SerializationFeature.INDENT_OUTPUT, true);
        return result;
    }
}
测试

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4)、使用Jettison处理JSON

Jettison是一种使用StAX来解析JSON的实现。Jettison支持两种JSON映射到XML的方式:

  • Jersey默认使用MAPPED方式
  • 另一种叫做BadgerFish方式
定义依赖
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jettison</artifactId>
</dependency>
定义Application

使用Jettison的应用,需要在其Application中注册JettisonFeature。同时,如果有必要根据不同的实体类做详细的解析,可以注册ContextResolver的实现类。

@Component
@ApplicationPath("/api/*")
public class JsonResourceConfig extends ResourceConfig {

    public JsonResourceConfig() {
        //注册资源
        register(StudentResource.class);
        register(JettisonFeature.class);
        //注册ContextResolver的实现类JsonContextProvider
        register(JettisonJsonContextResolver.class);
    }
}
定义POJO

本例定义了两个类名不同、内容相同的POJO(Student1和Student2),用以演示Jettison对JSON数据以JETTISON_MAPPED(默认)BADGERFISH两种不同方式的处理情况。

@XmlRootElement
public class Student1 {
    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "Student1{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
@XmlRootElement
public class Student2 {
    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "Student1{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
定义资源类
@Path("stu")
//类上定义媒体类型后,方法中无需再定义
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class StudentResource {

    @Path("mapped")
    @GET
    public Student1 getStudent1() {
        Student1 student1 = new Student1();
        student1.setId(1l);
        student1.setName("mapped");
        student1.setAge(18);
        return student1;
    }

    @Path("badgerfish")
    @GET
    public Student2 getStudent2() {
        Student2 student2 = new Student2();
        student2.setId(1l);
        student2.setName("badgerfish");
        student2.setAge(18);
        return student2;
    }
}
上下文解析实现类
@Provider
public class JettisonJsonContextResolver implements ContextResolver<JAXBContext> {

    private final JAXBContext c1;
    private final JAXBContext c2;

    public JettisonJsonContextResolver() throws JAXBException {
        Class[] clz = new Class[]{Student1.class, Student2.class};
        this.c1 = new JettisonJaxbContext(JettisonConfig.DEFAULT, clz);
        this.c2 = new JettisonJaxbContext(JettisonConfig.badgerFish().build(), clz);
    }

    @Override
    public JAXBContext getContext(Class<?> type) {
        if (type == Student1.class) {
            System.out.println("mapped");
            return c1;
        } else {
            System.out.println("badgerfish");
            return c2;
        }
    }
}

在这段代码中,JsonContextResolver定义了两种JAXBContext分别使用MAPPED方式或者BadgerFish方式。这两种方式的参数信息来自Jettision依赖包的
JettisonConfig类。在实现接口方法getContext()中,根据不同的POJO类型,返回两种JAXBContext实例之一。通过这样的实现,当流程获取JSON上下文时,既可使用Jettision依赖包完成对相关POJO的处理。

测试

注意Mapped和Badgerfish两种方式的JSON数据内容不同。

在这里插入图片描述

在这里插入图片描述

2、返回类型

2.1、void

在返回值类型是void的响应中,其响应实体为空,HTTP状态码为204

@Path("return")
public class ReturnResource {

    @Path("{s}")
    @DELETE
    public void delete(@PathParam("s") String s) {
        System.out.println(s);
    }
}

因为delete操作无须返回更多的关于资源表述的信息,因此该方法没有返回值,即返回值类型为void。

2.2、Response

在返回值类型为Response的响应中,响应实体为Response类的entity(方法定义的实体类实例。如果该内容为空,则HTTP状态码为204,否则HTTP状态码为200OK。

@Path("c")
@POST
public Response get(String s) {
    //构建无返回值的响应实例,即响应状态码为204
    // Response response = Response.noContent().build();
    return Response.ok().entity(s).build();
}

在这里插入图片描述

2.3、GenericEntity

通用实体类型作为返回值的情况并不常用。其形式是构造一个统一的实体实例并将其返回,实体实例作为第一个参数、该实体类型作为第二个参数。

@Path("g")
@POST
public GenericEntity<String> get(byte[] bs) {
    return new GenericEntity<String>(new String(bs), String.class);
}

在这里插入图片描述

2.4、File

@Path("f")
@POST
public File get(File f) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(f))) {
        String s = null;
        while ((s = br.readLine()) != null) {
            System.out.println(s);
        }
    }
    return f;
}

在这里插入图片描述

2.5、自定义类型

@XmlRootElement
public class Book implements Serializable {

    private Long id;
    private String name;
    private Double price;

    public Book() {
    }

    public Book(Long id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @XmlElement(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @XmlElement(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElement(name = "price")
    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
@Path("book")
@POST
@Produces(MediaType.APPLICATION_XML)
public Book get() {
    Book book = new Book();
    book.setId(1l);
    book.setName("三国演义");
    book.setPrice(33.45);
    return book;
}

在这里插入图片描述

3、异常处理

3.1、处理状态码

HTTP常用状态码:

状态码含义
200 OK服务器正常响应
201 Created创建新实体,响应头Location指定访问该实体的URL
202 Accepted服务器接受请求,处理尚未完成。可用于异步处理机制
204 No Content服务器正常响应,但响应实体为空
301 Moved Permanently请求资源的地址发生永久变动,响应头Location指定新的URL
302 Found请求资源的地址发生临时变动
304 Not Modified客户端缓存资源依然有效
400 Bad Request请求信息出现语法错误
401 Unauthorized请求资源无法授权给未验证用户
403 Forbidden请求资源未授权当前用户
404 Not Found请求资源不存在
405 Method Not Allowed请求方法不匹配
406 Not Acceptable请求资源的媒体类型不匹配
500 Internal Server Error服务器内部错误,意外终止响应
501 Not Implemented服务器不支持当前请求

JAX-RS2规定的REST式的Web服务的基本异常类型为运行时异常WebApplicationException类。该类包含3个主要的子类分别对应如下内容。

  • HTTP状态码为3xx的重定向类RedirectionException;
  • HTTP状态码为4xx的请求错误类ClientErrorException;
  • HTTP状态码为5xx的服务器错误类ServerErrorException;

它们各自的子类对照HTTP状态码再细分,比如常见的HTTP状态码404错误,对应的错误类为NotFoundException。

除了Jersey提供的标准异常类型,我们也可以根据业务需要自定义相关的业务异常类:

public class Jaxrs2GuideNotFoundException extends WebApplicationException {

    public Jaxrs2GuideNotFoundException() {
        //定义HTTP状态
        super(Response.Status.NOT_FOUND);
    }
    
    public Jaxrs2GuideNotFoundException(String message) {
        super(message);
    }
}

3.2、ExceptionMapper

Jersey框架为我们提供了更为通用的异常处理方式。通过实现ExceptionMapper接口并使用@Provider注解将其定义为一个Provider,可以实现通用的异常的面向切面处理,而非针对某一个资源方法的异常处理,示例如下。

@Provider
public class EntityNotFoundMapper implements ExceptionMapper<Jaxrs2GuideNotFoundException> {

    //拦截并返回新的响应实例
    @Override
    public Response toResponse(Jaxrs2GuideNotFoundException exception) {
        return Response.status(404)
                       .entity("自定义的404:" + exception.getMessage())
                       .type("text/plain")
                       .build();
    }
}

EntityNotFoundMapper实现了ExceptionMapper接口,并提供了泛型类型为前述刚定义的Jaxrs2GuideNotFoundException类;当响应中发生了
Jaxrs2GuideNotFoundException类型的异常,响应流程就会被拦截并补充HTTP状态码和异常消息,以文本作为媒体格式返回给客户端。

3.3、实例

使用上文中的自定义404异常Jaxrs2GuideNotFoundException和自定义切面处理EntityNotFoundMapper。

注册Provider:

@Component
@ApplicationPath("/ws")
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
        //注册单个资源类
        register(ReturnResource.class);
        //注册异常处理Provider
        register(EntityNotFoundMapper.class);
        //或直接声明资源包所在位置
        // packages("pers.zhang.resource");
       
    }
}

资源类:

@Path("return")
public class ReturnResource {

    @Path("ex")
    @GET
    public void getEx() {
        throw new Jaxrs2GuideNotFoundException("Jaxrs2GuideNotFoundException");
    }

}

测试:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值