Как проверить значения Spring MVC @PathVariable?

Для простого RESTful JSON api, реализованного в Spring MVC, могу ли я использовать Bean Validation (JSR-303) для проверки переменных пути, переданных в метод обработчика?

Например:

 @RequestMapping(value = "/number/{customerNumber}")
 @ResponseBody
 public ResponseObject searchByNumber(@PathVariable("customerNumber") String customerNumber) {
 ...
 }

Здесь мне нужно проверить длину переменной customerNumber с помощью проверки Bean. Возможно ли это с помощью Spring MVC v3.x.x? Если нет, то какой лучший подход для этого типа валидации?

Спасибо.

Ответ 1

Spring не поддерживает @javax.validation.Valid @PathVariable аннотированных параметров @PathVariable в методах-обработчиках. Был запрос на улучшение, но он все еще не решен.

Лучше всего просто выполнить пользовательскую проверку в теле метода-обработчика или использовать org.springframework.validation.annotation.Validated как предлагается в других ответах.

Ответ 2

Вы можете использовать следующее: используйте org.springframework.validation.annotation.Validated для действительных RequestParam или PathVariable.

 *
 * Variant of JSR-303 {@link javax.validation.Valid}, supporting the
 * specification of validation groups. Designed for convenient use with
 * Spring JSR-303 support but not JSR-303 specific.
 *

step.1 init ValidationConfig

@Configuration
public class ValidationConfig {
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        return processor;
    }
}

step.2 Добавить @Validated в класс обработчика контроллера, например:

@RequestMapping(value = "poo/foo")
@Validated
public class FooController {
...
}

step.3 Добавьте validators к вашему методу обработчика:

   @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
   public ResponseEntity<Foo> delete(
           @PathVariable("id") @Size(min = 1) @CustomerValidator int id) throws RestException {
        // do something
        return new ResponseEntity(HttpStatus.OK);
    }

окончательный шаг. Добавьте исключение в свой контекст:

@Component
public class BindExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (ex.getClass().equals(BindException.class)) {
            BindException exception = (BindException) ex;

            List<FieldError> fieldErrors = exception.getFieldErrors();
            return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
        }
    }
}

Ответ 3

Решение простое:

@GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(@PathVariable(value = "hash", required = false) String historyHash)
{
    // Accepted requests: either "/" or "/{40 character long hash}"
}

И да, PathVariables должны быть проверены, как и любой пользовательский ввод.

Ответ 4

@PathVariable не подлежит проверке, чтобы отправить обратно читаемое сообщение пользователю. Как правило, pathVariable никогда не должен быть недействительным. Если pathVariable недействителен, причиной может быть:

  • ошибка сгенерировала плохой url (например, href в jsp). Нет @Valid необходимо и сообщение не требуется, просто исправьте код;
  • "пользователь" манипулирует URL-адресом. Опять же, no @Valid не требуется, никакое содержательное сообщение пользователю не должно .

В обоих случаях просто оставляйте пузырь для исключения до тех пор, пока он не поймается   обычный Spring ExceptionHandlers, чтобы создать приятный   страницу ошибки или значимый ответ json, указывающий на ошибку. В   Чтобы получить этот результат, вы можете выполнить некоторую проверку с помощью настраиваемых редакторов.

Создайте класс CustomerNumber, возможно, как неизменный (реализация CharSequence не нужна, но позволяет использовать ее в основном так, как если бы она была String)

public class CustomerNumber implements CharSequence {

    private String customerNumber;

    public CustomerNumber(String customerNumber) {
        this.customerNumber = customerNumber;
    }

    @Override
    public String toString() {
        return customerNumber == null ? null : customerNumber.toString();
    }

    @Override
    public int length() {
        return customerNumber.length();
    }

    @Override
    public char charAt(int index) {
        return customerNumber.charAt(index);
    }

    @Override
    public CharSequence subSequence(int start, int end) {
        return customerNumber.subSequence(start, end);
    }

    @Override
    public boolean equals(Object obj) {
        return customerNumber.equals(obj);
    }

    @Override
    public int hashCode() {
        return customerNumber.hashCode();
    }
}

Создайте редактор, реализующий вашу логику проверки (в этом случае нет пробелов и фиксированной длины, как пример)

public class CustomerNumberEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {

        if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
            setValue(new CustomerNumber(text));
        } else {
            throw new IllegalArgumentException();
            // you could also subclass and throw IllegalArgumentException
            // in order to manage a more detailed error message
        }
    }

    @Override
    public String getAsText() {
        return ((CustomerNumber) this.getValue()).toString();
    }
}

Зарегистрируйте редактор в контроллере

@InitBinder
public void initBinder(WebDataBinder binder) {

    binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
    // ... other editors
}

Измените подпись вашего метода контроллера, принимающего CustomerNumber вместо String (независимо от вашего ResponseObject...)

@RequestMapping(value = "/number/{customerNumber}")
@ResponseBody
public ResponseObject searchByNumber(@PathVariable("customerNumber") CustomerNumber customerNumber) {
    ...
}

Ответ 5

Вместо использования @PathVariable вы можете воспользоваться возможностью Spring MVC для отображения переменных пути в bean-компонент:

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/{id}")
    public void get(@Valid GetDto dto) {
        // dto.getId() is the path variable
    }

}

И бин содержит действительные правила проверки:

@Data
public class GetDto {
     @Min(1) @Max(99)
     private long id;
}

Убедитесь, что ваши переменные пути ({id}) соответствуют полям бина (id);

Ответ 6

Переменная пути не может быть связана с любым bean в вашей системе. Что вы хотите комментировать аннотациями JSR-303? Чтобы проверить переменную пути, вы должны использовать этот подход Проблема проверки URL @PathVariable на spring 3 mvc