Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化

2025-05-29 0 51

导包和配置

导入 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 校验 使用消息资源文件对消息国际化,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对快网idc网站的支持!

原文链接:http://www.cnblogs.com/zengyufei/p/8056628.html

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化 https://www.kuaiidc.com/113445.html

相关文章

发表评论
暂无评论