jax-rs jax-ws
Bean验证简介
JavaBeans验证(Bean验证)是Java EE 6平台的一部分提供的新验证模型。 约束通过以JavaBeans组件(例如托管Bean)的字段,方法或类上的注释形式的约束来支持Bean验证模型。
javax.validation.constraints
包中提供了几个内置约束。 Java EE 6教程列出了所有内置约束。
Bean验证中的约束通过Java注释表示:
public class Person {
@NotNull
@Size(min = 2, max = 50)
private String name;
// ...
}
Bean验证和RESTful Web服务
JAX-RS 1.0为提取请求值并将其绑定到Java字段,属性和参数(使用@HeaderParam
, @QueryParam
等注释)提供了强大的支持。它还支持通过非注释参数将请求实体主体绑定到Java对象中(也就是说,未使用任何JAX-RS注释进行注释的参数)。 当前,必须以编程方式对资源类中的这些值进行任何其他验证。
下一个发行版JAX-RS 2.0包含一项建议,以使验证批注可以与JAX-RS批注结合使用。 例如,给定验证批注@Pattern
,以下示例显示如何验证表单参数。
@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);
}
但是,目前,唯一的解决方案是使用专有实现。 接下来介绍的是基于JBoss的RESTEasy框架的解决方案,该解决方案符合JAX-RS规范,并通过注释@ValidateRequest
添加了RESTful验证接口。
导出的接口允许我们创建自己的实现。 但是,已经有一种广泛使用的方法,RESTEasy还向其提供了无缝集成。 这个实现是Hibernate Validator 。
可以通过以下Maven依赖项将此提供程序添加到项目中:
<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
情况下, @ValidateRequest
在方法上应用了约束注释,也不会进行验证,例如,上面的示例。
@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
对象并对其进行验证的方法。
@POST
@Path("/validate")
@ValidateRequest
public Response validate(@Valid Person person) {
// ...
}
注意:
默认情况下,当验证失败时,容器将引发异常,并将HTTP 500状态返回给客户端。 可以/应该重写此默认行为,使我们能够自定义通过异常映射器返回给客户端的Response
。
国际化
到目前为止,我们一直在使用默认的或硬编码的错误消息,但这既是一种不好的做法,又一点也不灵活。 I18N是Bean验证规范的一部分,它使我们能够使用资源属性文件来指定自定义错误消息。 默认资源文件名称为ValidationMessages.properties
并且必须包含属性/值对,例如:
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}
是指与消息关联的约束的属性。
然后可以将这些已定义的消息注入验证约束中,如下所示:
@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提供程序不基于特定的HTTP请求支持I18N。 它不考虑Accept-Language
HTTP标头,并且始终使用Locale.getDefault()
提供的默认Locale
。 为了能够使用Accept-Language
HTTP标头更改Locale
,必须提供一个自定义实现。
定制验证器提供者
以下代码旨在解决此问题,并已通过JBoss AS 7.1进行了测试。
首先要做的是删除Maven resteasy-hibernatevalidator-provider
依赖性,因为我们提供了自己的提供程序,并添加了Hibernate Validator依赖性:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
接下来,创建一个自定义消息插值器以调整使用的默认Locale
。
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分离。
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
需要作为要验证的方法的第一个参数注入:
@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验证约束的类的提供程序:
@Provider
public class RESTValidatorContextResolver implements
ContextResolver<ValidatorAdapter> {
private static final RESTValidatorAdapter adapter = new RESTValidatorAdapter();
@Override
public ValidatorAdapter getContext(Class<?> type) {
return adapter;
}
}
映射异常
Bean验证API使用类型为javax.validation.ValidationException
或其任何子类的异常报告错误情况。 应用程序可以为任何异常提供自定义异常映射提供程序。 JAX-RS实现必须始终使用其泛型类型是异常的最接近超类的提供程序,其中应用程序定义的提供程序优先于内置提供程序。
异常映射器可能看起来像:
@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
类型的MethodConstraintViolationException
。 当用@ValidateRequest
注释的方法的一个或多个参数的验证失败时,Hibernate Validator实现将引发此异常。 这样可以确保客户端收到格式化的响应,而不仅仅是从资源传播的异常。
源代码
这篇文章使用的源代码可以在GitHub上找到 。
警告:确保更改资源属性文件名,以使文件ValidationMessages.properties
(即,没有任何后缀)映射到Locale.getDefault()
返回的Locale
。
作者:Samuel Santos是Java和开放源代码传播者,也是PT.JUG(葡萄牙Java用户组)的JUG负责人。 他是葡萄牙科英布拉市Present Technologies的技术主管,负责刺激创新,知识共享,教练和技术选择活动。 塞缪尔(Samuel)是博客samaxes.com的作者,并以@samaxes鸣叫。
参考: Java Advent Calendar博客上我们JCG合作伙伴 Samuel Santos的JAX-RS Bean验证错误消息国际化 。
翻译自: https://www.javacodegeeks.com/2012/12/jax-rs-bean-validation-error-message-internationalization.html
jax-rs jax-ws