zoukankan      html  css  js  c++  java
  • Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验,使用消息资源文件对消息国际化

    导包和配置

    导入 JSR 303 的包、hibernate valid 的包

    <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 启动类里面配置

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

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

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

    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 扫描并注册为单例

    
    @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 拦截方法执行参数,对参数获取注解。再利用工具类来验证参数,如果验证不通过,直接抛出自定义错误,自定义错误已经全局统一处理了。

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

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

    分享几个自定义验证注解

    字符串判空验证
    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 是否为其中一个值,可以根据验证组自定义判断

    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<? 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 支持的同一注解多次特性。

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

    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<? 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();
        }
    }
    
  • 相关阅读:
    51nod 1163 最高的奖励
    51nod 1191 消灭兔子
    51nod 2538 二三排列
    node做渲染服务器的实现
    gulp静态服务器的搭建
    canvas中裁切(橡皮檫)的应用--探照灯,点击去除遮罩
    canvas中图片、视频的加载(截图、切片)
    canvas中笔触基础知识
    Gitee的使用流程
    gulp的使用
  • 原文地址:https://www.cnblogs.com/zengyufei/p/8056628.html
Copyright © 2011-2022 走看看