導包和配置
導入 JSR 303 的包、hibernate valid 的包
1
2
3
4
5
6
7
8
9
10
|
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version> 6.0 . 5 .Final</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version> 2.0 . 0 .Final</version> </dependency> |
springboot 配置
resources/application.yml 消息資源文件國際化處理配置
spring:
messages:
basename: base,todo # 資源文件 base.properties 和 todo.properties,多個用逗號隔開
encoding: UTF-8 # 必須指定解析編碼,否則中文亂碼
在 springboot 啟動類里面配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
@SpringBootApplication public class Application extends WebMvcConfigurerAdapter { @Value ( "${spring.messages.basename}" ) private String basename; public static void main(String[] args) { SpringApplication.run(Application. class , args); } @Bean @Primary public MessageSource messageSource() { ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); resourceBundleMessageSource.setUseCodeAsDefaultMessage( false ); resourceBundleMessageSource.setDefaultEncoding( "UTF-8" ); // 重復定義 resourceBundleMessageSource.setBasenames(basename.split( "," )); return resourceBundleMessageSource; } @Bean @Primary public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean(); validatorFactoryBean.setProviderClass(HibernateValidator. class ); validatorFactoryBean.setValidationMessageSource(messageSource()); return validatorFactoryBean; } @Override public Validator getValidator() { return validator(); } /** * 方法級別的單個參數驗證開啟 */ @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { return new MethodValidationPostProcessor(); } } |
我們對于校驗參數通過不了拋出的異常進行處理,是通過統一異常捕捉。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
@ControllerAdvice @Component public class BindValidExceptionHandler { @ResponseStatus (value = HttpStatus.OK) @ExceptionHandler (ConstraintViolationException. class ) public @ResponseBody Msg handleConstraintViolationException(ConstraintViolationException e) { String messageTemplate = e.getConstraintViolations().iterator().next().getMessageTemplate(); return Msg.error(messageTemplate); } @ResponseStatus (value = HttpStatus.OK) @ExceptionHandler (BindException. class ) public @ResponseBody Msg handleBindException(BindException e) { BindingResult bindingResult = e.getBindingResult(); String className = bindingResult.getTarget().getClass().getName(); FieldError next = bindingResult.getFieldErrors().iterator().next(); String fieldName = next.getField(); String defaultMessage = next.getDefaultMessage(); if (Pattern.compile( "IllegalArgumentException: No enum" ).matcher(defaultMessage).find()) { Matcher matcher = Pattern.compile( "for value '(.*?)'" ).matcher(defaultMessage); if (matcher.find()) { defaultMessage = "找不到枚舉類型【" + matcher.group( 1 ) + "】" ; } } return Msg.error(defaultMessage); } @ResponseStatus (value = HttpStatus.OK) @ExceptionHandler (ValidError. class ) public @ResponseBody Msg handleValidError(ValidError e) { return Msg.error(e.getMessage()); } } |
resources/base.propertie
creatorId=創建者 id 不能為小于 {value}。
modifierId=修改者 id 不能為小于 {value}。
resources/todo.properties
todo.privateId.min=私有 id 不能為小于 {value}。
在 bean 字段上使用注解,其中 group 中的 C 和 S 接口是指 Controller 和 Service 的叫法簡稱,里面分別有 Insert 接口、Update 接口等等,都是自定義約定的東西。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** * 私有 id,是代表項目任務/非項目任務/風險/問題/評審待辦問題等多張表的外鍵 */ @Min (value = 1 , message = "{todo.privateId.min}" , groups = {C.Insert. class , C.Update. class , S.Insert. class , S.Update. class }) private long privateId; /** * 創建者id */ @Min (value = 1 , message = "{creatorId}" , groups = {S.Insert. class }) private long creatorId; Controller 控制層驗證 @Validated @RestController @RequestMapping ( "todo" ) public class TodoController { @Autowired private TodoService todoService; @GetMapping ( "getVo" ) public Msg getVo( @Min (value = 1 , message = "待辦 id 不能小于 1。" ) @RequestParam (required = false , defaultValue = "0" ) long id ) { return this .todoService.getVo(id); } @PostMapping ( "add" ) public Msg add( @Validated ({C.Insert. class }) Todo todo) { return this .todoService.add(todo); } } |
@Validated({C.Insert.class}) 聲明啟用 bean 注解上的驗證組,其他驗證組不會進行驗證,這樣可以區別開來進行單獨驗證。
而像沒有實體,只有一個基礎數據類型的,可以進行驗證,但是需要滿足三個條件:
- 在啟動類配置方法級別驗證啟用類
- 在 Controller 類上注解 @Validated
- 在方法參數里使用驗證注解如 @Min,@NotNull 等等
自行驗證。
Service 服務層 AOP 驗證
ValidUtil 工具類
需要被 springboot 掃描并注冊為單例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
@Component public class ValidUtil { @Autowired private Validator validator; public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) { return validator.validate(object, groups); } public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) { return validator.validateValue(beanType, propertyName, value, groups); } /** * 校驗參數,并返回第一個錯誤提示 * @param t 驗證的對象 * @param groups 驗證的組別 * @param <T> 對象擦除前原類型 * @return 第一個錯誤提示 */ public <T> void validAndReturnFirstErrorTips(T t, Class<?>... groups) { Set<ConstraintViolation<T>> validate = validator.validate(t, groups); if (validate.size() > 0 ) { ConstraintViolation<T> next = validate.iterator().next(); String message = next.getRootBeanClass().getName() + "-" + next.getPropertyPath() + "-" + next.getMessage(); throw new ValidError(message); } } /** * 校驗參數,并返回第一個錯誤提示 * @param targetClass 驗證的對象的 class 類型 * @param fieldName 需要驗證的名字 * @param obj 需要屬性值 * @param groups 驗證的組別 * @param <T> 對象擦除前原類型 * @return 第一個錯誤提示 */ public <T> void validAndReturnFirstErrorTips(Class targetClass, String fieldName, Object obj, Class<?>... groups) { Set<ConstraintViolation<T>> validate = validator.validateValue(targetClass, fieldName, obj, groups); if (validate.size() > 0 ) { String message = targetClass.getName() + "-" + fieldName + "-" + validate.iterator().next().getMessage(); throw new ValidError(message); } } } |
AOP 配置
主要原理是利用 aop 攔截方法執行參數,對參數獲取注解。再利用工具類來驗證參數,如果驗證不通過,直接拋出自定義錯誤,自定義錯誤已經全局統一處理了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
@Aspect @Component public class ValidatorAOP { @Autowired private ValidUtil validUtil; /** * 定義攔截規則:攔截 com.servic 包下面的所有類中,有 @Service 注解的方法。 */ @Pointcut ( "execution(* com.service..*(..)) and @annotation(org.springframework.stereotype.Service)" ) public void controllerMethodPointcut() { } /** * 攔截器具體實現 */ @Around ( "controllerMethodPointcut()" ) // 指定攔截器規則;也可以直接把 “execution(* com.xjj.........)” 寫進這里 public Object Interceptor(ProceedingJoinPoint pjp) { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method method = methodSignature.getMethod(); Annotation[][] argAnnotations = method.getParameterAnnotations(); Object[] args = pjp.getArgs(); for ( int i = 0 ; i < args.length; i++) { for (Annotation annotation : argAnnotations[i]) { if (Validated. class .isInstance(annotation)) { Validated validated = (Validated) annotation; Class<?>[] groups = validated.value(); validUtil.validAndReturnFirstErrorTips(args[i], groups); } } } try { return pjp.proceed(args); } catch (Throwable throwable) { throwable.printStackTrace(); } return true ; } } |
驗證注解 @Min @NotNull 使用方法
不能寫在實現類上,只能在接口中使用注解
與 Controller 使用方式基本一樣
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Validated public interface TodoService { /** * 查詢 單個待辦 * @param id 序號 * @return 單個待辦 */ Msg getVo( @Min (value = 1 , message = "待辦 id 不能小于 1。" ) long id); /** * 添加數據 * @param todo 對象 */ Msg add( @Validated ({S.Insert. class }) Todo todo); } |
分享幾個自定義驗證注解
字符串判空驗證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package javax.validation.constraints; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.Payload; import java.lang.annotation.*; /** * 字符串判空驗證,hibernate 自帶的可能有問題,使用不了,需要重寫,package 是不能變的。 */ @Documented @Constraint ( validatedBy = {NotBlank.NotBlankValidator. class } ) @Target ({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) @Retention (RetentionPolicy.RUNTIME) public @interface NotBlank { Class<?>[] groups() default {}; String message() default "{notBlank}" ; Class<? extends Payload>[] payload() default {}; class NotBlankValidator implements ConstraintValidator<NotBlank, Object> { public NotBlankValidator() { } @Override public void initialize(NotBlank constraintAnnotation) { } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { return value != null && !value.toString().isEmpty(); } } } |
類型判斷,判斷 type 是否為其中一個值,可以根據驗證組自定義判斷
1
2
3
4
5
6
7
8
9
10
11
|
resources/todo.properties todo.todoType.insert=新增時,待辦類型只能是 非項目任務、項目任務、問題 之中一。 todo.todoType.update=修改時,待辦類型只能是風險、評審待辦問題 之中一。 bean /** * 待辦類型0非項目任務1項目任務2問題3風險4評審待辦問題 */ @TodoTypeValid (value = { "0" , "1" , "2" }, message = "{todo.todoType.insert}" , groups = {C.Insert. class , S.Insert. class }) @TodoTypeValid (value = { "3" , "4" }, message = "{todo.todoType.update}" , groups = {C.Update. class , S.Update. class }) private String todoType; |
自定義注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Documented @Constraint (validatedBy = {TodoTypeValid.TodoTypeValidFactory. class }) @Target ({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) @Retention (RetentionPolicy.RUNTIME) @Repeatable (TodoTypeValid.List. class ) public @interface TodoTypeValid { String message() default "請輸入正確的類型" ; String[] value() default {}; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; class TodoTypeValidFactory implements ConstraintValidator<TodoTypeValid, String> { private String[] annotationValue; @Override public void initialize(TodoTypeValid todoStatusValid) { this .annotationValue = todoStatusValid.value(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (Arrays.asList(annotationValue).contains(value)) return true ; return false ; } } @Target ({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) @Retention (RetentionPolicy.RUNTIME) @Documented @interface List { TodoTypeValid[] value(); } } |
@Repeatable(TodoTypeValid.List.class) 是 JDK8 支持的同一注解多次特性。
根據上面的同樣也可以用在枚舉類上
1
2
3
4
5
6
7
8
9
10
|
resources/todo.properties todo.todoStatus.insert=新增時,狀態只能是未開始。 todo.todoStatus.update=修改時,狀態只能是進行中或已完成。 bean /** * 待辦狀態0未開始1進行中2已完成 */ @TodoStatusValid (enums = {TodoStatus.NOT_STARTED}, message = "{todo.todoStatus.insert}" , groups = {C.Insert. class , S.Insert. class }) @TodoStatusValid (enums = {TodoStatus.PROCESSING, TodoStatus.COMPLETED}, message = "{todo.todoStatus.update}" , groups = {C.Update. class , S.Update. class }) private TodoStatus todoStatus; |
自定義注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
@Documented @Constraint (validatedBy = {TodoStatusValid.TodoStatusValidFactory. class }) @Target ({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) @Retention (RetentionPolicy.RUNTIME) @Repeatable (TodoStatusValid.List. class ) public @interface TodoStatusValid { String message() default "請輸入正確的狀態" ; TodoStatus[] enums() default {}; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; class TodoStatusValidFactory implements ConstraintValidator<TodoStatusValid, TodoStatus> { private TodoStatus[] enums; @Override public void initialize(TodoStatusValid todoStatusValid) { this .enums = todoStatusValid.enums(); } @Override public boolean isValid(TodoStatus value, ConstraintValidatorContext context) { TodoStatus[] values = TodoStatus.values(); if (enums != null && enums.length != 0 ) { values = enums; } if (Arrays.asList(values).contains(value)) return true ; return false ; } } @Target ({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) @Retention (RetentionPolicy.RUNTIME) @Documented @interface List { TodoStatusValid[] value(); } } |
總結
以上所述是小編給大家介紹的Springboot 使用 JSR 303 對 Controller 控制層校驗及 Service 服務層 AOP 校驗 使用消息資源文件對消息國際化,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:http://www.cnblogs.com/zengyufei/p/8056628.html