博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验,使用消息资源文件对消息国际化...
阅读量:4513 次
发布时间:2019-06-08

本文共 17056 字,大约阅读时间需要 56 分钟。

导包和配置

导入 JSR 303 的包、hibernate valid 的包

org.hibernate.validator
hibernate-validator
6.0.5.Final
javax.validation
validation-api
2.0.0.Final

springboot 配置

resources/application.yml 消息资源文件国际化处理配置

spring:  messages:    basename: base,todo # 资源文件 base.properties 和 todo.properties,多个用逗号隔开    encoding: UTF-8 # 必须指定解析编码,否则中文乱码

在 springboot 启动类里面配置

@SpringBootApplicationpublic 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();    }}

我们对于校验参数通过不了抛出的异常进行处理,是通过统一异常捕捉。

@ControllerAdvice@Componentpublic 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());    }}

Msg 结果返回集

public class Msg {    private boolean success = true;    //是否成功    private Object data;        //数据    private String message;     //信息    private long code;       //错误代码    public Object getData() {        return this.data;    }    public String getMessage() {        return this.message;    }    public long getCode() {        return this.code;    }    public Msg() {    }    public Msg(int status) {        this.code = status;    }    public Msg(String msg, Object data) {        this.message = msg;        this.data = data;    }    public Msg(boolean success, String msg, Object data) {        this.success = success;        this.message = msg;        this.data = data;    }    public Msg(int status, String msg, Object data) {        this.code = status;        this.message = msg;        this.data = data;    }    public Msg(boolean success, int status, String msg, Object data) {        this.success = success;        this.code = status;        this.message = msg;        this.data = data;    }    public boolean isSuccess() {        return this.success;    }    public static Msg.BodyBuilder status(boolean success, int code) {        return new Msg.DefaultBuilder(success, code);    }    public static Msg.BodyBuilder status(boolean success) {        return new Msg.DefaultBuilder(success);    }    /* 快捷输出 start */    public static Msg.BodyBuilder ok() {        return status(true);    }    public static Msg.BodyBuilder ok(int code) {        return status(true, code);    }    public static Msg ok(Object data) {        Msg.BodyBuilder builder = ok();        return builder.body(data);    }    public static Msg ok(String msg) {        Msg.BodyBuilder builder = ok();        return builder.msg(msg).build();    }    public static Msg ok(String msg, Object data) {        Msg.BodyBuilder builder = ok();        return builder.msg(msg).body(data);    }    public static Msg ok(int code, String msg, Object data) {        Msg.BodyBuilder builder = ok(code);        return builder.msg(msg).body(data);    }    public static Msg.BodyBuilder fail() {        return status(false);    }    public static Msg.BodyBuilder fail(int code) {        return status(false, code);    }    public static Msg fail(Object data) {        Msg.BodyBuilder builder = fail();        return builder.body(data);    }    public static Msg fail(String msg) {        Msg.BodyBuilder builder = fail();        return builder.msg(msg).build();    }    public static Msg fail(String msg, Object data) {        Msg.BodyBuilder builder = fail();        return builder.msg(msg).body(data);    }    public static Msg fail(int code, String msg, Object data) {        Msg.BodyBuilder builder = fail(code);        return builder.msg(msg).body(data);    }    public static Msg error(Object data) {        Msg.BodyBuilder builder = fail();        return builder.body(data);    }    public static Msg error(String msg) {        Msg.BodyBuilder builder = fail();        return builder.msg(msg).build();    }    public static Msg error(String msg, Object data) {        Msg.BodyBuilder builder = fail();        return builder.msg(msg).body(data);    }    public static Msg error(int code, String msg, Object data) {        Msg.BodyBuilder builder = fail(code);        return builder.msg(msg).body(data);    }    /* 快捷输出 end */    private static class DefaultBuilder implements Msg.BodyBuilder {        private boolean success;        private int code;        private String message;        public DefaultBuilder(boolean success) {            this.success = success;        }        public DefaultBuilder(boolean success, int code) {            this.success = success;            this.code = code;        }        public DefaultBuilder(boolean success, String message) {            this.success = success;            this.message = message;        }        @Override        public Msg body(Object data) {            Msg msg = new Msg();            msg.success = this.success;            msg.message = this.message;            msg.code = this.code;            if (data instanceof Number) {                return new Msg(this.success, this.message, data);            }            msg.data = data;            if (msg.data == null) {                msg.data = new Object();            }            return msg;        }        @Override        public Msg.BodyBuilder msg(String message) {            this.message = message;            return this;        }        @Override        public Msg build() {            return new Msg(this.success, this.code, this.message, "");        }    }    public interface BodyBuilder {        Msg body(Object var1);        Msg.BodyBuilder msg(String message);        Msg build();    }

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 接口等等,都是自定义约定的东西。

public interface C {    interface Insert {}    interface Query {}    interface Update {}    interface UpdateStatus {}}
public interface S {    interface Insert {}    interface Query {}    interface Update {}    interface UpdateStatus {}}
/** * 私有 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 扫描并注册为单例

@Componentpublic class ValidUtil {    @Autowired    private Validator validator;    public 
Set
> validate(T object, Class
... groups) { return validator.validate(object, groups); } public
Set
> validateValue(Class
beanType, String propertyName, Object value, Class
... groups) { return validator.validateValue(beanType, propertyName, value, groups); } /** * 校验参数,并返回第一个错误提示 * @param t 验证的对象 * @param groups 验证的组别 * @param
对象擦除前原类型 * @return 第一个错误提示 */ public
void validAndReturnFirstErrorTips(T t, Class
... groups) { Set
> validate = validator.validate(t, groups); if (validate.size() > 0) { ConstraintViolation
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
对象擦除前原类型 * @return 第一个错误提示 */ public
void validAndReturnFirstErrorTips(Class targetClass, String fieldName, Object obj, Class
... groups) { Set
> 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 拦截方法执行参数,对参数获取注解。再利用工具类来验证参数,如果验证不通过,直接抛出自定义错误,自定义错误已经全局统一处理了。

@Aspect@Componentpublic 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 使用方式基本一样

@Validatedpublic 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);}

分享几个自定义验证注解

字符串判空验证
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
[] payload() default {}; class NotBlankValidator implements ConstraintValidator
{ public NotBlankValidator() { } @Override public void initialize(NotBlank constraintAnnotation) { } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { return value != null && !value.toString().isEmpty(); } }}
类型判断,判断 type 是否为其中一个值,可以根据验证组自定义判断

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;

自定义注解

@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
[] payload() default {}; class TodoTypeValidFactory implements ConstraintValidator
{ 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 支持的同一注解多次特性。

根据上面的同样也可以用在枚举类上

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;

自定义注解

@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
[] payload() default {}; class TodoStatusValidFactory implements ConstraintValidator
{ 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(); }}

转载于:https://www.cnblogs.com/zengyufei/p/8056628.html

你可能感兴趣的文章
Windows7下python2.7.6环境变量配置
查看>>
java设计模式------代理模式
查看>>
WPF学习笔记----注释标记,使用自定义资源字典(style)文件的流程
查看>>
元素定位的八大法则
查看>>
Sublime Text 3 使用小记
查看>>
总结Pycharm里面常用的快捷键
查看>>
util.promisify 的那些事儿
查看>>
配置phpstudy+phpstorm+xdebug环境
查看>>
BZOJ 1079 [SCOI2008]着色方案
查看>>
[Win8.1系统]双系统
查看>>
HDU 3899 树形DP
查看>>
继承上机作业
查看>>
设计模式 4/23 建造者模式
查看>>
Logging in Java
查看>>
leetcode算法:Distribute Candies
查看>>
机器学习之路: python 朴素贝叶斯分类器 MultinomialNB 预测新闻类别
查看>>
LINUX 忘记root密码
查看>>
json转换成Map
查看>>
MySQL查看当前用户、存储引擎、日志
查看>>
tpcc-mysql 系列二:进行TPCC测试
查看>>