JAX-RS中必需的@QueryParam(以及缺少它们时的操作)


75

我使用RESTEasy JAX-RS实现将Web服务组件部署到JBoss Application Server 7

JAX-RS中是否可以使用注释来声明必需的,必需的@QueryParam参数?而且,如果没有,处理缺少此类参数的情况的“标准”方法是什么?

当使用所有必需参数正确调用时,我的Web服务(资源)方法返回JSON字符串化的结果,但是我不确定向调用方指示缺少必需参数的最佳方法是什么。


4
您可以添加@DefaultValue注释,并在缺少参数时将参数设置为适当的值。如果您不能使用默认值并且该参数确实很重要,那么也许应该检查参数null并返回400 Bad request状态码。
toniedzwiedz

Answers:


72

好问题。不幸的是(或者幸运的是)JAX-RS中没有任何机制可以强制任何参数。如果未提供参数,则其值为NULL,您的资源应进行相应处理。我建议使用WebApplicationException来通知您的用户:

@GET
@Path("/some-path")
public String read(@QueryParam("name") String name) {
  if (name == null) {
    throw new WebApplicationException(
      Response.status(Response.Status.BAD_REQUEST)
        .entity("name parameter is mandatory")
        .build()
    );
  }
  // continue with a normal flow
}

14
JAX-RS 1.0文档说它不会总是为null。它将是“ List,Set或SortedSet的空集合,其他对象类型为null,原始类型的Java定义默认值”。
2013年

3
String不是原始类型,因此“对于其他对象类型为空”
yegor256

12
还建议不要使用HttpURLConnection.HTTP_BAD_REQUEST,而应使用javax.ws.rs.core.Response.Status.BAD_REQUEST来与方法的预期参数保持一致。
cmonkey

7
从遥远的将来注意:有一个BadRequestException可以抛出的对象,它或多或少地执行上面的代码,但是还允许您以编程方式专门捕获此异常。
errantlinguist

63

您可以使用javax.validation注释,通过使用注释来强制参数是必需的@javax.validation.constraints.NotNull。请参阅Jersey的示例RESTeasy的示例

因此,您的方法将变成:

@GET
@Path("/some-path")
public String read(@NotNull @QueryParam("name") String name) {
  String something = 
  // implementation
  return something;
}

注意,该异常随后由JAX-RS提供程序转换为某些错误代码。通常可以通过注册自己的实现来覆盖它javax.ws.rs.ext.ExceptionMapper<javax.validation.ValidationException>

这提供了将强制性参数转换为错误响应的集中方式,并且无需重复代码。


12
这种方法的一个问题是错误消息未指定缺失参数的名称,例如“ arg1可能不为null”。幸运的是,Bean验证规范引入了接口javax.validation.ParameterNameProvider。对于JAX-RS,我们可以使用注释QueryParam和PathParam来获取名称(因为反射不允许获取参数名称)。可以在此处找到示例:stackoverflow.com/q/22496527/998772
pavel_kazlou 2014年

是的,我经历了那种痛苦,问了一个问题。这是可行的,只需编写更多代码。
Giovanni Botta 2014年

1
我正在尝试做类似的事情,但是即使我从URL中省略了此查询参数,@ NotNull也没有检测到它。我已经开始在这里链接
话题

1
对于在2018年阅读的任何人,@NotNull注释现在会导致javax.validation.ConstraintViolationException而不是ValidationException,因此如果您想自己处理它,则需要一个不同的ExceptionMapper。请参阅接受这个问题的答案 stackoverflow.com/questions/18015630/...
马丁查尔斯沃思

17

我遇到了同样的问题,并决定我不希望在我的REST代码中散布成千上万的样板空检查,所以这就是我决定要做的:

  1. 创建一个注释,当未指定必需参数时,该注释将引发异常。
  2. 以与处理我的REST代码中引发的所有其他异常相同的方式处理引发的异常。

对于1),我实现了以下注释:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Required
{
    // This is just a marker annotation, so nothing in here.
}

...以及以下ContainerRequestFilter用于强制实施的JAX-RS :

import java.lang.reflect.Parameter;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

@Provider
public class RequiredParameterFilter implements ContainerRequestFilter
{
    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext)
    {
        // Loop through each parameter
        for (Parameter parameter : resourceInfo.getResourceMethod().getParameters())
        {
            // Check is this parameter is a query parameter
            QueryParam queryAnnotation = parameter.getAnnotation(QueryParam.class);

            // ... and whether it is a required one
            if (queryAnnotation != null && parameter.isAnnotationPresent(Required.class))
            {
                // ... and whether it was not specified
                if (!requestContext.getUriInfo().getQueryParameters().containsKey(queryAnnotation.value()))
                {
                    // We pass the query variable name to the constructor so that the exception can generate a meaningful error message
                    throw new YourCustomRuntimeException(queryAnnotation.value());
                }
            }
        }
    }
}

您需要以ContainerRequestFilter与将其他@Provider类注册到JAX-RS库相同的方式注册。也许RESTEasy会自动为您完成。

对于2),我使用通用JAX-RS处理所有运行时异常ExceptionMapper

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class MyExceptionMapper implements ExceptionMapper<RuntimeException>
{
    @Override
    public Response toResponse(RuntimeException ex)
    {
        // In this example, we just return the .toString() of the exception. 
        // You might want to wrap this in a JSON structure if this is a JSON API, for example.
        return Response
            .status(Response.Status.BAD_REQUEST)
            .entity(ex.toString())
            .build();
    }
}

和以前一样,请记住在您的JAX-RS库中注册该类。


2
这是否提供了@ javax.validation.constraints.NotNull不会提供的功能?
Michael Haefele'Mar

2
@MichaelHaefele保留参数名称,这对于显示有意义的错误消息很有用。如果使用NotNull注释,则参数名称会丢失,这是不幸的。那就是让我写自己的注释的问题。但也请参阅stackoverflow.com/questions/13968261/…。自从我编写这段代码以来,事情可能已经改变。
Zero3 '17

对我而言,此解决方案的主要功能之一是对要显示的内容(无论是否显示异常)进行精细控制。
亚历山大

2

可能最简单的方法是使用@Nonnullfromjavax.annotation实现此目的。它非常简单易用,因为您需要做的就是先添加它@QueryParam,如下所示。

但是,请记住,IllegalArgumentException当参数为null时,这将引发,因此您发回的响应将是您为异常所做的任何事情。如果您不截取,500 Server Error即使发送回正确的内容是,也将是a 400 Bad Request。您可以拦截IllegalArgumentException并处理它以返回正确的响应。


例:

import javax.annotation.Nonnull;
...

    @GET
    @Path("/your-path")
    public Response get(@Nonnull @QueryParam("paramName") String paramName) {
        ... 
    }

返回给调用方的默认错误消息如下所示:

{“时间戳”:1536152114437,“状态”:500,“错误”:“内部服务器错误”,“异常”:“ java.lang.IllegalArgumentException”,“消息”:“ com的@Nonnull参数'paramName'的参数/example/YourClass.get不能为null“,” path“:” / path / to / your-path“}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.