自定义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{ 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 错误信息列表 */ publicList validate(T model) { ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); Validator validator = validatorFactory.getValidator(); Set > resultSet = validator.validate(model); List 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"); ListmessageList = InvalidatorMgr.instance().validate(user); System.out.println(messageList);
invalidator注解主要用于实现长度,范围,非法字符等通用的规则校验。不适合用于做业务逻辑的校验,特定的业务校验写在业务层。
springboot 参数验证 validation
1、综述
springboot提供了强大的基于注解的、开箱即用的验证功能,这种基于bean validation的实现和 hibernate validator类似
2、依赖
创建springboot项目,包含以下依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 1.4.197 runtime
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{}
4、创建rest controller
@RestController public class UserController { @PostMapping("/users") ResponseEntityaddUser(@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 MaphandleValidationExceptions( MethodArgumentNotValidException ex) { Map 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注解了,下次介绍如何自己定义一个验证注解。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。