Потрібний @QueryParam у JAX-RS (і що робити за їх відсутності)


75

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

Чи доступна анотація для оголошення необхідних, обов’язкових параметрів @QueryParam у JAX-RS ? А якщо ні, то який «стандартний» спосіб вирішення ситуацій, коли такі параметри відсутні?

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


4
Ви можете додати @DefaultValueанотацію та встановити відповідне значення параметра, коли він відсутній. Якщо у вас не може бути значення за замовчуванням, а параметр справді важливий, можливо, вам слід перевірити параметр nullі повернути 400 Bad requestкод стану.
toniedzwiedz

Відповіді:


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 сказано, що вона не завжди буде нульовою. Це буде "порожня колекція для List, Set або SortedSet, нульова для інших типів об'єктів і визначена Java за замовчуванням для примітивних типів".
hotshot309

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. Див. Приклад для Джерсі та один для 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 може бути не нульовим". На щастя, специфікація перевірки Bean представила інтерфейс javax.validation.ParameterNameProvider. Для JAX-RS ми можемо використовувати анотації QueryParam та PathParam для отримання імен (оскільки відображення не дозволяє отримувати імена параметрів). В якості прикладу можна знайти тут: stackoverflow.com/q/22496527/998772
pavel_kazlou

Так, я пережив цей біль і запитав про це . Це можливо, лише трохи більше коду для написання.
Джованні Ботта,

1
Я намагаюся зробити подібне, але @NotNull не виявляє цього, навіть якщо я пропускаю цей параметр запиту з URL-адреси. Я розпочав тему тут посилання
Bruso

1
Для тих, хто читає в 2018 році, анотація @NotNull тепер призводить до javax.validation.ConstraintViolationException, а не ValidationException, тому вам знадобиться інший ExceptionMapper, якщо ви хочете обробити це самостійно. Дивіться прийняту відповідь на це питання stackoverflow.com/questions/18015630/…
Мартін Чарльзворт

17

Я зіткнувся з тією ж проблемою і вирішив, що не хочу, щоб нульові перевірки шаблону gazillion були розкидані по моєму коду 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так само, як і інші ваші @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

2
@MichaelHaefele Він зберігає ім'я параметра, що корисно для відображення значущого повідомлення про помилку. Ім'я параметра втрачається, якщо ви використовуєте NotNullанотацію, що дуже шкода. Саме це питання змусило мене написати мені власну анотацію. Але також див. Stackoverflow.com/questions/13968261/… . З моменту написання цього коду все могло змінитися.
Zero3

Однією з ключових особливостей цього рішення для мене є чіткий контроль над тим, що ви хочете показати у винятку чи ні.
Олександр

2

Можливо , найпростіший спосіб полягає у використанні @Nonnullвід javax.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) {
        ... 
    }

Повідомлення про помилку за замовчуванням, яке повертається абоненту, виглядає так:

{"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 "}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.