参考资料
- 参考: 妥当性チェックのエラーメッセージ出力方法 (需翻墙)
- springMVC之@InitBinder的用法1
- springMVC之@InitBinder的用法2
- springMVC之@InitBinder 和 Validator
- Spring MVCにおけるフォームバリデーションの適用事例【後編】
目录
- 一. 前期准备
- 1.1 自定义校验注解
- 1.2 国际化资源文件
- 1.3 application配置文件
- 1.4 国际化配置文件
- 1.5 待校验Bean
- 二. 实现Validator接口
- 三. @InitBinder校验Get请求
- 3.1 前端
- 3.2 controller层
- 3.3 全局捕获BindException异常
- 3.4 效果
- 四. @InitBinder校验Post请求
- 4.1 前端
- 4.2 controller层
- 4.3 全局捕获MethodArgumentNotValidException异常
- 4.4 效果
- 五. 注意事项
一. 前期准备
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;
}
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 国际化资源文件
⏹messages_zh.properties
1001E=请输入{msgArgs}。
1007E={0}和{1}的大小关系不正确。
⏹messages_ja.properties
1001E={msgArgs}を入力してください。
1007E={0}と{1}の大小関係が逆らいました。
⏹置于i18n文件夹下
1.3 application配置文件
spring:messages:# 指定国际化文件所在目录和文件前缀basename: i18n/messagesencoding: UTF-8
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 {// 默认解析器,用来设置当前会话默认的国际化语言@Beanpublic LocaleResolver localeResolver() {SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();// 指定当前项目的默认语言是中文sessionLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);return sessionLocaleResolver;}// 默认拦截器,用来指定切换国际化语言的参数名@Beanpublic LocaleChangeInterceptor localeChangeInterceptor() {LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();/*设置国际化请求参数为language设置完成之后,URL中的 ?language=zh 表示读取国际化文件messages_zh.properties*/localeChangeInterceptor.setParamName("language");return localeChangeInterceptor;}// 将我们自定义的国际化语言参数拦截器放入Spring MVC的默认配置中@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(localeChangeInterceptor());}
}
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;
}
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集合@Validprivate List<Test4Entity> tableList;
}
二. 实现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 {@Overridepublic boolean supports(Class<?> clazz) {// 只支持指定Bean类型的校验return Test16Form.class.equals(clazz);}@Overridepublic 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, "");}}
}
三. @InitBinder校验Get请求
3.1 前端
⏹test16.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</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>
3.2 controller层
@Controller
@RequestMapping("/test16")
public class Test16Controller {// 注入我们自定义的校验器@Resourceprivate FromToValidator fromToValidator;@InitBinderpublic 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")@ResponseBodypublic void receiveGet(@Validated Test16Form form) {System.out.println(form);}
}
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 {@Resourceprivate MessageSource messageSource;@ExceptionHandler(BindException.class)// 通过注解指定了响应的状态码,前台$.ajax会在error函数的xhr响应中接收错误json@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic List<Map<String, String>> BindExceptionHandle(BindException errors) {// 存放所有error信息的ListList<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;}
}
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>Title</title>
</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>
4.2 controller层
@Controller
@RequestMapping("/test16")
public class Test16Controller {// 注入我们自定义的校验器@Resourceprivate FromToValidator fromToValidator;@InitBinderpublic 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")@ResponseBodypublic void receivePost(@RequestBody @Validated Test16Form form) {System.out.println(form);}
}
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 {@Resourceprivate HttpServletResponse response;@Resourceprivate MessageSource messageSource;@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseBodypublic List<Map<String, String>> HandleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {// 存放所有error信息的ListList<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响应中接收错误jsonresponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);return errorList;
}
4.4 效果
五. 注意事项
当前端传入的数据无法通过校验规则的时候,会抛出相应的异常。
我们可通过FieldError对象从getMessage方法中获取出相应的错误信息
String msg = this.messageSource.getMessage(err, LocaleContextHolder.getLocale());
错误消息是根据FieldError的code,从国际化资源文件中获取,通过code获取错误消息需要遵循如下的优先顺规则
- errorCode.对象名.属性名
- errorCode.属性名
- errorCode.类型
- errorCode
1为最优先,4的优先顺最低
也就是说,如果国际化资源文件中有如下errorCode的话,会显示优先顺最高的
1007E={0}和{1}的大小关系不正确。
1007E.test16Form.fromNumber=我是测试内容,我的优先顺最高