Проверка JavaBeans (Bean Validation) — это новая модель проверки, доступная как часть платформы Java EE 6. Модель Bean Validation поддерживается ограничениями в форме аннотаций, размещенных в поле, методе или классе компонента JavaBeans, такого как управляемый компонент.
Несколько встроенных ограничений доступны в пакете javax.validation.constraints
. Учебник по Java EE 6 перечисляет все встроенные ограничения.
Ограничения в проверке bean-компонентов выражаются посредством аннотаций Java:
1
2
3
4
5
6
|
public class Person { @NotNull @Size (min = 2 , max = 50 ) private String name; // ... } |
Bean Validation и RESTful веб-сервисы
JAX-RS 1.0 предоставляет отличную поддержку для извлечения значений запроса и связывания их с полями, свойствами и параметрами Java с помощью аннотаций, таких как @HeaderParam
, @QueryParam
и т. Д. Он также поддерживает связывание тел объектов запроса с объектами Java с помощью аннотированных параметров ( т.е. параметры, которые не аннотированы ни одной из аннотаций JAX-RS). В настоящее время любая дополнительная проверка этих значений в классе ресурсов должна выполняться программно.
В следующем выпуске, JAX-RS 2.0, содержится предложение, позволяющее объединять аннотации проверки с аннотациями JAX-RS. Например, учитывая аннотацию проверки @Pattern
, в следующем примере показано, как можно проверять параметры формы.
1
2
3
4
5
6
7
8
|
@GET @Path ( '{id}' ) public Person getPerson( @PathParam ( 'id' ) @Pattern (regexp = '[0-9]+' , message = 'The id must be a valid number' ) String id) { return persons.get(id); } |
Однако на данный момент единственным решением является использование собственной реализации. Далее представлено решение на основе инфраструктуры RESTEasy от JBoss, которое соответствует спецификации JAX-RS и добавляет интерфейс проверки RESTful через аннотацию @ValidateRequest
.
Экспортированный интерфейс позволяет нам создавать нашу собственную реализацию. Тем не менее, уже есть один широко используемый и для которого RESTEasy также обеспечивает бесшовную интеграцию. Эта реализация — Hibernate Validator . Этот провайдер может быть добавлен в проект через следующие зависимости Maven:
01
02
03
04
05
06
07
08
09
10
11
|
< dependency > < groupId >org.jboss.resteasy</ groupId > < artifactId >resteasy-jaxrs</ artifactId > < version >2.3.2.Final</ version > < scope >provided</ scope > </ dependency > < dependency > < groupId >org.jboss.resteasy</ groupId > < artifactId >resteasy-hibernatevalidator-provider</ artifactId > < version >2.3.2.Final</ version > </ dependency > |
Примечание: без объявления @ValidateRequest
на уровне класса или метода, проверка не произойдет, несмотря на применение аннотаций ограничений к методам, например, в примере выше.
1
2
3
4
5
6
7
8
9
|
@GET @Path ( '{id}' ) @ValidateRequest public Person getPerson( @PathParam ( 'id' ) @Pattern (regexp = '[0-9]+' , message = 'The id must be a valid number' ) String id) { return persons.get(id); } |
После применения аннотации id
параметра будет автоматически подтвержден при выполнении запроса.
Конечно, вы можете проверять целые объекты вместо отдельных полей, используя аннотацию @Valid
. Например, у нас может быть один метод, который принимает объект Person
и проверяет его.
1
2
3
4
5
6
|
@POST @Path ( '/validate' ) @ValidateRequest public Response validate( @Valid Person person) { // ... } |
Замечания:
По умолчанию при сбое проверки контейнером выдается исключение, и клиенту возвращается состояние HTTP 500. Это поведение по умолчанию может / должно быть переопределено, что позволяет нам настраивать Ответ, который возвращается клиенту через сопоставители исключений.
интернационализация
До сих пор мы использовали стандартные или жестко закодированные сообщения об ошибках, но это и плохая практика, и совсем не гибкая. I18n является частью спецификации Bean Validation и позволяет нам задавать пользовательские сообщения об ошибках, используя файл свойств ресурса. Имя файла ресурса по умолчанию — ValidationMessages.properties
и должно включать пары свойств / значений, таких как:
1
2
|
person. id .pattern=The person id must be a valid number person.name.size=The person name must be between {min} and {max} chars long |
Замечания:
{min}
, {max}
относятся к свойствам ограничения, с которым будет связано сообщение.
Эти определенные сообщения могут затем вводиться в ограничения проверки как:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
@POST @Path ( 'create' ) @Consumes (MediaType.APPLICATION_FORM_URLENCODED) public Response createPerson( @FormParam ( 'id' ) @Pattern (regexp = '[0-9]+' , message = '{person.id.pattern}' ) String id, @FormParam ( 'name' ) @Size (min = 2 , max = 50 , message = '{person.name.size}' ) String name) { Person person = new Person(); person.setId(Integer.valueOf(id)); person.setName(name); persons.put(Integer.valueOf(id), person); return Response.status(Response.Status.CREATED).entity(person).build(); } |
Чтобы обеспечить переводы на другие языки, необходимо создать новый файл ValidationMessages_XX.properties
с переведенными сообщениями, где XX
— код предоставляемого языка.
К сожалению, провайдер Hibernate Validator не поддерживает i18n на основе определенного HTTP-запроса. Он не учитывает HTTP-заголовок Accept-Language
и всегда использует Locale
по умолчанию, предоставленную Locale.getDefault()
. Чтобы иметь возможность изменять Accept-Language
HTTP-заголовка Accept-Language
(например, изменение языка в параметрах браузера), должна быть предусмотрена пользовательская реализация.
Пользовательский поставщик валидатора
Приведенный ниже код предназначен для решения этой проблемы и был протестирован с JBoss AS 7.1 .
Первое, что нужно сделать, — это удалить resteasy-hibernatevalidator-provider
Maven resteasy-hibernatevalidator-provider
, поскольку мы предоставляем нашего собственного поставщика и добавить зависимость Hibernate Validator:
1
2
3
4
5
|
< dependency > < groupId >org.hibernate</ groupId > < artifactId >hibernate-validator</ artifactId > < version >4.2.0.Final</ version > </ dependency > |
Затем создайте пользовательский интерполятор сообщений, чтобы настроить используемый по умолчанию Locale
стандарт.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
public class LocaleAwareMessageInterpolator extends ResourceBundleMessageInterpolator { private Locale defaultLocale = Locale.getDefault(); public void setDefaultLocale(Locale defaultLocale) { this .defaultLocale = defaultLocale; } @Override public String interpolate( final String messageTemplate, final Context context) { return interpolate(messageTemplate, context, defaultLocale); } @Override public String interpolate( final String messageTemplate, final Context context, final Locale locale) { return super .interpolate(messageTemplate, context, locale); } } |
Следующим шагом является предоставление ValidatorAdapter
. Этот интерфейс был введен для отделения RESTEasy от реального API валидации.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
public class RESTValidatorAdapter implements ValidatorAdapter { private final Validator validator; private final MethodValidator methodValidator; private final LocaleAwareMessageInterpolator interpolator = new LocaleAwareMessageInterpolator(); public RESTValidatorAdapter() { Configuration<?> configuration = Validation.byDefaultProvider() .configure(); this .validator = configuration.messageInterpolator(interpolator) .buildValidatorFactory().getValidator(); this .methodValidator = validator.unwrap(MethodValidator. class ); } @Override public void applyValidation(Object resource, Method invokedMethod, Object[] args) { // For the i8n to work, the first parameter of the method being validated must be a HttpHeaders if ((args != null ) && (args[ 0 ] instanceof HttpHeaders)) { HttpHeaders headers = (HttpHeaders) args[ 0 ]; List<Locale> acceptedLanguages = headers.getAcceptableLanguages(); if ((acceptedLanguages != null ) && (!acceptedLanguages.isEmpty())) { interpolator.setDefaultLocale(acceptedLanguages.get( 0 )); } } ValidateRequest resourceValidateRequest = FindAnnotation .findAnnotation(invokedMethod.getDeclaringClass() .getAnnotations(), ValidateRequest. class ); if (resourceValidateRequest != null ) { Set<ConstraintViolation<?>> constraintViolations = new HashSet<ConstraintViolation<?>>( validator.validate(resource, resourceValidateRequest.groups())); if (constraintViolations.size() > 0 ) { throw new ConstraintViolationException(constraintViolations); } } ValidateRequest methodValidateRequest = FindAnnotation.findAnnotation( invokedMethod.getAnnotations(), ValidateRequest. class ); DoNotValidateRequest doNotValidateRequest = FindAnnotation .findAnnotation(invokedMethod.getAnnotations(), DoNotValidateRequest. class ); if ((resourceValidateRequest != null || methodValidateRequest != null ) && doNotValidateRequest == null ) { Set<Class<?>> set = new HashSet<Class<?>>(); if (resourceValidateRequest != null ) { for (Class<?> group : resourceValidateRequest.groups()) { set.add(group); } } if (methodValidateRequest != null ) { for (Class<?> group : methodValidateRequest.groups()) { set.add(group); } } Set<MethodConstraintViolation<?>> constraintViolations = new HashSet<MethodConstraintViolation<?>>( methodValidator.validateAllParameters(resource, invokedMethod, args, set.toArray( new Class<?>[set.size()]))); if (constraintViolations.size() > 0 ) { throw new MethodConstraintViolationException( constraintViolations); } } } } |
Предупреждает:
@HttpHeaders
должен быть введен как первый параметр методов, которые будут проверены:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
@POST @Path ( 'create' ) @Consumes (MediaType.APPLICATION_FORM_URLENCODED) public Response createPerson( @Context HttpHeaders headers, @FormParam ( 'id' ) @Pattern (regexp = '[0-9]+' , message = '{person.id.pattern}' ) String id, @FormParam ( 'name' ) @Size (min = 2 , max = 50 , message = '{person.name.size}' ) String name) { Person person = new Person(); person.setId(Integer.valueOf(id)); person.setName(name); persons.put(id, person); return Response.status(Response.Status.CREATED).entity(person).build(); } |
Наконец, создайте провайдера, который выберет вышеупомянутые классы, которые будут использоваться для проверки ограничений Bean Validation:
01
02
03
04
05
06
07
08
09
10
11
|
@Provider public class RESTValidatorContextResolver implements ContextResolver<ValidatorAdapter> { private static final RESTValidatorAdapter adapter = new RESTValidatorAdapter(); @Override public ValidatorAdapter getContext(Class<?> type) { return adapter; } } |
Отображение исключений
API Bean Validation сообщает об ошибках, используя исключения типа javax.validation.ValidationException
или любого из его подклассов. Приложения могут предоставлять настраиваемые поставщики сопоставления исключений для любого исключения. Реализация JAX-RS ДОЛЖНА всегда использовать провайдера, универсальный тип которого является ближайшим суперклассом исключения, причем определяемые приложением провайдеры имеют приоритет над встроенными провайдерами.
Отображение исключений может выглядеть так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
@Provider public class ValidationExceptionMapper implements ExceptionMapper<MethodConstraintViolationException> { @Override public Response toResponse(MethodConstraintViolationException ex) { Map<String, String> errors = new HashMap<String, String>(); for (MethodConstraintViolation<?> methodConstraintViolation : ex .getConstraintViolations()) { errors.put(methodConstraintViolation.getParameterName(), methodConstraintViolation.getMessage()); } return Response.status(Status.PRECONDITION_FAILED).entity(errors) .build(); } } |
В приведенном выше примере показана реализация ExceptionMapper
который отображает исключения типа MethodConstraintViolationException
. Это исключение выдается реализацией Hibernate Validator, когда проверка одного или нескольких параметров метода, аннотированного @ValidateRequest
завершается неудачно. Это гарантирует, что клиент получает отформатированный ответ, а не только исключение, распространяемое из ресурса.
Исходный код
Исходный код, используемый для этого поста, доступен на GitHub .
Предупреждает:
переименуйте файл свойств ресурса, чтобы иметь файл ValidationMessages.properties
(т. е. без суффикса) для сопоставления с Locale
как возвращено Locale.getDefault()
.
Похожие сообщения
- Кодировка Java и UTF-8
- Запуск Drools 5.4.0 Final в качестве модуля JBoss AS 7
- Сравнение описаний устройств
- Тестирование Java EE 6, часть II — Введение в Arquillian и ShrinkWrap
- Тестирование Java EE 6, часть I — встраиваемый API EJB 3.1
- Предыдущая запись: Запуск Drools 5.4.0 Final в виде модуля JBoss AS 7
Ссылка: интеграция проверки бинов с JAX-RS в Java EE 6 от нашего партнера JCG Сэмюэля Сантоса в блоге Samaxes .