在Java EE 6中将Bean验证与JAX-RS集成

JavaBeans验证(Bean验证)是一种新的验证模型,可作为Java EE 6平台的一部分使用。 约束条件支持Bean验证模型,该约束以注释的形式出现在JavaBeans组件(例如托管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 。 为了能够改变Locale使用Accept-Language HTTP标头(例如,改变语言在浏览器选项),必须提供自定义实现。

定制验证器提供商

以下代码旨在解决此问题,并已通过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 Validation 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

相关文章

参考:来自Samaxes博客的JCG合作伙伴 Samuel Santos的Java EE 6中的Bean验证与JAX-RS集成

翻译自: https://www.javacodegeeks.com/2013/01/integrating-bean-validation-with-jax-rs-in-java-ee-6.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值