如何通过自定义spring invalidator注解校验数据合法性

如何通过自定义spring invalidator注解校验数据合法性

目录

自定义spring invalidator注解校验数据合法性

1、定义校验属性字符串长度的注解

2、实现校验逻辑,校验失败后返回错误提示

3、在模型字段属性上增加校验的注解

4、提供统一的校验方法

5、业务层调用校验方法

springboot 参数验证 validation

1、综述

2、依赖

3、定义实体类

4、创建rest controller

5、实现ExceptionHandler

6、写测试代码

7、跑测试

8、自定义注解

自定义spring invalidator注解校验数据合法性

在项目中经常会对用户输入的数据,或者外部导入到系统的数据做合法性检查。在spring boot框架的微服务中可以使用invalidator注解对数据做合法性,安全性校验。

下面给一个样例说明如何自定义注解实现校验逻辑。

1、定义校验属性字符串长度的注解 package com.elon.springbootdemo.manager.invalidator; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; /**  * 属性字段长度校验注解定义。  *   * @author elon  * @version 2018年9月19日  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = FieldLengthInvalidatorImpl.class) @Documented public @interface FieldLengthInvalidator {     // 字段支持的最大长度(字符数)     int maxLength() default 50;     // 校验失败后返回的错误信息     String message() default "";     // 分组     Class<?>[] groups() default {};     // 负载     Class<? extends Payload>[] payload() default {}; }

在定义注解时可声明变量用于辅助校验。上面的注解中定义了maxLength变量用于指定最大长度限制。变量可以设置默认值,使用注解时不传参数,变量就使用默认值。

2、实现校验逻辑,校验失败后返回错误提示 package com.elon.springbootdemo.manager.invalidator; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /**  * 字段长度校验实现类。  *   * @author elon  * @version 2018年9月19日  */ public class FieldLengthInvalidatorImpl implements ConstraintValidator<FieldLengthInvalidator, String> {     private int maxLength = 0;     @Override     public void initialize(FieldLengthInvalidator invalidator) {         maxLength = invalidator.maxLength();     }     @Override     public boolean isValid(String fieldValue, ConstraintValidatorContext context) {         if (fieldValue.length() > maxLength) {             context.disableDefaultConstraintViolation();             context.buildConstraintViolationWithTemplate("对象属性长度超过限制。").addConstraintViolation();             // 校验失败返回false。返回true上游收集不到错误信息。             return false;         }         return true;     } } 3、在模型字段属性上增加校验的注解 public class User {     private int userId = -1;     @FieldLengthInvalidator(maxLength=10)     private String name = ""; } 4、提供统一的校验方法 package com.elon.springbootdemo.manager; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; /**  * 有效性校验管理类。对外提供统一的校验调用接口。  * @author elon  * @version 2018年9月19日  */ public class InvalidatorMgr {     private InvalidatorMgr() {     }     /**      * 获取单例对象。      *       * @return 单例对象      */     public static InvalidatorMgr instance() {         return InvalidatorMgrBuilder.instance;     }     /**      * 校验模型所有属性的有效性。      *       * @param model 待校验模型      * @return 错误信息列表      */     public <T> List<String> validate(T model) {         ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();         Validator validator = validatorFactory.getValidator();         Set<ConstraintViolation<T>> resultSet = validator.validate(model);         List<String> messageList = new ArrayList<>();         resultSet.forEach((r)->messageList.add(r.getMessage()));                 return messageList;     }     /**      * 单例构建器。      * @author elon      * @version 2018年9月19日      */     private static class InvalidatorMgrBuilder{         private static InvalidatorMgr instance = new InvalidatorMgr();     } } 5、业务层调用校验方法         User user = new User();         user.setName("ahskahskhqlwjqlwqlwhqlhwlqjwlqhwlhqwhqlwjjqlwl");         List<String> messageList = InvalidatorMgr.instance().validate(user);         System.out.println(messageList);

invalidator注解主要用于实现长度,范围,非法字符等通用的规则校验。不适合用于做业务逻辑的校验,特定的业务校验写在业务层。 

springboot 参数验证 validation 1、综述

springboot提供了强大的基于注解的、开箱即用的验证功能,这种基于bean validation的实现和 hibernate validator类似

2、依赖

创建springboot项目,包含以下依赖

<dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>      <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>  <dependency>      <groupId>com.h2database</groupId>      <artifactId>h2</artifactId>     <version>1.4.197</version>      <scope>runtime</scope> </dependency> 3、定义实体类

测试项目为了方便,直接用JPA,使用@NotBlank指定非空字段,message是验证触发后返回的信息,还有@Null、@NotNull、@NotBlank、@Email、@Max、@Min、@Size、@Negative、@DecimalMax、@DecimalMin、@Positive、@PositiveOrZero、@NegativeOrZero、@AssertTrue、@AssertFalse、@Future、@FutureOrPresent、@Past、@PastOrPresent、@Pattern

@Entity public class User {     @Id     @GeneratedValue(strategy = GenerationType.AUTO)     private long id;     @NotBlank(message = "Name is mandatory")     private String name;     @NotBlank(message = "Email is mandatory")     private String email;     // standard constructors / setters / getters / toString     }

创建JPA的repository定义增删改查接口

@Repository public interface UserRepository extends CrudRepository<User, Long> {} 4、创建rest controller @RestController public class UserController {     @PostMapping("/users")     ResponseEntity<String> addUser(@Valid @RequestBody User user) {         // persisting the user         return ResponseEntity.ok("User is valid");     }     // standard constructors / other methods }

接收到的user对象添加了@Valid,当Spring Boot发现带有@Valid注解的参数时,会自动引导默认的JSR 380验证器验证参数。当目标参数未能通过验证时,Spring Boot将抛出一个MethodArgumentNotValidException

5、实现ExceptionHandler

直接抛出异常显然是不合理的,大部分情况需要经过处理返回给前端更友好的提示信息,通过@ExceptionHandler来处理抛出的异常实现该功能

@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) public Map<String, String> handleValidationExceptions(   MethodArgumentNotValidException ex) {     Map<String, String> errors = new HashMap<>();     ex.getBindingResult().getAllErrors().forEach((error) -> {         String fieldName = ((FieldError) error).getField();         String errorMessage = error.getDefaultMessage();         errors.put(fieldName, errorMessage);     });     return errors; }

MethodArgumentNotValidException作为上一步抛出的异常,当springboot执行validition触发时会调用此实现,该方法将每个无效字段的名称和验证后错误消息存储在映射中,然后它将映射作为JSON表示形式发送回客户端进行进一步处理。

6、写测试代码

使用springboot自带的插件进行测试rest controller,

@RunWith(SpringRunner.class)  @WebMvcTest @AutoConfigureMockMvc public class UserControllerIntegrationTest {     @MockBean     private UserRepository userRepository;     @Autowired     UserController userController;     @Autowired     private MockMvc mockMvc;     //...      }

@WebMvcTest允许我们使用MockMvcRequestBuilders和MockMvcResultMatchers实现的一组静态方法测试请求和响应。测试addUser()方法,在请求体中传递一个有效的User对象和一个无效的User对象。

@Test public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {     MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));     String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";     mockMvc.perform(MockMvcRequestBuilders.post("/users")       .content(user)       .contentType(MediaType.APPLICATION_JSON_UTF8))       .andExpect(MockMvcResultMatchers.status().isOk())       .andExpect(MockMvcResultMatchers.content()         .contentType(textPlainUtf8)); } @Test public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {     String user = "{\"name\": \"\", \"email\" : \"bob@domain.com\"}";     mockMvc.perform(MockMvcRequestBuilders.post("/users")       .content(user)       .contentType(MediaType.APPLICATION_JSON_UTF8))       .andExpect(MockMvcResultMatchers.status().isBadRequest())       .andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))       .andExpect(MockMvcResultMatchers.content()         .contentType(MediaType.APPLICATION_JSON_UTF8));     } }

也可以使用postman或fiddler来测试REST controller API。

7、跑测试 @SpringBootApplication public class Application {     public static void main(String[] args) {         SpringApplication.run(Application.class, args);     }     @Bean     public CommandLineRunner run(UserRepository userRepository) throws Exception {         return (String[] args) -> {             User user1 = new User("Bob", "bob@domain.com");             User user2 = new User("Jenny", "jenny@domain.com");             userRepository.save(user1);             userRepository.save(user2);             userRepository.findAll().forEach(System.out::println);         };     } }

如果用没有用户名或邮箱的数据发送请求会收到返回的提示信息

{   "name":"Name is mandatory",   "email":"Email is mandatory" } 8、自定义注解

在进行参数验证的时候,往往存在现有的约束注解不能满足的情况,此时就需要我们自己定义validation注解了,下次介绍如何自己定义一个验证注解。 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持易知道(ezd.cc)。

推荐阅读