SpringMVC数据校验、数据格式化处理、国际化设置
1.数据验证
(1)使用JSR-303验证框架
JSR(Java Specification Requests),意思是Java 规范提案。JSR-303是JAVA EE 6中的一项子规范,叫做Bean Validation。JSR 303,Bean Validation规范 ,为Bean验证定义了元数据模型和API。默认的元数据模型是通过Annotations来描述的,使用规范定义的这些注解有效的替换了if-else冗长的校验代码。
引入依赖:
•validation-api
•hibernate-validator(附加了一些验证注解)
•jakarta.validation-api(Spring6以上引入此依赖)
JSR-303验证框架常用注解
使用案例:
@Data
public class SysUser {/*** 用户ID*/@NotNull(message = "用户id不能为空")private Long userId;/** 用户名*/@NotBlank(message = "用户名不能为空")@Length(max = 20, message = "用户名不能超过20个字符")@Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")private String username;/** 手机号*/@NotBlank(message = "手机号不能为空")@Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")private String mobile;/**性别*/private String sex;/** 邮箱*/@NotBlank(message = "联系邮箱不能为空")@Email(message = "邮箱格式不对")private String email;/** 密码*/private String password;/*** 创建时间 */@Future(message = "时间必须是将来时间")private Date startTime;
}
在controller层方法参数列表前加上@Valid或@Validated注解
@PostMapping("/save/valid")
@ResponseBody
public Result save(@RequestBody @Valid User user) {if(userService.save(user)>0)return Result.ok();elsereturn Result.fail();
}
@Valid 与 @Validated的区别 :
区别 | @Valid | @Validated |
---|---|---|
来源 | 标准JSR-303规范 | Spring‘s JSR-303规范,是标准JSR-303的一个变种。 |
分组验证 | 不支持 | 支持(使用注解的group属性设定) |
声明位置 | 可以用在方法、构造函数、方法参数和成员属性(字段)上,支持嵌套验证。 | 可以用在类型、方法和方法参数上,但不能用在成员属性(字段)上,从而不支持嵌套验证功能。 |
@Valid嵌套验证
@Data
public class Food {@Validprivate Drink drink;@NotNullprivate String type;
}@Data
public class Drink {@NotNullprivate String name;@Size(min = 1,max = 10000)private String describ;@Digits(integer = 2,fraction = 2)private Double price;
}
//controller层
@PostMapping("/food")
public String addFood(@Valid Food food){return food.getDrink().getName();
}
数据验证全局异常定义案例:
@Component
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler { /**方法参数校验(接收参数加上@RequestBody注解才会有这种异常) */@ExceptionHandler(MethodArgumentNotValidException.class)public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {log.error("方法参数校验失败", e);return Result.fail(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());}@ExceptionHandler(BindException.class)public Result bindException(BindException ex, HttpServletRequest request) {log.error("数据绑定异常", ex);try {// 拿到@NotNull,@NotBlank和 @NotEmpty等注解上的message值String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage();if (StringUtils.isNotEmpty(msg)) {return Result.fail(msg);}} catch (Exception e) {e.printStackTrace();} StringBuilder msg = new StringBuilder(); // 参数类型不匹配检验List<FieldError> fieldErrors = ex.getFieldErrors();fieldErrors.forEach((oe) ->msg.append("参数:[").append(oe.getObjectName()).append(".").append(oe.getField()).append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配."));return Result.fail(msg.toString());}/**ConstraintViolationException */@ExceptionHandler(ConstraintViolationException.class)public Result handleConstraintViolationException(ConstraintViolationException e) {log.error("注解校验异常", e);Set<ConstraintViolation<?>> violations = e.getConstraintViolations();String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));return Result.fail(message);}
}
自定义验证注解的步骤案例:
•定义注解接口
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CardNoValidator.class)
public @interface CardNo {String message() default "{edu.cqie.ssm.cardNoErrorMessage}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
•创建ConstraintValidator接口实现类
public class CardNoValidator implements ConstraintValidator<CardNo, Object> {@Overridepublic void initialize(CardNo constraintAnnotation) {ConstraintValidator.super.initialize(constraintAnnotation);}@Overridepublic boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {if (o == null) {return true;//身份证号未传递时,不做校验}return IdCardValidatorUtils.isValidate18Idcard(o.toString());}
}
•使用注解
@Data
public class User {/*** 用户ID*/@NotNull(message = "用户id不能为空")private Long userId;/** 用户名*/@NotBlank(message = "用户名不能为空")@Length(max = 20, message = "用户名不能超过20个字符")@Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")private String username;@CardNoprivate String cardNo;
}
2.数据格式化
SpringMVC Formatter
•SpringMVC类型转换器提供了一个统一的ConversionService API以及一个强类型的Converter SPI,用于实现从一种类型到另一种类型的转换逻辑。例如,将Short强制转换为Long。
•在Spring MVC中,HTTP中的源数据都是String类型,数据绑定需要将String转换为其他类型,同时也可能需要将数据转换为具有本地格式的字符串样式进行展示,而Converter SPI不能直接满足这种格式要求。
•Spring 3 引入了一个方便的Formatter SPI,当在客户端环境(如Web应用程序)中工作并且需要解析和输出本地化字段值时,可以使用Formatter SPI。
(1)使用Converter转换
public class StringToDateConverter implements Converter<String, Date> {private String pattern;public StringToDateConverter(String pattern) {this.pattern = pattern;}@Overridepublic Date convert(String source) {try {SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);dateFormat.setLenient(false);return dateFormat.parse(source);} catch (ParseException e) {throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + pattern + "\"");}}
}
注册bean
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"><property name="converters"><set><bean class="edu.cqie.ssm.converter.StringToDateConverter"><constructor-arg name="pattern" value="yyyy-MM-dd"/></bean></set></property>
</bean><mvc:annotation-driven conversion-service="conversionService"/>
(2)使用Formatter转换
public class DateFormatter implements Formatter<Date> {private SimpleDateFormat sdf;public DateFormatter(String pattern) {this.sdf = new SimpleDateFormat(pattern);sdf.setLenient(false);}@Overridepublic Date parse(String s, Locale locale) throws IllegalArgumentException {try {return this.sdf.parse(s);} catch (ParseException e) {throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + this.sdf.toPattern() + "\"");}}@Overridepublic String print(Date date, Locale locale) {return this.sdf.format(date);}
}
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"><property name="formatters"><set><bean class="edu.cqie.ssm.formatter.DateFormatter"><constructor-arg name="pattern" value="yyyy-MM-dd"/></bean></set></property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
(3)内置Formatter转换器
类型 | 说明 |
---|---|
NumberFormatter | 实现 Number 与 String 之间的解析与格式化 |
CurrencyFormatter | 实现 Number 与 String 之间的解析与格式化(带货币符号) |
PercentFormatter | 实现 Number 与 String 之间的解析与格式化(带百分数符号) |
DateFormatter | 实现 Date 与 String 之间的解析与格式化 |
两个格式化注解
•@NumberFormat
•@DateTimeFormat
使用注解格式化
@DateTimeFormat(pattern="yyyy/MM/dd")
private Date birthday;@NumberFormat(style = NumberFormat.Style.CURRENCY )
private Double balance;//货币金额 ¥5000@NumberFormat(pattern = "#,###.##")
private Double salary; //工资 10,000.00@NumberFormat(style = NumberFormat.Style.PERCENT)
private Double percent;//不加%按p*100来显示,加上按提交精度来显示
3.国际化
什么是国际化?
国际化(也叫 i18n),由于国际化英文单词是 internationalization,在 i 和 n 之间有 18 个字母,因此国际化又叫做 i18n。国际化是指程序在不做任何修改的情况下,就可以在不同的国家或地区和不同的语言环境下,按照当地的语言和格式习惯的显示字符,例如MyBatis官方网站等。
国际化设置场景:
•Spring标签国际化
•接口方法国际化
LocaleResolver
•AcceptHeaderLocaleResolver:根据请求头中的 Accept-Language 字段来确定当前的区域语言。
•SessionLocaleResolver:根据请求参数来确定区域语言,确定后会保存在 Session 中,只要 Session不变,Locale 对象就一直有效。
•CookieLocaleResolver:根据请求参数来确定区域语言,确定后会保存在Cookie中,只要Cookie不变Locale对象就一直有效。
•FixedLocaleResolver:配置时直接提供一个 Locale 对象,以后不能修改。
(1)AcceptHeaderLocaleResolver
步骤一:添加多语言配置文件
步骤二:修改****spring-mvc.xml
<bean id="messageSource“class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename" value="i18n.message"/><property name="defaultEncoding" value="UTF-8"/>
</bean>
步骤三:在页面中引用
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head><title><spring:message code="login.title"/></title>
</head>
<body><form action="toRegister" method="post"><div><spring:message code="login.content.title"/></div><div><span><spring:message code="login.account"/></span><input type="tel" name="phone" maxlength="11" placeholder='<spring:message code="login.account.holder"/>'/></div><div><span><spring:message code="login.password"/></span><input type="password" name="pwd" minlength="8" maxlength="20" placeholder='<spring:message code="login.password.holder"/>'/></div><div><input type="submit" value='<spring:message code="login.submit"/>'/></div></form>
</body>
</html>
步骤四:在接口中引用
@Controller
public class LoginController {@AutowiredMessageSource messageSource;@GetMapping("/login")public String login() {String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());System.out.println("username = " + username);System.out.println("password = " + password);return "login";}
}
(2)SessionLocaleResolver
**步骤一:添加多语言配置文件(与上面一样)
步骤二:修改****spring-mvc.xml
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename" value="i18n.message"/><property name="defaultEncoding" value="UTF-8"/>
</bean>
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"><property name="paramName" value="locale"/></bean></mvc:interceptor>
</mvc:interceptors>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean>
步骤三:在接口中使用
@Controller
public class LoginController {@AutowiredMessageSource messageSource;@GetMapping("/login")public String login(String locale,HttpSession session) {if ("zh-CN".equals(locale)) {session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale("zh", "CN"));} else if ("en-US".equals(locale)) {session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale("en", "US"));}String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());System.out.println("username = " + username);System.out.println("password = " + password);return "login";}
}
(3)CookieLocaleResolver
**步骤一:添加多语言配置文件(与上面一样)
步骤二:修改****spring-mvc.xml
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename" value="i18n.message"/><property name="defaultEncoding" value="UTF-8"/>
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
步骤三:在接口中使用
@GetMapping("/login")
public String login(String locale, HttpServletRequest req, HttpServletResponse resp) {CookieLocaleResolver resolver = new CookieLocaleResolver();if ("zh-CN".equals(locale)) {resolver.setLocale(req, resp, new Locale("zh", "CN"));} else if ("en-US".equals(locale)) {resolver.setLocale(req, resp, new Locale("en", "US"));}String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());System.out.println("username = " + username);System.out.println("password = " + password);return "login";
}
resolver.setLocale(req, resp, new Locale("zh", "CN"));
} else if ("en-US".equals(locale)) {resolver.setLocale(req, resp, new Locale("en", "US"));
}
String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());
String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());
System.out.println("username = " + username);
System.out.println("password = " + password);
return "login";
}