• SpringBoot @InitBinder注解实现Bean国际化校验


    参考资料

    1. 参考: 妥当性チェックのエラーメッセージ出力方法 (需翻墙)
    2. springMVC之@InitBinder的用法1
    3. springMVC之@InitBinder的用法2
    4. springMVC之@InitBinder 和 Validator
    5. Spring MVCにおけるフォームバリデーションの適用事例【後編】


    一. 前期准备

    1.1 自定义校验注解

    import javax.validation.Constraint;
    import javax.validation.OverridesAttribute;
    import javax.validation.Payload;
    import javax.validation.constraints.Size;
    import javax.validation.ReportAsSingleViolation;
    import java.lang.annotation.*;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    @Documented
    @Constraint(validatedBy = {})
    @ReportAsSingleViolation
    @Size
    public @interface ValidateSize {
    
        String msgArgs() default "";
    
        String message() default "{1006E}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        // 覆盖重写@Size注解中的属性
        @OverridesAttribute(constraint = Size.class, name = "min")
        int min() default 0;
    
        @OverridesAttribute(constraint = Size.class, name = "max")
        int max() default Integer.MAX_VALUE;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    import javax.validation.Constraint;
    import javax.validation.constraints.NotEmpty;
    import javax.validation.Payload;
    import javax.validation.ReportAsSingleViolation;
    import java.lang.annotation.*;
    
    @Documented
    @Target({ ElementType.FIELD })
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = {})
    @NotEmpty
    @ReportAsSingleViolation
    public @interface ValidateNotEmpty {
    
        String msgArgs() default "";
    
    	String message() default "{1001E}";
    
    	Class<?>[] groups() default {};
    
    	Class<? extends Payload>[] payload() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1.2 国际化资源文件

    ⏹messages_zh.properties

    1001E=请输入{msgArgs}。
    1007E={0}和{1}的大小关系不正确。
    
    • 1
    • 2

    ⏹messages_ja.properties

    1001E={msgArgs}を入力してください。
    1007E={0}と{1}の大小関係が逆らいました。
    
    • 1
    • 2

    ⏹置于i18n文件夹下

    在这里插入图片描述

    1.3 application配置文件

    spring:
      messages:
      	# 指定国际化文件所在目录和文件前缀
        basename: i18n/messages
        encoding: UTF-8
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.4 国际化配置文件

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.LocaleResolver;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
    import org.springframework.web.servlet.i18n.SessionLocaleResolver;
    
    import javax.annotation.Resource;
    import java.util.Locale;
    
    @Configuration
    public class InternationalConfig implements WebMvcConfigurer {
    
        // 默认解析器,用来设置当前会话默认的国际化语言
        @Bean
        public LocaleResolver localeResolver() {
            SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
            // 指定当前项目的默认语言是中文
            sessionLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
            return sessionLocaleResolver;
        }
    
        // 默认拦截器,用来指定切换国际化语言的参数名
        @Bean
        public LocaleChangeInterceptor localeChangeInterceptor() {
    
            LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
            /*
                设置国际化请求参数为language
                设置完成之后,URL中的 ?language=zh 表示读取国际化文件messages_zh.properties
             */
            localeChangeInterceptor.setParamName("language");
            return localeChangeInterceptor;
        }
        
        // 将我们自定义的国际化语言参数拦截器放入Spring MVC的默认配置中
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(localeChangeInterceptor());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1.5 待校验Bean

    import lombok.Data;
    
    import javax.validation.groups.Default;
    
    @Data
    public class Test4Entity {
    
        @ValidateNotEmpty(msgArgs = "ID项目", groups = {Default.class})
        private String id;
    
        @ValidateSize(msgArgs = "地址项目", max = 6, groups = {Default.class})
        private String address;
    
        @ValidateSize(msgArgs = "兴趣项目", max = 5, groups = {Default.class})
        private String hobby;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    import lombok.Data;
    
    import javax.validation.Valid;
    import java.math.BigDecimal;
    import java.util.Date;
    import java.util.List;
    
    @Data
    public class Test16Form {
    
        @ValidateNotEmpty(msgArgs = "姓名")
        private String name;
    
        private Date birthday;
    
        private BigDecimal money;
    
        private Integer fromNumber;
    
        private Integer toNumber;
    	
    	// 校验List集合
        @Valid
        private List<Test4Entity> tableList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    二. 实现Validator接口

    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.util.ObjectUtils;
    import org.springframework.validation.Errors;
    import org.springframework.validation.Validator;
    
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
    
    @Component
    public class FromToValidator implements Validator {
    
        @Override
        public boolean supports(Class<?> clazz) {
    
            // 只支持指定Bean类型的校验
            return Test16Form.class.equals(clazz);
        }
    
        @Override
        public void validate(Object target, Errors errors) {
    
            Test16Form form = (Test16Form) target;
    		
    		// 获取from和to的数字
            Integer fromNumber = form.getFromNumber();
            Integer toNumber = form.getToNumber();
    		
    		// 有任何一方为空,就不行校验
            if (ObjectUtils.isEmpty(fromNumber) || ObjectUtils.isEmpty(toNumber)) {
                return;
            }
    
            // 模拟从缓存或者session或者数据库中获取国际化消息
            Map<String, Object[]> languageErrorParamMap = new HashMap<String, Object[]>() {
                {
                    put("zh", new Object[] { "开始数字", "结束数字" });
                    put("ja", new Object[] { "スタートの数字", "エンドの数字" });
                }
            };
    
            // 获取当前设置地区的语言
            Locale locale = LocaleContextHolder.getLocale();
            String language = locale.getLanguage();
            Object[] errorParam = languageErrorParamMap.get(language);
    		
    		// 当from数字 大于 to数字的时候,进行业务校验
            if (fromNumber > toNumber) {
            	/*
            		参数1: bean中被校验住的属性名
            		参数2: 国际化资源文件中的key
            		参数3: error消息的参数
            		参数4: 默认消息
    			*/
                errors.rejectValue("fromNumber", "1007E", errorParam, "");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    三. @InitBinder校验Get请求

    3.1 前端

    ⏹test16.html

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
    head>
    <body>
    <div>
        <button id="getBtn">发送get请求button>
    body>
    <script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}">script>
    <script>
        let languageFlag = false;
    
        $("#getBtn").click(function() {
    
            languageFlag = !languageFlag;
    
            const urlSearchParams = new URLSearchParams();
            urlSearchParams.append("money", "10000");
            urlSearchParams.append("fromNumber", "20");
            urlSearchParams.append("toNumber", "10");
            urlSearchParams.append("language", languageFlag ? "zh" : "ja");
    
            const url = `/test16/receiveGet?${urlSearchParams.toString()}`;
            $.ajax({
                url,
                type: 'GET',
                success: function (data, status, xhr) {
                    console.log("请求成功");
                    console.log(data);
                },
                error: function (xhr, status, error) {
                    console.warn("请求失败");
                    // 获取后台全局异常捕获中返回的json响应
                    const errorJson = xhr.responseJSON;
                    console.log(errorJson);
                }
            });
        });
    script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    3.2 controller层

    @Controller
    @RequestMapping("/test16")
    public class Test16Controller {
    
    	// 注入我们自定义的校验器
        @Resource
        private FromToValidator fromToValidator;
    
        @InitBinder
        public void initBinder(WebDataBinder binder) {
    
            // 去除字符串前后的空格
            binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    		// 使用我们自定义的校验器
            binder.addValidators(fromToValidator);
        }
    
        @GetMapping("/init")
        public ModelAndView init() {
    
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("test16");
            return modelAndView;
        }
    	
    	// 校验
        @GetMapping("/receiveGet")
        @ResponseBody
        public void receiveGet(@Validated Test16Form form) {
    
            System.out.println(form);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.3 全局捕获BindException异常

    • Get请求被被校验住之后,会抛出BindException异常
    import org.springframework.context.MessageSource;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.http.HttpStatus;
    import org.springframework.validation.BindException;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @Resource
        private MessageSource messageSource;
    
        @ExceptionHandler(BindException.class)
        // 通过注解指定了响应的状态码,前台$.ajax会在error函数的xhr响应中接收错误json
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ResponseBody
        public List<Map<String, String>> BindExceptionHandle(BindException errors) {
    		
    		// 存放所有error信息的List
            List<Map<String, String>> errorList = new ArrayList<>();
    		
            for(FieldError err : errors.getFieldErrors()){
    			
    			// 根据当前的FieldError对象从国际化资源文件中获取信息
                String msg = this.messageSource.getMessage(err, LocaleContextHolder.getLocale());
    			
    			// 封装错误信息
                Map<String, String> errorMap = new HashMap<String, String>() {
                    {
                        put("field", err.getField());
                        put("msg", msg);
    
                    }
                };
                errorList.add(errorMap);
            }
    
            return errorList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    3.4 效果

    在这里插入图片描述

    四. @InitBinder校验Post请求

    4.1 前端

    ⏹test16.html

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
    head>
    <body>
    <div>
        <button id="postBtn">发送post请求button><br>
    div>
    body>
    <script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}">script>
    <script>
    
        let languageFlag = false;
    
        $("#postBtn").click(function() {
    
            languageFlag = !languageFlag;
    
            const urlSearchParams = new URLSearchParams();
            urlSearchParams.append("language", languageFlag ? "zh" : "ja");
    		
    		// 待校验的list对象
            const tableList = [
                {
                    id: null,
                    address: '测试address123',
                    hobby: '测试hobby123'
                },
                {
                    id: 110,
                    address: '测试',
                    hobby: '测试AAAAAAAAAA'
                },
                {
                    id: 120
                }
            ];
            
            // 待校验的bean对象
            const paramObj = {
                money: "10000",
                fromNumber: "20",
                toNumber: "10",
                tableList
            };
    
            $.ajax({
                url: `/test16/receivePost?${urlSearchParams.toString()}`,
                type: 'POST',
                data: JSON.stringify(paramObj),
                // 指定向后台提交json数据
                contentType : 'application/json;charset=utf-8',
                // 指定后台返回json数据给前台
                dataType: 'json',
                success: function (data, status, xhr) {
                    console.log("请求成功");
                    console.log(data);
                },
                error: function (xhr, status, error) {
                    console.warn("请求失败");
                    const errorJson = xhr.responseJSON;
                    console.log(errorJson);
                }
            });
        });
    
    script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    4.2 controller层

    @Controller
    @RequestMapping("/test16")
    public class Test16Controller {
    
    	// 注入我们自定义的校验器
        @Resource
        private FromToValidator fromToValidator;
    
        @InitBinder
        public void initBinder(WebDataBinder binder) {
    
            // 去除字符串前后的空格
            binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    		// 使用我们自定义的校验器
            binder.addValidators(fromToValidator);
        }
    
        @GetMapping("/init")
        public ModelAndView init() {
    
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("test16");
            return modelAndView;
        }
    	
    	// 校验
        @PostMapping("/receivePost")
        @ResponseBody
        public void receivePost(@RequestBody @Validated Test16Form form) {
    
            System.out.println(form);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4.3 全局捕获MethodArgumentNotValidException异常

    • Post请求被被校验住之后,会抛出MethodArgumentNotValidException异常
    import org.springframework.context.MessageSource;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletResponse;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @Resource
        private HttpServletResponse response;
    
        @Resource
        private MessageSource messageSource;
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseBody
        public List<Map<String, String>> HandleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
    		
    		// 存放所有error信息的List
            List<Map<String, String>> errorList = new ArrayList<>();
    
    		List<FieldError> errors = ex.getFieldErrors();
            for(FieldError err : errors){
    
                // 根据当前的FieldError对象从国际化资源文件中获取信息
                String msg = this.messageSource.getMessage(err, LocaleContextHolder.getLocale());
                Map<String, String> errorMap = new HashMap<String, String>() {
                    {
                        put("field", err.getField());
                        put("msg", msg);
    
                    }
                };
                errorList.add(errorMap);
            }
            
    		// 通过response对象指定了响应的状态码,前台$.ajax会在error函数的xhr响应中接收错误json
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return errorList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    4.4 效果

    在这里插入图片描述

    五. 注意事项

    当前端传入的数据无法通过校验规则的时候,会抛出相应的异常。
    我们可通过FieldError对象getMessage方法中获取出相应的错误信息

    String msg = this.messageSource.getMessage(err, LocaleContextHolder.getLocale());
    
    • 1

    错误消息是根据FieldError的code,从国际化资源文件中获取,通过code获取错误消息需要遵循如下的优先顺规则

    1. errorCode.对象名.属性名
    2. errorCode.属性名
    3. errorCode.类型
    4. errorCode

    1为最优先,4的优先顺最低

    在这里插入图片描述
    也就是说,如果国际化资源文件中有如下errorCode的话,会显示优先顺最高的

    1007E={0}和{1}的大小关系不正确。
    1007E.test16Form.fromNumber=我是测试内容,我的优先顺最高
    
    • 1
    • 2

    在这里插入图片描述

  • 相关阅读:
    视频生成模型Sora的全面解析:从AI绘画、ViT到ViViT、DiT、VDT、NaViT、VideoPoet
    WPF实现html中的table控件
    国内离线安装 Chrome 扩展程序的方法总结
    关于出国留学和考研比较----以本人双非跨考计算机为例
    全球与中国液体壁纸行业需求趋势及投资策略分析报告2022-2028年
    java毕业设计在线宠物用品交易网站Mybatis+系统+数据库+调试部署
    Springboot毕设项目小区物业管理系统75n19java+VUE+Mybatis+Maven+Mysql+sprnig)
    数字藏品值得探究,依然是广阔的大海播
    国际商务谈判A卷+答案
    企业运维 | MySQL关系型数据库在Docker与Kubernetes容器环境中快速搭建部署主从实践
  • 原文地址:https://blog.csdn.net/feyehong/article/details/128158337