JAX-RS发布多个对象

Thy*_*hys 74 java rest jax-rs

我有一个方法;

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(ObjectOne objectOne, ObjectTwo objectTwo)
Run Code Online (Sandbox Code Playgroud)

现在我知道我可以用json格式发布一个对象,只需将它放入体内即可.但是可以做多个对象吗?如果是这样,怎么样?

tin*_*e2k 84

不能像Tarlog那样正确地使用你的方法.

但是,您可以这样做:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects)
Run Code Online (Sandbox Code Playgroud)

或这个:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(BeanWithObjectOneAndObjectTwo containerObject)
Run Code Online (Sandbox Code Playgroud)

此外,您始终可以将方法与GET参数组合使用:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, @QueryParam("objectTwoId") long objectTwoId)
Run Code Online (Sandbox Code Playgroud)

  • 最后一个(使用QueryParam)不起作用.错误 - "服务器拒绝此请求,因为请求实体所采用的方法所请求的资源不支持该格式(不支持的媒体类型)" (3认同)

Tar*_*log 63

答案是否定的.

原因很简单:这是关于您可以在方法中接收的参数.它们必须与请求相关.对?所以它们必须是标题或cookie或查询参数或矩阵参数或路径参数或请求体.(只是为了讲述完整的故事,还有其他类型的参数称为上下文).

现在,当您在请求中收到JSON对象时,您会在请求正文中收到它 .请求可能有多少个尸体?唯一的一个.所以你只能收到一个JSON对象.

  • @Scholle我想知道您是如何确定我的答案是关于后续请求的? (2认同)

Pau*_*tha 33

如果我们看看OP正在尝试做什么,他/她正试图发布两个(可能不相关的)JSON对象.首先尝试发送一个部分作为正文的任何​​解决方案,以及作为其他一个参数的一部分,IMO,都是可怕的解决方案.POST数据应该放在正文中.做某事只是因为它有效才是正确的.一些解决方法可能违反了基本的REST原则.

我看到了一些解决方案

  1. 使用application/x-www-form-urlencoded
  2. 使用Multipart
  3. 只需将它们包装在单个父对象中即可

1.使用application/x-www-form-urlencoded

另一种选择是使用application/x-www-form-urlencoded.我们实际上可以拥有JSON值.为了考试

curl -v http://localhost:8080/api/model \
     -d 'one={"modelOne":"helloone"}' \
     -d 'two={"modelTwo":"hellotwo"}'

public class ModelOne {
    public String modelOne;
}

public class ModelTwo {
    public String modelTwo;
}

@Path("model")
public class ModelResource {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public String post(@FormParam("one") ModelOne modelOne,
                       @FormParam("two") ModelTwo modelTwo) {
        return modelOne.modelOne + ":" + modelTwo.modelTwo;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们需要让它工作的一件事是ParamConverterProvider让它工作.以下是泽西队的Michal Gadjos实施的(在此处有解释).

@Provider
public class JacksonJsonParamConverterProvider implements ParamConverterProvider {

    @Context
    private Providers providers;

    @Override
    public <T> ParamConverter<T> getConverter(final Class<T> rawType,
                                              final Type genericType,
                                              final Annotation[] annotations) {
        // Check whether we can convert the given type with Jackson.
        final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
                genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
        if (mbr == null
              || !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
            return null;
        }

        // Obtain custom ObjectMapper for special handling.
        final ContextResolver<ObjectMapper> contextResolver = providers
                .getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);

        final ObjectMapper mapper = contextResolver != null ?
                contextResolver.getContext(rawType) : new ObjectMapper();

        // Create ParamConverter.
        return new ParamConverter<T>() {

            @Override
            public T fromString(final String value) {
                try {
                    return mapper.reader(rawType).readValue(value);
                } catch (IOException e) {
                    throw new ProcessingException(e);
                }
            }

            @Override
            public String toString(final T value) {
                try {
                    return mapper.writer().writeValueAsString(value);
                } catch (JsonProcessingException e) {
                    throw new ProcessingException(e);
                }
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您不扫描资源和提供程序,只需注册此提供程序,上面的示例应该可行.

2.使用Multipart

没有人提到的一个解决方案是使用multipart.这允许我们在请求中发送任意部分.由于每个请求只能有一个实体主体,因此多部分是解决方法,因为它允许将不同的部分(具有自己的内容类型)作为实体主体的一部分.

以下是使用Jersey的示例(请参阅此处的官方文档)

依赖

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>${jersey-2.x.version}</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

注册 MultipartFeature

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/api")
public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {
        packages("stackoverflow.jersey");
        register(MultiPartFeature.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

资源类

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataParam;

@Path("foobar")
public class MultipartResource {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response postFooBar(@FormDataParam("foo") Foo foo,
                               @FormDataParam("bar") Bar bar) {
        String response = foo.foo + "; " + bar.bar;
        return Response.ok(response).build();
    }

    public static class Foo {
        public String foo;
    }

    public static class Bar {
        public String bar;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,对于一些客户来说,棘手的部分是没有办法设置Content-Type每个身体部位,这是上述工作所必需的.多部分提供程序将根据每个部分的类型查找邮件正文阅读器.如果它没有设置application/json或类型,Foo或者Bar有一个阅读器,这将失败.我们将在这里使用JSON.没有额外的配置,但有一个阅读器可用.我会用杰克逊.使用以下依赖项,不需要其他配置,因为将通过类路径扫描发现提供程序.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey-2.x.version}</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

现在测试.我将使用cURL.你可以看到我明确地Content-Type为每个部分设置了type.这-F意味着不同的部分.(请参阅帖子的最底部,了解请求主体的实际外观.)

curl -v -X POST \ -H "Content-Type:multipart/form-data" \ -F "bar={\"bar\":\"BarBar\"};type=application/json" \ -F "foo={\"foo\":\"FooFoo\"};type=application/json" \ http://localhost:8080/api/foobar
结果: FooFoo; BarBar

结果完全符合我们的预期.如果查看资源方法,我们所做的就是返回foo.foo + "; " + bar.bar从两个JSON对象收集的字符串.

您可以在下面的链接中看到使用一些不同JAX-RS客户端的一些示例.您还将从这些不同的JAX-RS实现中看到一些服务器端示例.每个链接都应该包含一个指向该实现的官方文档的链接

还有其他JAX-RS实现,但您需要自己查找文档.以上三个是我唯一经历过的.

至于Javascript客户端,我看到的大部分示例(例如其中一些涉及设置Content-Type为undefined/false(使用FormData),让浏览器处理它.但这对我们不起作用,因为浏览器不会设置Content-Type对于每个部分.默认类型是text/plain.

我确定有些库允许为每个部分设置类型,但只是为了向您展示如何手动完成,我将发布一个示例(从这里得到一些帮助.我将使用Angular ,但构建实体主体的笨拙工作将是简单的旧Javascript.

<!DOCTYPE html>
<html ng-app="multipartApp">
    <head>
        <script src="js/libs/angular.js/angular.js"></script>
        <script>
            angular.module("multipartApp", [])
            .controller("defaultCtrl", function($scope, $http) {

                $scope.sendData = function() {
                    var foo = JSON.stringify({foo: "FooFoo"});
                    var bar = JSON.stringify({bar: "BarBar"});

                    var boundary = Math.random().toString().substr(2);                    
                    var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;

                    $http({
                        url: "/api/foobar",
                        headers: { "Content-Type": header }, 
                        data: createRequest(foo, bar, boundary),
                        method: "POST"
                    }).then(function(response) {
                        $scope.result = response.data;
                    });
                };

                function createRequest(foo, bar, boundary) {
                    var multipart = "";
                    multipart += "--" + boundary
                        + "\r\nContent-Disposition: form-data; name=foo"
                        + "\r\nContent-type: application/json"
                        + "\r\n\r\n" + foo + "\r\n";        
                    multipart += "--" + boundary
                        + "\r\nContent-Disposition: form-data; name=bar"
                        + "\r\nContent-type: application/json"
                        + "\r\n\r\n" + bar + "\r\n";
                    multipart += "--" + boundary + "--\r\n";
                    return multipart;
                }
            });
        </script>
    </head>
    <body>
        <div ng-controller="defaultCtrl">
            <button ng-click="sendData()">Send</button>
            <p>{{result}}</p>
        </div>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

有趣的部分是createRequest功能.这是我们构建multipart的地方,将Content-Type每个部分设置为application/json,并将字符串化foobar对象连接到每个部分.如果您不熟悉多部分格式,请参阅此处以获取更多信息.另一个有趣的部分是标题.我们把它设置为multipart/form-data.

结果如下.在Angular中我只是使用结果在HTML中显示$scope.result = response.data.您看到的按钮就是发出请求.您还将在firebug中看到请求数据

在此输入图像描述

3.将它们包装在单个父对象中

其他人已经提到过,这个选项应该是自我解释的.


Ole*_*syn 8

下一种方法通常适用于这种情况:

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(TransferObject object){
//        object.getObejctOne()....
}
Run Code Online (Sandbox Code Playgroud)