如何将复杂对象作为参数传递给RESTful服务?

43
我成功地创建了一个快速测试,建立了一个“REST-like”服务,它返回一个序列化为JSON的对象,这很容易且快速(基于 这篇文章)。
但是,虽然将对象序列化为JSON很轻松,但我还没有看到任何处理非基本类型输入参数的示例。如何传递复杂对象作为参数?我正在使用Apache CXF,但也欢迎使用其他框架如Jackson的示例 :)
客户端可能类似于构建一个JavaScript对象,将其传递到JSON.stringify(complexObj),并将该字符串作为其中一个参数传递。
服务可能看起来像这样:
@Service("myService")
class RestService {
    @GET
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(@QueryParam("foo") double foo, @QueryParam("bar") double bar,
        @QueryParam("object") MyComplex object) throws WebServiceException {
    ...
    }
}

将序列化的对象作为参数发送,可能很快就会触及Internet Explorer所强制实施的2KB URL限制。你是否建议在这些情况下使用POST,并且我需要在函数定义中做出多少更改?

3个回答

32

经过一番搜索,我很快就发现基本上有两个选择:

选项1

您可以传递一个包含所有其他参数的“包装对象”到服务中。您可能需要使用JAXB注释(如@XmlRootElement)对此包装类进行注释,以便它能够与基于Jettison的提供程序一起使用,但如果您使用的是Jackson,则不需要这样做。只需将内容类型设置为正确的类型,正确的消息正文阅读器就会被调用。 当然,这仅适用于POST类型的服务(据我所知)。

示例

这只是将原始问题中提到的服务转换为使用包装对象的示例。

@Service("myService")
class RestService {

    @POST
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(

          /** 
          * Using "" will inject all form params directly into a ParamsWrapper 
          * @see http://cxf.apache.org/docs/jax-rs-basics.html
          */
          @FormParam("") FooBarParamsWrapper wrapper

        ) throws WebServiceException {
            doSomething(wrapper.foo);
    }
}

class ParamsWrapper {
  double foo, bar;
  MyComplexObject object;
}

选项2

您可以提供一些特殊的字符串格式,将对象打包到其中,然后在类中实现一个构造函数,需要接受一个字符串,或者一个静态的valueOf(String s)方法或一个静态的fromString(String s)方法,以从该字符串创建一个对象。或者创建一个执行完全相同操作的ParameterHandler。

据我所知,只有第二种方法才能让您使用JSONP从浏览器调用服务(因为JSONP是限制为GET的技巧)。 我选择这条路线是为了能够在URI中传递复杂对象的数组。

以下是如何使用此方法的示例域类和服务:

示例

@GET
@Path("myService")
public void myService(@QueryParam("a") MyClass [] myVals) {
    //do something
}

class MyClass {
    public int foo;
    public int bar;

   /** Deserializes an Object of class MyClass from its JSON representation */
   public static MyClass fromString(String jsonRepresentation) {
           ObjectMapper mapper = new ObjectMapper(); //Jackson's JSON marshaller
           MyClass o= null;
           try {
                   o = mapper.readValue(jsonRepresentation, MyClass.class );
           } catch (IOException e) {
                    throw new WebApplicationException()
           }
           return o;
   }
}

一个URI http://my-server.com/myService?a={"foo":1, "bar":2}&a={"foo":100, "bar":200} 在这种情况下将被反序列化为由两个MyClass对象组成的数组。

2019年评论: 看到这个答案在2019年仍然有一些点击,我觉得我应该发表评论。事后来看,我不会推荐选项2,因为通过这些步骤只是为了能够进行GET调用而增加了复杂性,这可能并不值得。如果您的服务接受如此复杂的输入,由于输入的排列方式过多,您可能无法利用客户端缓存。我会配置服务器上的适当跨源共享(CORS)头并POST输入。然后专注于在服务器上缓存任何可缓存的内容。


你能否说明一下客户端的外观是什么样子的?我们需要在客户端自己进行序列化吗? - chan
在我们的情况下,客户端是JavaScript应用程序。因此,他们只需创建对象,例如var myObject = { foo: 1, bar: 2 },然后使用JSON.stringify(myObject)将其转换为字符串。 - oligofren
对于选项2,当从字符串解析MyClass对象时出现问题,如何将WebApplicationException发送回客户端?“throw”不会将异常发送到客户端。 (请注意,这是机器翻译,可能不够准确。) - yo_haha
@yo_haha,不确定你所说的“发送异常”是什么意思。我不再使用Java进行开发,但我相当确定抛出异常会导致HTTP 500服务器异常-无论是否有堆栈跟踪。至少会有一个日志转储。如果您想显式控制响应对象,则可以使用jax.rs中的Response对象来实现,但这需要进行一些重构。 - oligofren
1
@yo_haha,你提出的问题实际上是一个独立的问题。最好将其作为一个新问题发布。这样会引起更多的关注。 - oligofren
显示剩余2条评论

7
接受的答案缺少@BeanParam。请参见https://docs.jboss.org/resteasy/docs/3.0-rc-1/javadocs/javax/ws/rs/BeanParam.html了解更多详细信息。它允许您在包装对象中定义查询参数。例如:
public class TestPOJO {

    @QueryParam("someQueryParam")
    private boolean someQueryParam;

    public boolean isSomeQueryParam() {
        return someQueryParam;
    }

    public boolean setSomeQueryParam(boolean value) {
        this.someQueryParam = value;
    }
}

... // inside the Resource class
@GET
@Path("test")
public Response getTest(@BeanParam TestPOJO testPOJO) {
    ...
}

1
在我写代码的时候那并不是必需的。现在七年之后,我无法确定我的代码是否使用较新的JAX-RS迭代仍然有效。 - oligofren
3
它能够发挥作用,因此即使答案不在当时,对于其他人现在仍然有用。 - pedrofb

2

最好和最简单的解决方案是将您的对象作为JSON字符串发送,并在服务器端实现一个方法,该方法将解码该JSON并根据您的需求映射到指定的对象。是的,最好使用POST。


2
这已经被接受的答案涵盖了,而问题明确涉及jax-rs,这是你未能解决的。 - oligofren
希望不是这样。谢谢。 - Koustuv Ganguly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接