精华内容
下载资源
问答
  • JSR-303 在开始动手实践之前,我们先了解一下接下来我们将使用的一项标准规范:JSR-303 什么是JSR? JSR是Java Specification Requests的...JSR-303定义的是什么标准? JSR-303 是JAVA EE 6 中的一项子规范,叫做

    JSR-303

    在开始动手实践之前,我们先了解一下接下来我们将使用的一项标准规范:JSR-303

    什么是JSR?

    JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

    JSR-303定义的是什么标准?

    JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

    Bean Validation中内置的constraint

    Hibernate Validator附加的constraint

    在JSR-303的标准之下,我们可以通过上面这些注解,优雅的定义各个请求参数的校验

     

    快速入门

    我们先来做一个简单的例子,比如:定义字段不能为Null。只需要两步

    第一步:在要校验的字段上添加上@NotNull注解,具体如下:

    @Data
    @ApiModel(description="用户实体")
    public class User {
    
        @ApiModelProperty("用户编号")
        private Long id;
    
        @NotNull
        @ApiModelProperty("用户姓名")
        private String name;
    
        @NotNull
        @ApiModelProperty("用户年龄")
        private Integer age;
    
    }
    

    第二步:在需要校验的参数实体前添加@Valid注解,具体如下:

    @PostMapping("/")
    @ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
    public String postUser(@Valid @RequestBody User user) {
        users.put(user.getId(), user);
        return "success";
    }
    

    完成上面配置之后,启动应用,并用POST请求访问localhost:8080/users/接口

     

     

    2.1提供一个场景
    提供一个实体类,该实体类含有一个属性

    @Data
    public class ExceptionQuery {
        private String userName;
    }

    要求用户提供的姓名不能为空,下面开始校验该属性

    2.1首先使用if判断
    @PostMapping("/query")
    public Result  getResult(@RequestBody ExceptionQuery query){
        //校验参数
        if (StringUtils.isEmpty(query.getUserName())){
            //抛出异常
        }
        return Result.buildSuccess(query);
    }

    这个方法本省是没有问题的,但是如果入参有很多属性需要校验,那么程序中就会存在很多if判断,这样显的代码很冗余。有没有方法替代if判断呢?当然是有的,使用jsr303可以解决该问题

    2.3jsr303使用
    1.为属性加@NotBlank属性

    @Data
    public class ExceptionQuery {

        @NotBlank(message = "用户姓名必须提交")
        private String userName;

    }

    注:像@NotBlank一类的校验注解都可以重写message属性

    public @interface NotBlank {
        
        //default表明可以重写message属性
        String message() default "{javax.validation.constraints.NotBlank.message}";
        //省略...
    }

    2.在方法入参参数上加@Valid注解

    @PostMapping("/query")
    public Result  getResult(@Valid @RequestBody ExceptionQuery query){
        return Result.buildSuccess(query);
    }

    3.postman测试结果

    从输出结果上可以看到重写后的message属性,而对于错误的输出结果Spring也提供了对应类存储错误信息,这个类就是BindingResult,将上面的方法加上BindingResult参数,如下代码

    @PostMapping("/query")
    public Result  getResult(@Valid @RequestBody ExceptionQuery query, BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map = new HashMap<>();
            result.getFieldErrors().forEach(item->{
                //获取发生错误时的message
                String message = item.getDefaultMessage();
                //获取发生错误的字段
                String field = item.getField();
                map.put(field,message);
                System.out.println(field+":"+message);
            });
            return Result.buildFailure(400,"参数错误",map);
        }
        return Result.buildSuccess(query);
    }

    输出结果


    然而,咱们不能对每个方法都传入一个BindingResult参数,可以使用统一异常处理输出错误信息

    4.配合异常处理

    首先说下为什么能通过异常处理输出错误信息?

    我们定位到一个类MethodArgumentNotValidException,该类也是Spring提供的,包含BindingResult引用,因此该类可以拿到输出的错误信息


    异常处理代码

    @Slf4j
    @RestControllerAdvice(annotations = {RestController.class, Controller.class})
    public class GlobalExceptionHandler {
        
        @ResponseStatus(HttpStatus.OK)
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public Result handleValidationException(MethodArgumentNotValidException e) {
            log.error(ErrorStatus.ILLEGAL_DATA.getMessage() + ":" + e.getMessage());
            Map<String,Object> map=new HashMap<>();
            
            //拿到异常输出信息
            BindingResult bindingResult = e.getBindingResult();
            bindingResult.getFieldErrors().forEach(fieldError -> {
                String message = fieldError.getDefaultMessage();
                String field = fieldError.getField();
                map.put(field,message);
            });
            return Result.buildFailure(ErrorStatus.ILLEGAL_DATA.getCode(),String.valueOf(getFirstOrNull(map)),map);
        }

        //拿到map第一个值
        private static Object getFirstOrNull(Map<String, Object> map) {
            Object obj = null;
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                obj = entry.getValue();
                if (obj != null) {
                    break;
                }
            }
            return  obj;
        }
    }

    加上异常处理后的方法代码如下所示,关于@ControllerAdvice全局异常处理请看这一篇博客

    @PostMapping("/query")
    public Result  getResult(@Valid @RequestBody ExceptionQuery query){
        return Result.buildSuccess(query);
    }

    是不是很简洁,下面看下输出结果


    三、jsr303相关校验注解导图展示

    注意:@Validated和@Valid注解都是开启注解校验功能的注解

    下面举个例子说明一下

    阐述基本用法可能不是那么适合,到实际应用改下就行

    输出结果


    四、@Valid和@Validated的区别
    上述例子咱们用的是@Valid进行校验的,用@Validated注解也可以,那么他两的区别是什么呢?

    所属不同:
    该注解所属包为:javax.validation.Valid,而 @Validated是Spring基于@Valid进一步封装,并提供了一些高级功能,如分组,分组顺序等


    使用位置不同:
    @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性上
    @Valid:可以用在方法、构造函数、方法参数和成员属性上

    正是由于@Valid能用于成员属性(字段)上,因此@Valid可以提供级联校验,关于级联校验下面就要阐述

    五、@Valid和@Validated高级使用
    5.1Valid级联校验
    级联校验也叫嵌套检测,嵌套即一个实体类包含另一个实体类

    举个例子
    下面有个People实体类

    该类还有两个属性,一个是名字,一个头发,因为一个人有很多头发(暂不考虑光头哈哈),Hair实体类如下

    下面写一个方法检测下用户数据

    在方法参数上用@Validated或者@Valid都可以

    @PostMapping("/add")
    public Result getPeople(@Validated @RequestBody People people){
        return Result.buildSuccess(people);
    }

    测试结果如下

    暴露一个问题

    没有输出Hair实体类对数据的校验结果,可以证明@Validated和@Valid加在方法参数前,都不会对嵌套的实体类Hair进行检测

    实现检测

    为了能够完成对嵌套实体类的检测,我们需要在属性上使用@Vaild注解


    看下此时的输出结果


    5.2@Validated分组校验
    5.2.1什么是分组校验
    分组校验是由@Validated的value方法提供的,用于开启指定的组校验,分别作用不同的业务场景中,下面举个例子说明下

    5.2.2.引出jsr303校验注解的groups方法
    假如使用Mybatis做持久化框架(其他也可以),我们知道当向数据库插入一条数据时,这条数据id是自动生成,不用用户传入的,而当我们修改一条数据时,id需要用户传入,因此在修改操作时需要对id进行校验。像以上修改和插入对id不同的操作,这个时候就要使用groups方法对id进行分组,如下图所示


    为了涵盖多种情况,加入了两个属性,personName属性在AddGroup.class,UpdateGroup.class时都会校验,personAge属性在不指定分组时校验

    5.2.3.@Validated注解value方法开启指定分组校验


    5.2.4.检验结果
    下面使用一样的数据请求三个方法,得到的结果如下

    模拟数据,该数据都不符合要求
    {
        "personId":"",
        "personName":"",
        "personAge":0
    }

    getPerson方法不指定分组
    校验了@Range(min=1,max=400,message="年龄提交有误"),该注解不含groups方法
    {
        "success": false,
        "code": 10003,
        "msg": "年龄提交有误",
        "data": {
            "personAge": "年龄提交有误"
        }
    }


    addPerson方法指定AddGroup分组
    校验了@NotBlank(message = "名字不能为空",groups = {AddGroup.class,UpdateGroup.class})
    {
        "success": false,
        "code": 10003,
        "msg": "名字不能为空",
        "data": {
            "personName": "名字不能为空"
        }
    }

    updatePerson方法指定UpdateGroup分组
    校验了@NotBlank(message = "id不能为空",groups = UpdateGroup.class)和
    @NotBlank(message = "名字不能为空",groups = {AddGroup.class,UpdateGroup.class})
    {
        "success": false,
        "code": 10003,
        "msg": "名字不能为空",
        "data": {
            "personName": "名字不能为空",
            "personId": "id不能为空"
        }
    }

    5.2.5.结论
    如果校验注解添加上groups方法并指定分组,只有@Validated注解value方法指定该分组,才会开启校验注解的校验数据功能。

    同样的如果校验注解没有groups指定分组,则@Validated注解value方法为默认分组时才会开启

    5.3@Validated分组校验顺序
    5.3.1为什么需要?
    默认情况下,分组间的约束验证是无序的,然而某些情况下分组间的校验顺序却很重要,比如第二组约束验证依赖于第一组稳定状态来进行,这个时候需要分组按照顺序校验。

    5.3.2如何实现?
    分组校验顺序由@GroupSequence注解实现

    举个例子

    1.列出分组顺序

    public interface First {}

    public interface Second {}

    //此时first、second顺序校验
    @GroupSequence({First.class,Second.class})
    public interface Group {}

    2.实体类及请求方法

    @Data
    public class UserGroupSequence {

        @NotEmpty(message = "id不能为空",groups = First.class)
        private String userId;

        @NotEmpty(message = "姓名不能空",groups = First.class)
        @Size(min = 3,max = 8,message = "姓名长度在3到8之间",groups = Second.class)
        private String userName;
    }


    @RestController
    @RequestMapping("/group")
    public class UserGroupSequenceController {

        //此时用的是value方法指定的是Group接口
        @PostMapping("/add")
        public Result addGroup(@Validated(value = Group.class) @RequestBody UserGroupSequence sequence){
            return Result.buildSuccess(sequence);
        }
    }

    3.测试结果

    模拟数据
    {
        "userId":"",
        "userName":""
    }

    结果
    {
        "success": false,
        "code": 10003,
        "msg": "姓名不能空",
        "data": {
            "userName": "姓名不能空",
            "userId": "id不能为空"
        }
    }

    4.结论

    @GroupSequence注解指定分组顺序时,如果First标识的校验注解没有通过,则Second标识的注解不会生效。

    读者可以测试下,如果把userName上两个校验注解groups去掉,输出结果是"姓名不能空"及"姓名长度在3到8之间"交替出现

    5.4@Validated非实体类校验
    上面校验都是对实体类的校验,下面来介绍下对非实体类的校验

    5.4.1.使用@Validated注解
    这里特别要注意一点就是@Validated注解对于非实体类的校验,在类上注解才会起效果

    @Validated
    public class AnnotationController {
        
        @GetMapping("/getage")
        public Result getAge(@Range(min = 3,max = 8,message = "年龄在3-8岁") @RequestParam String age){
            return Result.buildSuccess(age);
        }
    }

    5.4.2.GlobalExceptionHandler类添加异常处理方法
        @ExceptionHandler(ConstraintViolationException.class)
        @ResponseBody
        public Result resolveConstraintViolationException(ConstraintViolationException ex){

            Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
            
            //对异常信息进行处理
            if(!CollectionUtils.isEmpty(constraintViolations)){
                StringBuilder msgBuilder = new StringBuilder();
                for(ConstraintViolation constraintViolation :constraintViolations){
                    msgBuilder.append(constraintViolation.getMessage()).append(",");
                }
                String errorMessage = msgBuilder.toString();
                if(errorMessage.length()>1){
                    errorMessage = errorMessage.substring(0,errorMessage.length()-1);
                }
                return Result.buildFailure(ErrorStatus.ILLEGAL_DATA.getCode(),errorMessage);
            }
            return Result.buildFailure(ErrorStatus.ILLEGAL_DATA.getCode(),ex.getMessage());
        }

    5.4.3测试结果
    http://localhost:8082/annotation/getage?age=1

    {
        "success": false,
        "code": 10003,
        "msg": "年龄在3-8岁",
        "data": null
    }

    六、自定义注解校验
    提供一个场景,假如一个字段只能让用户传入特定的值,比如判断是否显示的属性isShow,只能取0和1,下面我们实现这一个功能

    6.1自定义注解@ListValue
    @Documented
    //该注解由哪个类校验
    @Constraint(validatedBy = {ListValue.ListValueConstraintValidator.class})
    @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RUNTIME)
    public @interface ListValue {

        String message() default "{com.thinkcoder.annotation.ListValue.message}";

        Class<?>[] groups() default {};

        Class<? extends Payload>[] payload() default { };

        int[] value();

        class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
            private Set<Integer> set=new HashSet<>();

            //获取value属性指定的数字,存入set集合
            @Override
            public void initialize(ListValue constraintAnnotation) {
                int[] value = constraintAnnotation.value();
                for (int i : value) {
                    set.add(i);
                }
            }

            //校验用户输入数据是否在set集合中
            //isValid第一个参数传入要校验属性的类型
            @Override
            public boolean isValid(Integer value, ConstraintValidatorContext context) {
                return  set.contains(value);
            }
        }
    }

    上面的代码需要说明以下几点

    校验类要实现ConstraintValidator<ListValue,Integer>接口;第一个泛型参数是自定义注解,第二个泛型参数是要校验的属性类型
    initalize方法:获取到用户使用自定义注解中的数据
    isValid方法:实现校验逻辑,结果是返回boolean类型
    @Constraint注解:将自定义注解和校验类联系起来
    6.2使用自定义注解
    @Data
    public class AnnotationQuery {

        @ListValue(value = {0,1},message = "数值只能是0或者1")
        private Integer isShow;
    }

    6.3请求方法
    @PostMapping("/add")
    public Result addAnnotation(@Validated @RequestBody AnnotationQuery query){
        return Result.buildSuccess(query);
    }

    6.4测试结果
    模拟数据
    {
        "isShow":-1
    }

    测试结果
    {
        "success": false,
        "code": 10003,
        "msg": "数值只能是0或者1",
        "data": {
            "isShow": "数值只能是0或者1"
        }
    }


    七、补充@PathVariable注解校验
    7.1@PathVariable作用
    用来对指定请求的URL路径里面的变量,比如@GetMapping("/get/{id}"),其中{id}就是这个请求的变量,可以通过@PathVariable来获取。

    和@RequestParam的区别是,@RequestParam用来获得静态的URL请求入参。

    7.2使用正则表达式校验路径变量
    下面正则表达式表示id值只能是数字,如果不是数字报出404路径找不到的异常

    @GetMapping("/get/{id:\\d+}")
    public Result getId(@PathVariable(name="id") String userId){
        return Result.buildSuccess(userId);
    }

    7.3自定义类实现ErrorController接口
    说下为什么要这么做?

    @ControllerAdive注解只能处理进入控制器方法抛出的异常,ErrorController接口可以处理全局异常,而404路径找不到异常不是控制器方法抛出的,此时还没有进入控制器方法。ErrorController处理404异常时会跳转到/error路径,此时会返回错误的html页。为了让返回结果统一,重写下ErrorController接口的getErrorPath方法

    @RestController
    public class MyErrorController extends BasicErrorController {

        @Autowired
        public MyErrorController(ErrorAttributes errorAttributes,
                                 ServerProperties serverProperties,
                                 List<ErrorViewResolver> errorViewResolvers) {
            super(errorAttributes, serverProperties.getError(), errorViewResolvers);
        }

        //处理html请求
        @Override
        public ModelAndView errorHtml(HttpServletRequest request,
                                      HttpServletResponse response) {
            HttpStatus status = getStatus(request);
            Map<String, Object> model = getErrorAttributes(
                    request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
            ModelAndView modelAndView = new ModelAndView("myErrorPage", model, status);
            return modelAndView;
        }

        //处理json请求
        @Override
        public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
            Map<String, Object> body = getErrorAttributes(request,
                    isIncludeStackTrace(request, MediaType.ALL));

            Map<String,Object> resultBody=new HashMap<>(16);
            resultBody.put("success",false);
            resultBody.put("code",body.get("status"));
            resultBody.put("msg",body.get("error"));

            return new ResponseEntity<>(resultBody, HttpStatus.OK);
        }
    }


    7.4测试结果
    实例:
    http://localhost:8082/get/aa

    结果:
    {
        "success": false,
        "code": 404,
        "msg": "请求接口不存在请检查路径",
    }

    八、导图总结
    通过以上叙述基本包含了常用开发用到的校验注解,下面用导图总结下

    参考博客

    https://www.jb51.net/article/115431.htm

    @Validated和@Valid区别

    两个注解的区别

    https://blog.csdn.net/gaojp008/article/details/80583301
     

    展开全文
  • 开发过程中,后台的参数校验是必不可少的,所以经常会看到类似下面这样的代码 这样写并没有什么错,还挺工整的,只是看起来不是很优雅而已。 那么有什么办法可以省去这么繁琐的工作呢? 当然,利用自定义注解和...

    开发过程中,后台的参数校验是必不可少的,所以经常会看到类似下面这样的代码

    这样写并没有什么错,还挺工整的,只是看起来不是很优雅而已。

    那么有什么办法可以省去这么繁琐的工作呢?

    当然,利用自定义注解和Spring AOP可以做到,参考我的另一篇博客:利用自定义注解和Aspect实现方法参数的非空校验

    但是,自己弄有点重复发明轮子的意思,因为spring已经提供了一套完整的validation,基本上就已经够用了。

    一、Spring MVC使用Validator做控制层参数校验

    1、在参数类的字段加上校验注解

    这里的注解有两种:一种是javax.validation.constraints包里面的,不知道是Spring提供的还是JDK提供的,没去研究;另一种在org.hibernate.validator.constraints包里面,Hibernate提供,需要引入hibernate-validator的jar包,不过已经标识过期了,提示我们用第一种替代。

    还有其它各种注解,限于篇幅此处不做介绍,大家可以自己去搜集。

    /**
     * 用户类
     * @author z_hh
     * @time 2019年1月18日
     */
    @Getter
    @Setter
    @ToString
    public class User {
    
    	/** id */
    	@NotNull(message="id不能为空")
    	private Long id;
    	
    	/** 姓名 */
    	@NotBlank(message="姓名不能为空")
    	private String name;
    	
    	/** 年龄 */
    	@Max(message="年龄不能超过120岁", value = 120)
    	@Min(message="年龄不能小于0岁", value = 0)
    	private Integer age;
    	
    	/** 创建时间 */
    	@Future
    	private Date createTime;
    }

    2、在Controller里需要检验的方法参数加上注解Valid,并定义BindingResult类型参数,然后方法体前面加上处理逻辑

    在执行时,Spring会将校验结果保存到bindingResult变量里,我们在代码里面判断处理就可以了。这里为了测试是将所有的错误信息返回。

            /**
    	 * 添加用户
    	 * @param user
    	 * @param bindingResult 校验结果收集器
    	 * @return
    	 */
    	@PostMapping
    	public Object add(@Valid @RequestBody User user, BindingResult bindingResult) {
    		if (bindingResult.hasErrors()) {
    			List<ObjectError> objectErrors = bindingResult.getAllErrors();
    			return objectErrors.stream()
    				.map(ObjectError::getDefaultMessage)
    				.reduce((msg1, msg2) -> msg1 + "/" + msg2)
    				.get();
    		}
    		return "添加用户成功!";
    	}

    3、测试一下

    使用MockMvc测试API

     

    可以看到,将不通过的校验的信息打印出来了 

    这样,就算是解决了参数校验的问题,一切似乎很完美... ...

    但是,如果我们每个需要校验的方法代码里面都这样写,是否觉得不太妥?

    我觉得有两个问题,第一是代码重复了,第二是跟业务逻辑耦合了。

    那有什么方法解决呢?

    或许可以将这段代码封装到一个函数,放到一个公共类(或者父类)里面。但是,还是要在每个方法体里面写代码,只是少了一些而已,不算好的方法。

    直接敲黑板划重点吧

    二、省略方法体内处理校验结果代码的几种方式

    第一种,也是我觉得最简单的,就是使用Spring的Aspect。直接上代码

    /**
     * Validato的切面
     * @author z_hh  
     * @date 2019年1月18日
     */
    @Component
    @Aspect
    public class ValidatorAspect {
    	
    	/**
    	 * 这里可以具体到包含BindingResult参数的Controller API方法???
    	 */
        @Pointcut("execution(public * cn.zhh.controller.*.*(..))")
        private void annotationPointCut() {
        }
     
        /**
    	 * 如果@Pointcut匹配到的全部都是需要校验的方法,那么可以省略很多逻辑
    	 */
        @Around("annotationPointCut()")
        public Object process(ProceedingJoinPoint pjp) throws Throwable {
        	// 1、获取目标方法
        	Signature signature = pjp.getSignature();
    		MethodSignature methodSignature = (MethodSignature)signature;
    		Method targetMethod = methodSignature.getMethod();
    		// 2、获取方法参数
    		Parameter[] parameters = targetMethod.getParameters();
    		// 3、遍历参数。如果有BindingResult参数,就判断是否校验不通过
    		for (int i = 0; i < parameters.length; i++) {
    			Parameter parameter = parameters[i];
    			if (Objects.equals(parameter.getType(), BindingResult.class)) {
    				BindingResult bindingResult = (BindingResult) pjp.getArgs()[i];
    				if (bindingResult.hasErrors()) {
    					List<ObjectError> objectErrors = bindingResult.getAllErrors();
    					return objectErrors.stream()
    						.map(ObjectError::getDefaultMessage)
    						.reduce((msg1, msg2) -> msg1 + "/" + msg2)
    						.get();
    				}
    			}
    		}
    	    // finish、执行目标方法
    	    return pjp.proceed();
        }
     
    }

    需要引入AOP的相关依赖。如SpringBoot需要引入

                <dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-aop</artifactId>
    		</dependency>

    第二种,在控制类实例化之后注入Spring容器之前修改bean。也就是创建一个实现BeanPostProcessor接口的bean,重写postProcessBeforeInitialization方法。而这里的修改bean,又可以分为两种方式

    (1)创建代理对象替换原有对象

    动态代理的相关博客:https://blog.csdn.net/qq_31142553/article/details/81489678

    代理对象执行的时候判断方法参数是否有BindingResult类型的参数,有就处理。跟第一种类似,此处不再赘述。值得注意的是,因为控制类不一定实现接口,所以最好使用Cglib创建代理对象。

    (2)使用Javassist修改字节码,创建新的类及其对象替换原有对象。这里重点讲解一下

    Javassist的相关博客:https://blog.csdn.net/qq_31142553/article/details/85395997

    首先需要引入jar包依赖

            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.24.0-GA</version>
            </dependency>

    接着需要定义一个类级别注解,表明此类的bean需要参数校验(非必需,也可以根据包名、父类什么的)。这里定义的注解混合了@RestController,让使用起来方便一点,不用写两个。

    /**
     * 表明该类是控制器,并且需要方法参数校验
     * @author z_hh
     * @time 2019年1月18日
     */
    @Retention(RUNTIME)
    @Target(TYPE)
    @Documented
    @RestController
    public @interface RestValidatorController {
    
    	@AliasFor(annotation = RestController.class)
    	String value() default "";
    }

    最后,就是写bean注入容器前修改字节码创建新对象进行替换的功能了。(这里是重难点)

    /**
     * 对存在RestValidatorController注解的bean修改字节码
     * @author z_hh
     * @time 2019年1月18日
     */
    @Component
    public class AddValidatorCodeProcessor implements BeanPostProcessor {
    	
    	private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    	
    	/**
    	 * 处理参数校验结果的代码,非java.lang包的类需要写全类名。部分代码如foreach、lambda等会编译不通过...
    	 */
    	private static final String CODE = "if (bindingResult.hasErrors()) {\r\n" + 
    			"			java.util.List objectErrors = bindingResult.getAllErrors();\r\n" + 
    			"			StringBuffer sb = new StringBuffer();\r\n" + 
    			"			for (int i = 0; i < objectErrors.size(); i++) {\r\n" + 
    			"				String msg = ((org.springframework.validation.ObjectError) objectErrors.get(i)).getDefaultMessage();\r\n" + 
    			"				sb.append(\"/\" + msg);\r\n" + 
    			"			}\r\n" + 
    			"			return sb.substring(1);\r\n" + 
    			"		}";
    	
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		Class<?> clazz = bean.getClass();
    		// 只处理带RestValidatorController注解的类
    		if (clazz.isAnnotationPresent(RestValidatorController.class)) {
    			ClassPool pool = ClassPool.getDefault();
    			String oldName = clazz.getName();
    			try {
    				CtClass ctClass = pool.getCtClass(clazz.getName());
    				// 类名需要改
    				ctClass.setName(oldName + "$javassist");
    				CtMethod[] ctMethods = ctClass.getMethods();
    				// 对存在BindingResult类型参数的方法进行处理:在方法体前面插入代码
    				for (CtMethod ctMethod : ctMethods) {
    					CtClass[] parameterTypes = ctMethod.getParameterTypes();
    					boolean anyMatch = Arrays.stream(parameterTypes)
    						.anyMatch(parameterType -> Objects.equals(parameterType.getSimpleName(), "BindingResult"));
    					if (anyMatch) {
    						ctMethod.insertBefore(CODE);
    					}
    				}
    				// 生成新的对象替代原有对象
    				Class<?> newClazz = ctClass.toClass();
    				return newClazz.newInstance();
    				
    			} catch (Throwable t) {
    				LOGGER.error("修改类{}的字节码异常", bean.getClass().getName(), t);
    			}
    		}
    		// 其它对象不做处理
    		return bean;
    	}
    	
    }

    第三种,使用lombok进行自定义扩展。它可以在编译阶段对字节码进行修改

    lombok挺好用的,我在上面的User类已经使用它来省略了getter和setter方法。通过自定义扩展,可以让类编译时添加一些代码。

    需要引入相关jar包依赖

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>

    具体怎么操作,我还没研究过。反正我知道它是可以扩展的... ...

    文章介绍完了。可能大家会觉得,用第一种Spring的那个Aspect就可以了,后面的搞那么复杂没什么用。哈哈,确实也是,不过是为了学习嘛~

    完整项目代码已上传,点击下载

    展开全文
  • 什么是JSR-303 JSRJava Specification Requests的缩写,意思是Java规范提案,指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务,JSR...
  • 1、什么是Bean Validation JSR-303(JSRJava Specification Requests的缩写,意思是Java 规范提案) JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator Bean Validation 的参考实现。...

    1、什么是Bean Validation

    JSR-303(JSR是Java Specification Requests的缩写,意思是Java 规范提案) 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现。Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

    Bean Validate规定的Constraint
    meta-data comment version
    @Null 对象,为空 Bean Validation 1.0
    @NotNull 对象,不为空 Bean Validation 1.0
    @AssertTrue 布尔,为True Bean Validation 1.0
    @AssertFalse 布尔,为False Bean Validation 1.0
    @Min(value) 数字,最小为value Bean Validation 1.0
    @Max(value) 数字,最大为value Bean Validation 1.0
    @DecimalMin(value) 数字,最小为value Bean Validation 1.0
    @DecimalMax(value) 数字,最大为value Bean Validation 1.0
    @Size(max, min) min<=value<=max Bean Validation 1.0
    @Digits (integer, fraction) 数字,某个范围内 Bean Validation 1.0
    @Past 日期,过去的日期 Bean Validation 1.0
    @Future 日期,将来的日期 Bean Validation 1.0
    @Pattern(value) 字符串,正则校验 Bean Validation 1.0
    @Email 字符串,邮箱类型 Bean Validation 2.0
    @NotEmpty 集合,不为空 Bean Validation 2.0
    @NotBlank 字符串,不为空字符串 Bean Validation 2.0
    @Positive 数字,正数 Bean Validation 2.0
    @PositiveOrZero 数字,正数或0 Bean Validation 2.0
    @Negative 数字,负数 Bean Validation 2.0
    @NegativeOrZero 数字,负数或0 Bean Validation 2.0
    @PastOrPresent 过去或者现在 Bean Validation 2.0
    @FutureOrPresent 将来或者现在 Bean Validation 2.0

    2、使用Hibernate Validator编程式校验

    2.1、简单的实体对象校验

    maven依赖:

    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>2.0.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.1.Final</version>
    </dependency>

    一个简单的JavaBean:

    public class User {
        private String name;
        private String gender;
        @Positive
        private int age;
        private List<@Email String> emails;
    
        // getter and setter 
        // ...
    }
    

    使用Validator校验:

    User user = new User();
    user.setName("seven");
    user.setGender("man");
    user.setAge(-1);
    user.setEmails(Arrays.asList("sevenlin@gmail.com", "sevenlin.com"));
    
    Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(user);
    
    List<String> message
        = result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
            .collect(Collectors.toList());
    
    message.forEach(System.out::println);
    

    校验结果:

    emails[1].<list element> must be a well-formed email address: sevenlin.com
    age must be greater than 0: -1

    代码讲解:

    Validation类是Bean Validation的入口点,buildDefaultValidatorFactory()方法基于默认的Bean Validation提供程序构建并返回ValidatorFactory实例。使用默认验证提供程序解析程序逻辑解析提供程序列表。代码上等同于Validation.byDefaultProvider().configure().buildValidatorFactory()。

    以上代码根据java spi查找ValidationProvider的实现类,如果类路径加入了hibernate-validator,则使用HibernateValidator,关于HibernateValidator细节暂不探讨。

    之后调用该ValidatorFactory.getValidator()返回一个校验器实例,使用这个校验器的validate方法对目标对象的属性进行校验,返回一个ConstraintViolation集合。ConstraintViolation用于描述约束违规。 此对象公开约束违规上下文以及描述违规的消息。

    2.2、分组校验

    如果同一个类,在不同的使用场景下有不同的校验规则,那么可以使用分组校验。

    首先需要在constraint注解上指定groups属性,这个属性是一个class对象数组,再调用javax.validation.Validator接口的validate方法的时候将第二个参数groups传入class数组元素之一就可以针对这个这个group的校验规则生效。

    public class User {
        private String name;
        private String gender;
    
        @Positive
        @Min(value = 18,groups = {Adult.class})
        private int age;
        private List<@Email String> emails;
    
        // getter and setter 
        // ...
        
        // 一个分组标记
        public interface Adult{}
    }

    这样当Validation.buildDefaultValidatorFactory().getValidator().validate(user,User.Adult.class);只会对groups=Adult.class的constraint注解生效。

    2.3、自定义校验

    业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求。

    2.3.1、编写自定义校验注解 

    我们尝试添加一个“字符串不能包含空格”的限制。

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = {CannotHaveBlankValidator.class})<1>
    public @interface CannotHaveBlank {
    
        //默认错误消息
        String message() default "不能包含空格";
    
        //分组
        Class<?>[] groups() default {};
    
        //负载
        Class<? extends Payload>[] payload() default {};
    
        //指定多个时使用
        @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
        @interface List {
            CannotHaveBlank[] value();
        }
    
    }

    我们不需要关注太多东西,例如payload,List ,groups,都可以忽略。

    <1> 自定义注解中指定了这个注解真正的验证者类。

    2.3.2、编写真正的校验者类

    public class CannotHaveBlankValidator implements <1> ConstraintValidator<CannotHaveBlank, String> {
    
        @Override
        public void initialize(CannotHaveBlank constraintAnnotation) {
        }
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context <2>) {
            //null时不进行校验
            if (value != null && value.contains(" ")) {
                <3>
                //获取默认提示信息
                String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
                System.out.println("default message :" + defaultConstraintMessageTemplate);
                //禁用默认提示信息
                context.disableDefaultConstraintViolation();
                //设置提示语
                context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
                return false;
            }
            return true;
        }
    }

    <1> 所有的验证者都需要实现ConstraintValidator接口,它的接口也很形象,包含一个初始化事件方法,和一个判断是否合法的方法。

    public interface ConstraintValidator<A extends Annotation, T> {
    
        void initialize(A constraintAnnotation);
    
        boolean isValid(T value, ConstraintValidatorContext context);
    }

    <2> ConstraintValidatorContext 这个上下文包含了认证中所有的信息,我们可以利用这个上下文实现获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作。

    <3> 一些典型校验操作,或许可以对你产生启示作用。

    值得注意的一点是,自定义注解可以用在METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER之上,ConstraintValidator的第二个泛型参数T,是需要被校验的类型。

    3、Spring Validation

    Spring框架为开发者提供了也提供了Validator接口,可用于验证对象。注意Spring Validation的Validator与Bean Validation 的Validator接口是两个接口。Spring的Validator通过使用Errors对象来工作,以便在验证时,验证器可以向Errors对象报告验证失败。

    Spring 3开始为其验证支持引入了几项增强功能。 首先,完全支持JSR-303 Bean Validation API。 其次,当以编程方式使用时,Spring的DataBinder可以验证对象以及绑定它们。 第三,Spring MVC支持声明性地验证@Controller输入。

    3.1、Validator接口

    该接口完全脱离任何基础设施或上下文; 也就是说,它不会仅仅验证Web层,数据访问层或任何层中的对象。 因此,它适用于应用程序的任何层。

    public interface Validator {
    
       //此Validator可以验证提供的clazz的实例吗?
       boolean supports(Class<?> clazz);
    
       //验证提供的目标对象,该对象必须是supports(Class)方法返回true的。
       //提供的错误实例可用于报告任何结果验证错误。
       void validate(Object target, Errors errors);
    
    }

    3.2、使用LocalValidatorFactoryBean编程式校验

    Spring对validation全面支持JSR-303、JSR-349的标准,并且封装了LocalValidatorFactoryBean作为validator的实现。这个类同时实现了Spring Validation和Bean Validation的Validator接口,代替上述的从工厂方法中获取的hibernate validator。

    public static class ValidPerson {
    
       @NotNull
       private String name;
    
       @Valid
       private ValidAddress address = new ValidAddress();
    
       @Valid
       private List<ValidAddress> addressList = new LinkedList<>();
    
       @Valid
       private Set<ValidAddress> addressSet = new LinkedHashSet<>();
    }
    
    public static class ValidAddress {
    
       @NotNull
       private String street;
    }

    @javax.validation.Valid标记用于验证级联的属性,方法参数或方法返回类型。在验证属性,方法参数或方法返回类型时,将验证在对象及其属性上定义的约束。此行为以递归方式应用。 

    @Test
    public void testSimpleValidation() {
       LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
       validator.afterPropertiesSet();
    
       ValidPerson person = new ValidPerson();
       //这里使用的是Bean Validation而不是Spring Validation的接口
       Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
       assertEquals(2, result.size());
       for (ConstraintViolation<ValidPerson> cv : result) {
          String path = cv.getPropertyPath().toString();
          if ("name".equals(path) || "address.street".equals(path)) {
             assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull);
          }
          else {
             fail("Invalid constraint violation with path '" + path + "'");
          }
       }
    }

    3.3、LocalValidatorFactoryBean源码分析

    3.3.1、类结构图

    3.3.2、JSR-303 Validator转化Spring Validator

    SpringValidatorAdapter获取JSR-303 javax.validator.Validator并将其公开为Spring org.springframework.validation.Validator同时还公开原始JSR-303 Validator接口本身的适配器。

    在SpringValidatorAdapter构造方法可以传入一个javax.validation.Validator赋值给变量targetValidator,当实现javax.validator.Validator接口方法时直接调用targetValidator的同名方法,而实现org.springframework.validation.Validator接口方法也会通过targetValidator对象间接实现功能,如下方法:

    @Override
    public void validate(Object target, Errors errors) {
       if (this.targetValidator != null) {
          processConstraintViolations(this.targetValidator.validate(target), errors);
       }
    }

    processConstraintViolations方法处理给定的JSR-303 ConstraintViolations,向提供的Spring Errors对象添加相应的错误。

    protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
       for (ConstraintViolation<Object> violation : violations) {
          String field = determineField(violation);
          FieldError fieldError = errors.getFieldError(field);
          if (fieldError == null || !fieldError.isBindingFailure()) {
             try {
                ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
                String errorCode = determineErrorCode(cd);
                Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd);
                if (errors instanceof BindingResult) {
                   // Can do custom FieldError registration with invalid value from ConstraintViolation,
                   // as necessary for Hibernate Validator compatibility (non-indexed set path in field)
                   BindingResult bindingResult = (BindingResult) errors;
                   String nestedField = bindingResult.getNestedPath() + field;
                   if ("".equals(nestedField)) {
                      String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
                      bindingResult.addError(new ObjectError(
                            errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()));
                   }
                   else {
                      Object rejectedValue = getRejectedValue(field, violation, bindingResult);
                      String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
                      bindingResult.addError(new FieldError(
                            errors.getObjectName(), nestedField, rejectedValue, false,
                            errorCodes, errorArgs, violation.getMessage()));
                   }
                }
                else {
                   // got no BindingResult - can only do standard rejectValue call
                   // with automatic extraction of the current field value
                   errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
                }
             }
             catch (NotReadablePropertyException ex) {
                throw new IllegalStateException("JSR-303 validated property '" + field +
                      "' does not have a corresponding accessor for Spring data binding - " +
                      "check your DataBinder's configuration (bean property versus direct field access)", ex);
             }
          }
       }
    }

    3.3.3、LocalValidatorFactoryBean

    虽然LocalValidatorFactoryBean继承了SpringValidatorAdapter,但是没有继承它带参的构造方法,默认targetValidator变量为空,对targetValidator变量赋值的操作放在了afterPropertiesSet()方法中。

    上图是afterPropertiesSet()方法最后三行,上面都是对configuration的配置。

    3.4、Spring MVC使用Validator

    默认情况下,如果类路径上存在Bean Validation(例如,Hibernate Validator),则LocalValidatorFactoryBean将注册为全局Validator,以便与控制器方法参数一起使用@Valid和@Validated。

    3.4.1、校验参数实体

    创建需要被校验的实体类

    public class Foo {
    
        @NotBlank
        private String name;
    
        @Min(18)
        private Integer age;
    
        @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
        @NotBlank(message = "手机号码不能为空")
        private String phone;
    
        @Email(message = "邮箱格式错误")
        private String email;
    
        //... getter setter
    
    }

    在@Controller中校验数据
    springmvc为我们提供了自动封装表单参数的功能,一个添加了参数校验的典型controller如下所示。

    @Controller
    public class FooController {
    
        @RequestMapping("/foo")
        public String foo(@Validated Foo foo <1>, BindingResult bindingResult <2>) {
            if(bindingResult.hasErrors()){
                for (FieldError fieldError : bindingResult.getFieldErrors()) {
                    //...
                }
                return "fail";
            }
            return "success";
        }
    
    }

    值得注意的地方:

    <1> 参数Foo前需要加上@Validated注解,表明需要spring对其进行校验,而校验的信息会存放到其后的BindingResult中。注意,必须相邻,如果有多个参数需要校验,形式可以如下。foo(@Validated Foo foo, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult);即一个校验类对应一个校验结果。

    <2> 校验结果会被自动填充,在controller中可以根据业务逻辑来决定具体的操作,如跳转到错误页面。

    3.4.2、基于方法校验

    @RestController
    @Validated <1>
    public class BarController {
    
        @RequestMapping("/bar")
        public @NotBlank <2> String bar(@Min(18) Integer age <3>) {
            System.out.println("age : " + age);
            return "";
        }
    
        @ExceptionHandler(ConstraintViolationException.class)
        public Map handleConstraintViolationException(ConstraintViolationException cve){
            Set<ConstraintViolation<?>> cves = cve.getConstraintViolations();<4>
            for (ConstraintViolation<?> constraintViolation : cves) {
                System.out.println(constraintViolation.getMessage());
            }
            Map map = new HashMap();
            map.put("errorCode",500);
            return map;
        }
    
    }

    <1> 为类添加@Validated注解

    <2> <3> 校验方法的返回值和入参

    <4> 添加一个异常处理器,可以获得没有通过校验的属性相关信息

    3.4.3、分组校验

    如果同一个类,在不同的使用场景下有不同的校验规则,那么可以使用分组校验。未成年人是不能喝酒的,而在其他场景下我们不做特殊的限制,这个需求如何体现同一个实体,不同的校验规则呢?

    改写注解,添加分组:

    Class Foo{
    
        @Min(value = 18,groups = {Adult.class})
        private Integer age;
    
        public interface Adult{}
    
        public interface Minor{}
    }

    这样表明,只有在Adult分组下,18岁的限制才会起作用。

    Controller层改写:

    @RequestMapping("/drink")
    public String drink(@Validated({Foo.Adult.class}) Foo foo, BindingResult bindingResult) {
        if(bindingResult.hasErrors()){
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                //...
            }
            return "fail";
        }
        return "success";
    }
    
    @RequestMapping("/live")
    public String live(@Validated Foo foo, BindingResult bindingResult) {
        if(bindingResult.hasErrors()){
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                //...
            }
            return "fail";
        }
        return "success";
    }

    drink方法限定需要进行Adult校验,而live方法则不做限制。

    3.5、Spring MVC注解原理解析

    《Spring MVC参数值的绑定》中分析过,Spring MVC @Controller方法参数会使用HandlerMethodArgumentResolver进行HttpServletRequest参数到实体类的装换也就是参数绑定的过程,其中有一个HandlerMethodArgumentResolver就是ModelAttributeMethodProcessor,解析@ModelAttribute带注释的方法参数,并处理来自@ModelAttribute注释方法的返回值。创建后,通过数据绑定到Servlet请求参数来填充属性。 如果参数使用@javax.validation.Valid或Spring自己的@org.springframework.validation.annotation.Validated注释,则可以应用验证。

    从这段文档描述可以知道ModelAttributeMethodProcessor除了可以完成Servlet请求参数填充实体类对象的属性,如果实体对象被@javax.validation.Valid或@org.springframework.validation.annotation.Validated注释,还会对其进行数据校验。

    3.5.1、校验原理

    什么类型的参数会校验@Validated?来看ModelAttributeMethodProcessor的supportsParameter方法就知道了。

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
       return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
             (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }

    annotationNotRequired是ModelAttributeMethodProcessor的构造参数,在RequestMappingHandlerAdapter中annotationNotRequired参数是true和false的ModelAttributeMethodProcessor(ServletModelAttributeMethodProcessor继承于ModelAttributeMethodProcessor)都有。

    我们看ModelAttributeMethodProcessor的resolveArgument方法。此方法中一部分是参数绑定的过程不是我们分析的重点,下面看参数校验的部分。

    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    if (binder.getTarget() != null) {
       if (!mavContainer.isBindingDisabled(name)) {
          bindRequestParameters(binder, webRequest);
       }
       validateIfApplicable(binder, parameter);
       if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
          throw new BindException(binder.getBindingResult());
       }
    }
    // Value type adaptation, also covering java.util.Optional
    if (!parameter.getParameterType().isInstance(attribute)) {
       attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }
    bindingResult = binder.getBindingResult();

    上述代码首先通过binderFactory创建一个WebDataBinder对象将webRequest的参数值绑定到attribute上,然后调用validateIfApplicable方法,参数校验的过程就发生在这个方法中。

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
       for (Annotation ann : parameter.getParameterAnnotations()) {
          //检查@javax.validation.Valid,Spring的@Validated以及名称以“Valid”开头的自定义注释。
          Object[] validationHints = determineValidationHints(ann);
          if (validationHints != null) {
             //这里使用DataBinder完成属性校验
             binder.validate(validationHints);
             break;
          }
       }
    }
    @Nullable
    private Object[] determineValidationHints(Annotation ann) {
       Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
       if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
          Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
          if (hints == null) {
             return new Object[0];
          }
          return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
       }
       return null;
    }

    我们可以看到validateIfApplicable方法内部使用DataBinder的validate方法完成校验。

    public void validate() {
       Object target = getTarget();
       Assert.state(target != null, "No target to validate");
       BindingResult bindingResult = getBindingResult();
       // Call each validator with the same binding result
       for (Validator validator : getValidators()) {
          validator.validate(target, bindingResult);
       }
    }

    这个方法调用getValidators()获取DataBinder内所有的Validator对实体参数进行校验,校验错误信息存放到BindingResult中。

    3.5.2、Validator的注册

    那么下面有一个疑问,DataBinder的Validator是何时被注入的。在《Spring MVC设计原理》介绍过,RequestMappingHandlerAdapter处理请求时构建创建DataBinder的ServletRequestDataBinderFactory对象的构造参数传入了一个WebBindingInitializer对象,在创建DataBinder的时候会使用WebBindingInitializer来初始化这个DataBinder。

    下面是WebBindingInitializer接口的实现ConfigurableWebBindingInitializer,使用自身持有的Validator设置到DataBinder上。

    那么上面的问题就转移到了Spring MVC何时实例化的WebBindingInitializer?

    《Spring MVC设计原理》介绍过,在开启注解@EnableWebMvc后,为Spring容器注册了一个bean——DelegatingWebMvcConfiguration,它继承于WebMvcConfigurationSupport,内部定义了一个@Bean方法来向Spring注册RequestMappingHandlerAdapter。

    getConfigurableWebBindingInitializer方法就是创建上面说的ConfigurableWebBindingInitializer对象的。

    mvcValidator()方法返回一个Validator实例,这个实例是OptionalValidatorFactoryBean。

    OptionalValidatorFactoryBean继承于LocalValidatorFactoryBean。

     

    参考

    https://www.jianshu.com/p/2a495bf5504e

    https://blog.csdn.net/u013815546/article/details/77248003

    展开全文
  • 一、什么是JSR? JSR是Java Specification Requests的缩写,意思是Java 规范提案。...二、JSR-303定义的是什么标准? JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Vali

    一、什么是JSR?

    JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

    二、JSR-303定义的是什么标准?

    JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

    三、Bean Validation中内置的constraint

    1. @Null:被注释的元素必须为null
    2. @NotNull:被注释的元素必须不为null
    3. @AssertTrue:被注释的元素必须为true
    4. @AssertFalse:被注释的元素必须为false
    5. @Min:被注释的元素必须是一个数字,且必须大于等于指定最小值
    6. @Max:被注释的元素必须是一个数字,且必须小于等于指定最小值
    7. @DecimalMin(value):被注释的元素必须是一个数字,且必须大于等于指定最小值
    8. @DecimalMax(value):被注释的元素必须是一个数字,且必须小于等于指定最小值
    9. @Size(min,max):被注释元素大小必须在指定的范围内
    10. @Digits(integer,fraction):被注释元素必须是数字,且值必须在可接受范围内
    11. @Past:被注释的元素必须是一个过去的日期
    12. @Future:被注释的元素必须是一个将来的日期
    13. @Pattern(value):被注释的元素必须符合指定的正则表达式

    Hibernate Validator附加的constraint:

    1. @Email:被注释的元素必须是电子邮箱地址
    2. @Length:被注释的字符串长度必须在指定的范围内
    3. @NotEmpty:被注释的字符串必须非空
    4. @Range:被注释的元素必须在合适的范围内

    四、Swagger支持

    目前,Swagger共支持以下几个注解:

    • @NotNull
    • @Max
    • @Min
    • @Size
    • @Pattern
    展开全文
  • 什么是JSR-303 JSRJava Specification Requests的缩写,意思是Java规范提案,指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务,...
  • 显示器亮度,清晰度,色调,对比度,伽马值是什么意思?下面的内容主要在于让朋友们清楚,这些概念的含义,实际上一般情况下,显示器出现显示上的问题,大多与显卡有关,除非显示器出现硬件问题,最好的办法在于更新显卡...
  • 类型`的形式指定函数的**返回值类型然后特别要强调的,Python 解释器并不会因为这些注解而提供额外的校验,没有任何的类型检查工作。也就是说,这些类型注解加不加,对你的代码来说没有任何影响。但这么做的好处...
  • 展开全部ZipEntry 用于保存一些被压缩文件的信息,32313133353236313431303231363533e78988e69d...ZipEntry 具有一个带 String 类型参数的构造方法:ZipEntry(String name), name 入口名称,就是打开压缩文...
  • sum返回str中字符一个基本的n位校验和,这里n可选的参数,默认16。对str中的每一个字符的二进制值简单求和,然后用2的n次方减1求模,得到这个校验和。 哪位前辈能作出详细的解答,谢谢了!
  • SAP OData的CSRF校验开关

    2019-08-22 10:11:25
    布尔变量mv_check_csrf_token控制服务器是否进行校验,默认值为true,意思是要检查。 那什么时候不检查呢? cl_icf_its_service=>params_string_to_table返回的参数列表里,如果co_xsrf_check的值为0,代表不...
  • PHP 中 in_array 的隐式转换问题今天在写一个接口的时候, 需要传入大量的基本信息参数, 参数分别 int 和 string 两种类型, 为了校验方便, 我打算把所有的参数都放在数组中, 然后用 in_array(0, $param) 判断 int ...
  • 组件参数校验都有哪些属性设置?分别表示什么意思? 什么props特性?何为非props特性?如何判断? 重要代码如下: 父组件通过属性content向子组件chhild进行参数hello world的传递, 而子组件可以对父...
  • 还有就是0xFF和0x25是什么意思,我原来以为是16进制的数,但是发现转换成10进制 不同于0-9,请问这到底是什么意思呢 最后一点是配置参数里面讲校验位为无,但是在命令格式中却有校验值,这又是怎么回事呢
  • JSR-303学习

    2020-12-31 09:26:54
    JSR-303请求参数校验 什么是JSR? JSRJava Specification Requests的缩写,意思是Java 规范提案。指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台...
  • 当我们用spring 拦截器实现权限校验的时候,往往我们需要从request中获取一些数据进行校验,但是当我们在拦截器获取到数据,getinputStream,那么在后续的action即:controller中我们获取不到request,这什么呢?...
  • 微信接入笔记记录

    2018-01-05 14:39:00
    这两步是什么意思呢,就是微信要验证你的服务器地址是否正确,所以会向你你的服务器请求一次,你得根据微信要求的规则,返回相应的数据 微信的规则如下 开发者通过检验signature对请求进行校验(下面有校验方式...
  • GD32实战6__串口读写

    千次阅读 2019-06-13 17:32:21
    目的 ​ 串口通信非常非常常见的一种通信方式,必须掌握的。可以从如下几个方面掌握串口通信: 串口通信原理,此处我们只研究异步串口 GD32常见的几种串口通信...那么配置的这些参数分别代表什么意思呢? 串口...
  • spring学习日记(一)

    2019-03-27 17:38:27
    2.使用@BindingResult来接收校验的结果,如果有不合法的参数,从中取出结果信息,然后返回给前端页面 3.什么是token token的意思是“令牌”,服务端生成的一串字符串,作为客户端进行请求的一个标识。 当用户第一...
  • # 1. 问题提出 看spring security官方...意思是原本只处理json数据的接口,增加校验后,将会拒绝处理其他数据类型么?但是如果接口传递的数据不是json类型的,在参数解析的时候也会报错啊?增加这个校验的必要性
  • java面试宝典

    2013-02-28 16:04:01
    47、在java 中一个类被声明为final 类型,表示了什么意思? 12 48、下面哪些类可以被继承? 12 49、指出下面程序的运行结果: 【基础】 13 52、关于内部类: 13 53、数据类型之间的转换: 14 54、字符串操作:如何实现...
  • 千方百计笔试题大全

    2011-11-30 21:58:33
    47、在java 中一个类被声明为final 类型,表示了什么意思? 12 48、下面哪些类可以被继承? 12 49、指出下面程序的运行结果: 【基础】 13 52、关于内部类: 13 53、数据类型之间的转换: 14 54、字符串操作:如何实现...
  • 具体什么意思呢,可以举个栗子看一下tjpcms动态如何配置增删改查 下载下来的tjpcms怎么用? 下载下来的压缩包工程源码及数据库文件,将源码导入到MyEclipse中,数据库导入到Mysql中,再配置一下工程中数据...
  • 什么是Linux?.................................................................................................................2 二.安装Linux的好处?.....................................................
  • 3. 得出的旋转向量和平移向量是什么意思?为什么我得不到旋转向量和平移向量,这两个参数带NULL是没有问题的 cvCalibrateCamera2(object_points2, image_points2, point_counts2, cvGetSize(image), intrinsic_...
  • costas_loop代码

    2012-10-08 14:40:33
    至少不会有“环路滤波器参数设置没什么意思.阻尼0.707.噪声带宽25”。 % 楼上两位说得很有道理,确实,针对不同的应用场合,环路的参数设置,滤波器的设计,鉴相方式,都会有所变化. % 我发这篇帖的目的,就是希望...
  • 27、GC是什么? 为什么要有GC?  GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象...
  • 是什么意思? 我该做什么? E.6.11 在安装时, Linux无法工作在我的Ultra DMA IDE驱动器和主机板上, 怎么办? E.6.12 我的机器有一个PCI Ultra DMA 控制器. 我可以安装Linux吗? E.6.13 我有NT, 并想安装Linux, 但...

空空如也

空空如也

1 2 3
收藏数 54
精华内容 21
关键字:

参数校验是什么意思