Подтвердить что ты не робот

Обязательный @QueryParam в JAX-RS (и что делать в их отсутствие)

Я развертываю компонент веб-сервисов на JBoss Application Server 7, используя реализацию RESTEasy JAX-RS.

Имеется ли аннотация для объявления обязательных параметров @QueryParam в JAX-RS? И, если нет, то какой "стандартный" способ справиться с ситуациями, когда такие параметры отсутствуют?

Мои методы веб-службы (ресурса) возвращают JSON-строковые результаты при правильном вызове со всеми обязательными аргументами, но я не уверен, что лучший способ указать вызывающему, что требуемый параметр отсутствует.

4b9b3361

Ответ 1

Хороший вопрос. К сожалению (или, может быть, к счастью) в JAX-RS нет механизма, чтобы сделать какие-либо параметры обязательными. Если параметр не указан, значение будет NULL, и ваш ресурс должен обработать его соответствующим образом. Я бы рекомендовал использовать WebApplicationException для информирования пользователей:
@GET
@Path("/some-path")
public String read(@QueryParam("name") String name) {
  if (name == null) {
    throw new WebApplicationException(
      Response.status(HttpURLConnection.HTTP_BAD_REQUEST)
        .entity("name parameter is mandatory")
        .build()
    );
  }
  // continue with a normal flow
}

Ответ 2

Вы можете использовать аннотации javax.validation для обеспечения того, чтобы параметры были обязательными, аннотируя их с помощью @javax.validation.constraints.NotNull. Смотрите пример для Джерси и один для 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>.

Это обеспечивает централизованный способ перевода обязательного параметра в ответы об ошибках и отсутствие дублирования кода.

Ответ 3

Я столкнулся с одной и той же проблемой и решил, что я не хочу, чтобы в моем коде REST были разбросаны галиллионные шаблонные нулевые проверки, поэтому я решил это сделать:

  • Создайте аннотацию, которая вызывает исключение, если требуемый параметр не указан.
  • Обрабатывать исключение с броском точно так же, как я обрабатываю все другие исключения, брошенные в мой код 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 так же, как вы зарегистрировали бы свои другие классы @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.

Ответ 4

Вероятно, самый простой способ - использовать @Nonnull из javax.annotation для достижения этого. Он очень прост в использовании, поскольку все, что вам нужно сделать, это добавить его перед @QueryParam как показано ниже.

Однако имейте в виду, что это вызовет исключение IllegalArgumentException когда параметр равен null, поэтому ответ, который вы отправляете обратно, будет тем, что вы делаете для исключения. Если вы не перехватите его, это будет 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 "of com/example/YourClass.get не должно быть null "," path ":"/path/to/your-path "}