JBoss.orgCommunity Documentation

第 27 章 JAX-RS 2.1 Additions

27.1. CompletionStage support
27.2. Reactive Clients API
27.3. Server-Sent Events (SSE)
27.3.1. SSE Server
27.3.2. SSE Broadcasting
27.3.3. SSE Client
27.4. Java API for JSON Binding
27.5. JSON Patch and JSON Merge Patch

JAX-RS 2.1 adds more asynchronous processing support in both the Client and the Server API. The specification adds a Reactive programming style to the Client side and Server-Sent Events (SSE) protocol support to both client and server.

The specification adds support for declaring asynchronous resource methods by returning a CompletionStage instead of using the @Suspended annotation.

The specification defines a new type of invoker named RxInvoker, and a default implementation of this type named CompletionStageRxInvoker. CompletionStageRxInvoker implements Java 8's interface CompletionStage. This interface declares a large number of methods dedicated to managing asynchronous computations.

There is also a new rx method which is used in a similar manner to async.

SSE is part of HTML standard, currently supported by many browsers. It is a server push technology, which provides a way to establish a one-way channel to continuously send data to clients. SSE events are pushed to the client via a long-running HTTP connection. In case of lost connection, clients can retrieve missed events by setting a "Last-Event-ID" HTTP header in a new request.

SSE stream has text/event-stream media type and contains multiple SSE events. SSE event is a data structure encoded with UTF-8 and contains fields and comment. The field can be event, data, id, retry and other kinds of field will be ignored.

From JAX-RS 2.1, Server-sent Events APIs are introduced to support sending, receiving and broadcasting SSE events.

RESTEasy supports both JSON-B and JSON-P. In accordance with the specification, entity providers for JSON-B take precedence over those for JSON-P for all types except JsonValue and its sub-types.

The support for JSON-B is provided by the JsonBindingProvider from resteasy-json-binding-provider module. To satisfy JAX-RS 2.1 requirements, JsonBindingProvider takes precedence over the other providers for dealing with JSON payloads, in particular the Jackson one. The JSON outputs (for the same input) from Jackson and JSON-B reference implementation can be slightly different. As a consequence, in order to allow retaining backward compatibility, RESTEasy offers a resteasy.preferJacksonOverJsonB context property that can be set to true to disable JsonBindingProvider for the current deloyment.

WildFly 14 supports specifying the default value for the resteasy.preferJacksonOverJsonB context property by setting a system property with the same name. Moreover, if no value is set for the context and system properties, it scans JAX-RS deployments for Jackson annotations and sets the property to true if any of those annotations is found.

RESTEasy supports apply partial modification to target resource with JSON Patch/JSON Merge Patch. Instead of sending json request which represents the whole modified resource with HTTP PUT method, the json request only contains the modified part with HTTP PATCH method can do the same job.

JSON Patch request has an array of json object and each JSON object gives the operation to execute against the target resource. Here is an example to modify the target Student resource which has these fields and values: {"firstName":"Alice","id":1,"school":"MiddleWood School"}:

            PATCH /StudentPatchTest/students/1 HTTP/1.1
            Content-Type: application/json-patch+json
            Content-Length: 184
            Host: localhost:8090
            Connection: Keep-Alive

            [{"op":"copy","from":"/firstName","path":"/lastName"},
             {"op":"replace","path":"/firstName","value":"John"},
             {"op":"remove","path":"/school"},
             {"op":"add","path":"/gender","value":"male"}]
                 
                

This JSON Patch request will copy the firstName to lastName field , then change the firstName value to "John". The next operation is remove the school value and add male gender to this "id=1" student resource. After this JSON Path is applied, the target resource will be modified to: {"firstName":"John","gender":"male","id":1,"lastName":"Taylor"}. The operation keyword here can be "add", "remove", "replace", "move", "copy", or "test". The "path" value must be a JSON Pointer value to point the part to apply this JSON Patch.

Unlike use the operation keyword to patch the target resource, JSON Merge Patch request directly send the expect json change and RestEasy merge this change to target resource which identified by the request URI. Like the below JSON Merge Patch request, it remove the "school" value and change the "firstName" to "Green". This is much straightforward:

             PATCH /StudentPatchTest/students/1 HTTP/1.1
             Content-Type: application/merge-patch+json
             Content-Length: 34
             Host: localhost:8090
             Connection: Keep-Alive
             {"firstName":"Green","school":null}
             
            

Enable JSON Patch or JSON Merge Patch only needs correctly annotate the resource method with mediaType: @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) is to enable JSON Patch and @Consumes("application/merge-patch+json") to enable JSON Merge Patch:

            @GET
            @Path("/{id}")
            @Consumes(MediaType.APPLICATION_JSON)
            @Produces(MediaType.APPLICATION_JSON)
            public Student getStudent(@PathParam("id") long id)
            {
            Student student = studentsMap.get(id);
            if (student == null)
            {
            throw new NotFoundException();
            }
            return student;
            }
            @PATCH
            @Path("/{id}")
            @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)
            @Produces(MediaType.APPLICATION_JSON)
            public Student patchStudent(@PathParam("id") long id, Student student)
            {
            if (studentsMap.get(id) == null)
            {
            throw new NotFoundException();
            }
            studentsMap.put(id, student);
            return student;
            }
            @PATCH
            @Path("/{id}")
            @Consumes("application/merge-patch+json")
            @Produces(MediaType.APPLICATION_JSON)
            public Student mergePatchStudent(@PathParam("id") long id, Student student)
            {
            if (studentsMap.get(id) == null)
            {
            throw new NotFoundException();
            }
            studentsMap.put(id, student);
            return student;
            }
            
            

Client side needs create these json objects and send it with http PATCH method.

            //send JSON Patch request
            WebTarget patchTarget = client.target("http://localhost:8090/StudentPatchTest/students/1"));
            javax.json.JsonArray patchRequest = Json.createArrayBuilder()
            .add(Json.createObjectBuilder().add("op", "copy").add("from", "/firstName").add("path", "/lastName").build())
            .build();
            patchTarget.request().build(HttpMethod.PATCH, Entity.entity(patchRequest, MediaType.APPLICATION_JSON_PATCH_JSON)).invoke();
            //send JSON Merge Patch request
            WebTarget patchTarget = client.target("http://localhost:8090/StudentPatchTest/students/1");
            JsonObject object = Json.createObjectBuilder().add("lastName", "Green").addNull("school").build();
            Response result = patchTarget.request().build(HttpMethod.PATCH, Entity.entity(object, "application/merge-patch+json")).invoke();