参考资料
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 {};
}
⏹messages_zh.properties
1001E=请输入{msgArgs}。
1007E={0}和{1}的大小关系不正确。
⏹messages_ja.properties
1001E={msgArgs}を入力してください。
1007E={0}と{1}の大小関係が逆らいました。
⏹置于i18n文件夹下
spring:
messages:
# 指定国际化文件所在目录和文件前缀
basename: i18n/messages
encoding: UTF-8
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());
}
}
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集合
@Valid
private List<Test4Entity> tableList;
}
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, "");
}
}
}
⏹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>
@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);
}
}
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;
}
}
⏹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>
@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);
}
}
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;
}
当前端传入的数据无法通过校验规则的时候,会抛出相应的异常。
我们可通过FieldError对象从getMessage方法中获取出相应的错误信息
String msg = this.messageSource.getMessage(err, LocaleContextHolder.getLocale());
错误消息是根据FieldError的code,从国际化资源文件中获取,通过code获取错误消息需要遵循如下的优先顺规则
1为最优先,4的优先顺最低
也就是说,如果国际化资源文件中有如下errorCode的话,会显示优先顺最高的
1007E={0}和{1}的大小关系不正确。
1007E.test16Form.fromNumber=我是测试内容,我的优先顺最高