• Java SpringBoot VIII


    Java SpringBoot VIII

    1. Validation框架

    当客户端向服务器提交请求时,如果请求数据出现明显的问题(例如关键数据为null、字符串的长度不在可接受范围内、其它格式错误),应该直接响应错误,而不是将明显错误的请求参数传递到Service!

    关于判断错误,只有涉及数据库中的数据才能判断出结果的,都由Service进行判断,而基本的格式判断,都由Controller进行判断。

    Validation框架是专门用于解决检查数据基本格式有效性的,最早并不是Spring系列的框架,目前,Spring Boot提供了更好的支持,所以,通常结合在一起使用。

    在Spring Boot项目中,需要添加spring-boot-starter-validation依赖项,例如:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-validationartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    在控制器中,首先,对需要检查数据格式的请求参数添加@Valid@Validated注解(这2个注解没有区别),例如:

    @RequestMapping("/add-new")
    public JsonResult<Void> addNew(@Validated AdminAddNewDTO adminAddNewDTO) {
        adminService.addNew(adminAddNewDTO);
        return JsonResult.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    真正需要检查的是AdminAddNewDTO中各属性的值,所以,接下来需要在此类的各属性上通过注解来配置检查的规则,例如:

    @Data
    public class AdminAddNewDTO implements Serializable {
    
        @NotNull // 验证规则为:不允许为null
        private String username;
        
        // ===== 原有其它代码 =====
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    重启项目,通过不提交用户名的URL(例如:http://localhost:8080/admins/add-new)进行访问,在浏览器上会出现400错误页面,并且,在IntelliJ IDEA的控制台会出现以下警告:

    2022-06-07 11:37:53.424  WARN 6404 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [
    org.springframework.validation.BindException: 
    org.springframework.validation.BeanPropertyBindingResult: 1 errorsField error in object 'adminAddNewDTO' on field 'username': rejected value [null]; codes [NotNull.adminAddNewDTO.username,NotNull.username,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [adminAddNewDTO.username,username]; arguments []; default message [username]]; default message [不能为null]]
    
    • 1
    • 2
    • 3

    从警告信息中可以看到,当验证失败时(不符合所使用的注解对应的规则时),会出现org.springframework.validation.BindException异常,则自行处理此异常即可!

    如果有多个属性需要验证,则多个属性都需要添加注解,例如:

    @Data
    public class AdminAddNewDTO implements Serializable {
    
        @NotNull
        private String username;
    
        @NotNull
        private String password;
        
        // ===== 原有其它代码 =====
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先,在State中添加新的枚举:

    public enum State {
    
        OK(200),
        ERR_USERNAME(201),
        ERR_PASSWORD(202),
        ERR_BAD_REQUEST(400), // 新增
        ERR_INSERT(500);
     
        // ===== 原有其它代码 =====
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后,在GlobalExceptionHandler中添加新的处理异常的方法:

    @ExceptionHandler(BindException.class)
    public JsonResult<Void> handleBindException(BindException e) {
        return JsonResult.fail(State.ERR_BAD_REQUEST, e.getMessage());
    }
    
    • 1
    • 2
    • 3
    • 4

    完成后,再次重启项目,继续使用为null的用户名提交请求时,可以看到异常已经被处理,此时,响应的JSON数据例如:

    {
        "state":400,
        "message":"org.springframework.validation.BeanPropertyBindingResult: 2 errors\nField error in object 'adminAddNewDTO' on field 'username': rejected value [null]; codes [NotNull.adminAddNewDTO.username,NotNull.username,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [adminAddNewDTO.username,username]; arguments []; default message [username]]; default message [不能为null]\nField error in object 'adminAddNewDTO' on field 'password': rejected value [null]; codes [NotNull.adminAddNewDTO.password,NotNull.password,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [adminAddNewDTO.password,password]; arguments []; default message [password]]; default message [不能为null]"
    }
    
    • 1
    • 2
    • 3
    • 4

    关于错误提示信息,以上内容中出现了不能为null的字样,是默认的提示文本,可以通过@NotNull注解的message属性进行配置,例如:

    @Data
    public class AdminAddNewDTO implements Serializable {
    
        @NotNull(message = "添加管理员失败,请提交用户名!")
        private String username;
    
        @NotNull(message = "添加管理员失败,请提交密码!")
        private String password;
        
        // ===== 原有其它代码 =====
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后,在处理异常时,通过异常信息获取自定义的提示文本:

    @ExceptionHandler(BindException.class)
    public JsonResult<Void> handleBindException(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        String defaultMessage = bindingResult.getFieldError().getDefaultMessage();
        return JsonResult.fail(State.ERR_BAD_REQUEST, defaultMessage);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再次运行,在不提交用户名和密码的情况下,会随机的提示用户名或密码验证失败的提示文本中的某1条。

    在Validation框架中,还有其它许多注解,用于进行不同格式的验证,例如:

    • @NotEmpty:只能添加在String类型上,不许为空字符串,例如""即视为空字符串
    • @NotBlank:只能添加在String类型上,不允许为空白,例如普通的空格可视为空白,使用TAB键输入的内容也是空白,(虽然不太可能在此处出现)换行产生的空白区域也是空白
    • @Size:限制大小
    • @Min:限制最小值
    • @Max:限制最大值
    • @Range:可以配置minmax属性,同时限制最小值和最大值
    • @Pattern:只能添加在String类型上,自行指定正则表达式进行验证
    • 其它

    以上注解,包括@NotNull是允许叠加使用的,即允许在同一个参数属性上添加多个注解!

    以上注解均可以配置message属性,用于指定验证失败的提示文本。

    通常:

    • 对于必须提交的属性,都会添加@NotNull
    • 对于数值类型的,需要考虑是否添加@Range(则不需要使用@Min@Max
    • 对于字符串类型,都添加@Pattern注解进行验证

    2. 解决跨域问题

    在使用前后端分离的开发模式下,前端项目和后端项目可能是2个完全不同的项目,并且,各自己独立开发,独立部署,在这种做法中,如果前端直接向后端发送异步请求,默认情况下,在前端会出现类似以下错误:

    Access to XMLHttpRequest at 'http://localhost:8080/admins/add-new' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    
    • 1

    以上错误信息的关键字是CORS,通常称之为“跨域问题”。

    在基于Spring MVC框架的项目中,当需要解决跨域问题时,需要一个Spring MVC的配置类(实现了WebMvcConfigurer接口的类),并重写其中的方法,以允许指定条件的跨域访问,例如:

    package cn.tedu.boot.demo.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class SpringMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOriginPatterns("*")
                    .allowedMethods("*")
                    .allowedHeaders("*")
                    .allowCredentials(true)
                    .maxAge(3600);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3. 关于客户端提交请求参数的格式

    通常,客户端向服务器端发送请求时,请求参数可以有2种形式,第1种是直接通过&拼接各参数与值,例如:

    // FormData
    // username=root&password=123456&nickname=jackson&phone=13800138001&email=jackson@baidu.com&description=none
    let data = 'username=' + this.ruleForm.username
                  + '&password=' + this.ruleForm.password
                  + '&nickname=' + this.ruleForm.nickname
                  + '&phone=' + this.ruleForm.phone
                  + '&email=' + this.ruleForm.email
                  + '&description=' + this.ruleForm.description;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第2种方式是使用JSON语法来组织各参数与值,例如:

    let data = {
        'username': this.ruleForm.username, // 'root'
        'password': this.ruleForm.password, // '123456'
        'nickname': this.ruleForm.nickname, // 'jackson'
        'phone': this.ruleForm.phone, // '13800138001'
        'email': this.ruleForm.email, // 'jackson@baidu.com'
        'description': this.ruleForm.description // 'none'
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    具体使用哪种做法,取决于服务器端的设计:

    • 如果服务器端处理请求的方法中,在参数前添加了@RequestBody,则允许使用以上第2种做法(JSON数据)提交请求参数,不允许使用以上第1种做法(使用&拼接)
    • 如果没有使用@RequestBody,则只能使用以上第1种做法

    我是将军;我一直都在,。!

  • 相关阅读:
    【读书笔记】高级FPGA设计之高速率结构设计
    项目控制有哪些关键?又何时实施项目控制?
    Mysql中的目录和文件详解
    【JavaWeb】火车票管理系统 (三)用户登录-最终版
    Linux下PC与开发板进行数据通讯的三种方式
    关于设置图标
    网络安全/渗透测试工具AWVS14.9下载/使用教程/安装教程
    Collections.synchronizedMap() 和 ConcurrentHashMap 区别
    最长连续序列(C++解法)
    RocketMQ源码(二十一)之延迟消息
  • 原文地址:https://blog.csdn.net/letterljhx/article/details/126919927