验证是用来检查某些数据是否遵循预定义的规则。本章将介绍Jersey对Bean Validation的支持,包括所需要的依赖、配置、注册及使用。
18.1 BeanValidation 依赖
Bean Validation在Jersey中是以扩展模块的形式来提供的,使用时需要在pom.xml(如果使用的是Maven)加入依赖。
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>2.22.2</version>
</dependency>
这个模块直接依赖Hibernate Validator,Hibernate Validator实现了Bean Validation API规范中的大多数接口。
如果想使用Bean Validation API的其它实现,将HibernateValidator的maven依赖从pom文件中删除,然后加入其它实现的依赖即可。
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>2.22.2</version>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
</exclusions>
</dependency>
18.2在Jersey中启用Bean Validation
只要将jersey-bean-validation模块加入classpath,jersey中的自动发现机制就会发现依赖,因此无需显式注册该模块。
18.3 配置Bean Validation
ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK // 实验了下还没搞明白,等看源码搞懂再补充。ServerProperties.BV_SEND_ERROR_IN_RESPONSE 启用将错误信息存于Response entity中并发送到客户端。
18.4 验证JAX-RS资源与方法
18.4.1 注解约束
Bean validation规范支持使用注解来验证beans,方法参数与方法返回值,例如。
@Path("constraint")
public class MyResourceClass {
@GET
public void paramTest(@NotNull @QueryParam("q") String q){
System.out.println(q);
}
}
public class Main {
public static void main(String[] args) throws IOException {
URI baseUri = UriBuilder.fromUri("http://localhost/").port(9998).build();
ResourceConfig config = new ResourceConfig();
config.packages("jersey.docCh3");
config.packages("jersey.docCh18");
config.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true)
.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(baseUri, config);
httpServer.start();
}
}
当发送请求http://localhost:9998/constraint 返回不能为null (path = MyResourceClass.paramTest.arg0, invalidValue = null)上面的代码中@NotNull对查询参数进行验证,验证注解并非只能用在方法参数上,这些注解也可以用在任何支持JAX-RS注解的地方。下面的代码将注解添在变量上,及getter方法上进行验证。
@Path("constraint")
public class MyResourceClass {
@NotNull
private String test;
private String test2;
@NotNull
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
@GET
public void paramTest(@NotNull @QueryParam("q") String q){
System.out.println(q);
}
}
发出请求http://localhost:9998/constraint 返回
不能为null (path =MyResourceClass.test, invalidValue =null)
不能为null (path =MyResourceClass.paramTest.arg0, invalidValue = null)
不能为null (path =MyResourceClass.test, invalidValue =null)
除了在变量上添加验证注解外,还可以在类上定义注解。有@NonEmptyNames 注解,验证MyResourceClass,代码示例如下。
@Target( {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface NonEmptyNames {
String message() default "{UserDefined NonEmptyNames}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class CheckCaseValidator implements ConstraintValidator<NonEmptyNames, Param> {
public void initialize(NonEmptyNames constraintAnnotation) {
}
public boolean isValid(Param value, ConstraintValidatorContext context) {
return false;
}
}
Resource如下
@Path("constraint")
public class MyResourceClass {
@GET
@Path("beanAno")
public Response beanAnoTest(@Valid @QueryParam("q") Param param){
System.out.println(param);
return Response.status(200).entity(param).build();
}
}
Bean的定义如下
@NonEmptyNames
public class Param {
@NotBlank
private String s;
@Range(min = -10,max = -5)
private int i;
public Param(String s) {
}
public Param() {
}
}
发出请求http://localhost:9998/constraint/beanAno?q=1234 返回。
需要在-10和-5之间 (path = MyResourceClass.beanAnoTest.arg0.i, invalidValue = 0)
不能为空 (path = MyResourceClass.beanAnoTest.arg0.s, invalidValue = null)
{UserDefined NonEmptyNames} (path = MyResourceClass.beanAnoTest.arg0, invalidValue = jersey.docCh18.Param@763fdff1)
其中{UserDefined NonEmptyNames}返回的是对类进行注解的结果。
18.4.2 注解约束与验证器
注解约束与验证器的定义遵循Bean Validation规范。在域上定义的@Email注解使用@Constraint元注解来定义,定义如下。
@Target({ METHOD, FIELD, PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
public @interface Email {
String message() default "{com.example.validation.constraints.email}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint注解必须包含对验证器类的引用,验证器类将用来验证值。EmailValidator类必须实现ConstraintValidator<Email,T>,这里T是要被验证的类型,EmailValidator的定义如下。
public class EmailValidator implements ConstraintValidator<Email, String> {
public void initialize(Email email) {
}
public boolean isValid(String value, ConstraintValidatorContext context) {
}
}
EmailValidator用在标有@Email注解的String类型的值上。 而除String外的Java类型也可以以同样的方式使用。
18.4.3 实体验证
请求实体可以被映射到资源方法参数上。有两种方式验证实体。如果请求实体被映射到Java bean上,这个javabean通过注解约束注解,那么可以通过使用@Valid注解可以来启用验证。
@StandardUser
class User {
@NotNull
private String firstName;
...
}
@Path("/")
class MyResourceClass {
@POST
@Consumes("application/xml")
public void registerUser(@Valid User user) {
...
}
}
上面的代码中,与@StandardUser关联的验证器将被调用,来对映射到user上的请求实体进行验证。除了将注解参数放在类上,也可以将对Bean验证的注解放在方法参数上,如下面的例子所示。以本文18.4.1中@NonEmptyNames的定义为例,修改并将该注解放到方法参数前对Bean验证。
@Target({ElementType.PARAMETER,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface NonEmptyNames {
String message() default "{UserDefined NonEmptyNames}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class CheckCaseValidator implements ConstraintValidator<NonEmptyNames, Param> {
public void initialize(NonEmptyNames constraintAnnotation) {
}
public boolean isValid(Param value, ConstraintValidatorContext context) {
return false;
}
}
@Path("constraint")
public class MyResourceClass {
@GET
@Path("beanAno")
public Response beanAnoTest(@NonEmptyNames @QueryParam("q") Param param){
System.out.println(param);
return Response.status(200).entity(param).build();
}
}
发出请求http://localhost:9998/constraint/beanAno?q=1234
返回响应{UserDefined NonEmptyNames}
(path =MyResourceClass.beanAnoTest.arg0, invalidValue = jersey.docCh18.Param@2d926039)
从资源方法参数中返回的响应实体也可以通过注解的方式来验证,只需要使用方法注解即可,例子如下。
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE,})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface NonEmptyNames {
String message() default "{UserDefined NonEmptyNames}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class CheckCaseValidator implements ConstraintValidator<NonEmptyNames, Param> {
public void initialize(NonEmptyNames constraintAnnotation) {
}
public boolean isValid(Param value, ConstraintValidatorContext context) {
return false;
}
}
@Path("constraint")
public class MyResourceClass {
@GET
@Path("beanAno")
@NonEmptyNames
public Param beanAnoTest(@QueryParam("q") String param){
System.out.println(param);
return new Param(param);
}
}
发送请求http://localhost:9998/constraint/beanAno?q=1234 返回验证信息 {UserDefinedNonEmptyNames} (path =MyResourceClass.beanAnoTest., invalidValue = jersey.docCh18.Param@a4d840d)