【问题标题】:Required @QueryParam in JAX-RS (and what to do in their absence)JAX-RS 中必需的 @QueryParam(以及在他们缺席的情况下该怎么做)
【发布时间】:2012-12-20 08:48:57
【问题描述】:

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

JAX-RS 中是否有可用于声明必需的强制 @QueryParam 参数的注释?如果不是,那么处理缺少此类参数的情况的“标准”方法是什么?

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

【问题讨论】:

  • 您可以添加@DefaultValue 注释,并在参数丢失时将其设置为适当的值。如果你不能有默认值并且参数真的很重要,也许你应该检查null的参数并返回400 Bad request状态码。

标签: java jax-rs


【解决方案1】:

好问题。不幸的是(或者幸运的是)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
}

【讨论】:

  • documentation for JAX-RS 1.0 表示它不会总是为空。它将是“List、Set 或 SortedSet 的空集合,其他对象类型的 null 以及基本类型的 Java 定义的默认值。”
  • String 不是原始类型,因此它是“其他对象类型为空”
  • 还建议不要使用 HttpURLConnection.HTTP_BAD_REQUEST,而是使用 javax.ws.rs.core.Response.Status.BAD_REQUEST 以保持与方法的预期参数保持一致。
  • 来自遥远未来的注意: 有一个 BadRequestException 可以被抛出,它或多或少地完成了上面代码的作用,但也允许你专门捕捉它以编程方式异常。
【解决方案2】:

您可以使用javax.validation 注释来强制参数是强制性的,方法是使用@javax.validation.constraints.NotNull 注释它们。请参阅an example for Jerseyone for 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> 实现来覆盖。

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

【讨论】:

  • 这种方法的一个问题是错误消息没有指定缺失参数的名称,例如“arg1 may not be null”。幸运的是 Bean Validation 规范引入了接口 javax.validation.ParameterNameProvider。对于 JAX-RS,我们可以使用注释 QueryParam 和 PathParam 来获取名称(因为反射不允许获取参数名称)。可以在此处找到示例:stackoverflow.com/q/22496527/998772
  • 是的,我经历了痛苦并问了question about it。这是可行的,只是要多写一点代码。
  • 我正在尝试做类似的事情,但即使我从 URL 中省略了这个查询参数,@NotNull 也没有检测到它。我在这里开了一个帖子link
  • 对于任何在 2018 年阅读的人,@NotNull 注释现在会导致 javax.validation.ConstraintViolationException,而不是 ValidationException,因此如果您想自己处理它,则需要不同的 ExceptionMapper。请参阅此问题的已接受答案stackoverflow.com/questions/18015630/…
【解决方案3】:

我遇到了同样的问题并决定不希望在我的 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.
}

... 和以下 JAX-RS ContainerRequestFilter 来强制执行它:

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,就像在您的JAX-RS 库中注册您的其他@Provider 类一样。也许 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 库中。

【讨论】:

  • 这是否提供了 @javax.validation.constraints.NotNull 不会做的事情?
  • @MichaelHaefele 它保留了参数名称,这对于显示有意义的错误消息很有用。如果使用NotNull 注解,参数名称会丢失,这很不幸。这就是让我自己写注释的问题。但也请参阅stackoverflow.com/questions/13968261/…。自从我写了这段代码之后,事情可能已经改变了。
  • 对我来说,这个解决方案的一个关键特性是对你想在异常中显示的内容进行细粒度控制。
【解决方案4】:

可能最简单的方法是使用javax.annotation 中的@Nonnull 来实现此目的。使用起来超级简单,只需在@QueryParam 之前添加即可,如下所示。

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


例子:

import javax.annotation.Nonnull;
...

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

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

{"timestamp":1536152114437,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"@Nonnull 参数 'paramName 的参数' 的 com/example/YourClass.get 不能为 null","path":"/path/to/your-path"}

【讨论】:

    猜你喜欢
    • 2016-12-03
    • 1970-01-01
    • 1970-01-01
    • 2014-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多