SpringMVC 会根据请求方法签名不同,将请求消息的消息以一定的方式转换并绑定到请求的参数中。在请求消息到达真正处理方法之前的这一段时间内,SpringMVC需要完成请求消息转换,数据转换,格式化,数据校验等
1.数据绑定流程
SpringMVC 框架将ServletRequest 对象(HttpServletRequest 为其子类)以及处理方法的参数对象实例传递给DataBinder,DataBinder 调用Spring web 上下文的ConversionService 数据组件进行数据转换,并将ServletRequest 中消息填充到参数对象中,然后调用Validator组件进行数据合法性验证,最总生成的BindingResult 对象包含已完成转换的参数对象和校验错误对象。
2.数据转换
1.org.springframework.core.convert.ConversionService 是Spring类型转换体系中的核心接口,可以利用org.springframework.context.support.ConversionServiceFactoryBeanzai 在Spring上下文中定义一个ConversionService,Spring 将自动识别上下文中的Conversion Service,并在SpringMVC 处理方法的参数绑定中使用它进行数据转换。实例代码:
<bean id = "conversionService" class ="org.springframework.context.support.ConversionServiceFactoryBean"/>
在ConversionServiceFactoryBean中内置了多种类型转换器,可以完成大多数类型的转换,此外可以通过ConversionServiceFactoryBean中converters属性注册自定义类型转换器.
<!-- 装配自定义类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--自定义类型转换器-->
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name ="converters">
<list>
<!--自己编写的转换器类-->
<bean class="org...StringtoDataConverter"
p:dataPattern="yyyy-MM-dd"></bean>
</list>
</property>
</bean>
public class StringToDateConverter implements Converter<String,Date>{
private String dataPattern;
@Override
public Date Convert(String date){
...
}
拓展:Spring 3.0.x中使用了mvc:annotation-driven后,默认会帮我们注册默认处理请求,参数和返回值的类,其中最主要的两个类:DefaultAnnotationHandlerMapping 和 AnnotationMethodHandlerAdapter ,分别为HandlerMapping的实现类和HandlerAdapter的实现类,从3.1.x版本开始对应实现类改为了RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
2.在控制器的任何处理方法中使用这个转换器
3.此外我们可以在Controller 类中新增一个initBinder()方法,并使用@initBinder注解,该注解将在控制器初始化是注册属性编辑器。
//自定义属性编辑器
public class DateEditor extends PropertyEditorSupport{
@Override
public void setAsText(String text) throws IllegalArgumentException{
...
}
}
@InitBinder
public void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Date.class, new DateEditor);
}
数据校验
输入校验分为客户端校验和服务端校验,客户端校验主要是过来正常用户的误操作,服务器端校验组织非法数据
Spring 拥有自己独立的数据校验框架 位于org.springframework.validation 包中。而位于org.springframework.validation.beanvalidation包中,LocalValidatorFactoryBean类实现了Spring Validator接口同时也实现了JSR 303的validator 接口,因此只需要在Spring容器中定义一个LocalValidatorFactoryBean即可注入到有需要的bean中完成数据校验。spring-boot-starter-web包里面有hibernate-validator包,所以如果开发 web 就不需要重复添加 spring-boot-starter-validation 依赖了。但如果没用 web 依赖时候想要实现 Bean 验证,则只要单单加入 spring-boot-starter-validation 依赖即可。
< mvn:nnotation-driven/>会默认装配好一个LocalValidatorFactoryBean ,所以在实际开发过程中不需要手动配置LocalValidatorFactoryBean。
1.validation 校验框架
位于org.Springframework.validation ,重要接口和类定义如下
- Validator 接口:提供两个方法
Boolean supports(Class< ?> clazz):对clazz 类型的对象进行校验
void validate(Object targe,Errors errors):对目标类target 进行校验 - Error接口:存放错误信息
- LocalValidatorFactoryBean:该类实现了Spring的Validator 接口,也实现了JSR303 的Validator接口,只要在容器中定义一个LcoalValidatorFactorybean ,既可以注册到数据校验的Bean 中。< mvc:annotation-driven/> 默认装配好LocalValidatorFactoryBean.
实例:实现Spring的Validator 接口,生成校验bean完成对象的校验
@Repository("userValidator")
public class UserValidator implements validator{
public boolean supports(Class<?> clazz){
...}
public void validate(Object target,Error errors){
ValidationUtils.rejectIfEmpty(errors,"loginname",null,"登陆名不能为空")
通过@Repository(“userValidator”)注解将该对象注释为Spring容器中一个bean,名字为”userValidator"
@Autowired
@Qualifier("uservalidator")
private UserValidator userValidator;
@requestmapping(value="/login")
public static login(
@ModelAttribute User user,
Model model,
Errors errors){
userValidator.validate(user,errors);
...
}
2.JSR303
JSR303 是Java 为Bean数据合法性校验提供一个标准规范,用于对java Bean 中字段值进行校验,通过在Bean 属性上标注类似@NotNull 指定校验规则,通过校验接口实现对bean 校验(可以自定义注解)
//constraint 注解
@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {
Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
一个 constraint 通常由 annotation 和相应的 constraintValidator(约束验证器) 组成,它们是一对多的关系。也就是说可以有多个 constraintValidator 对应一个 annotation。在运行时,Bean Validation 框架本身会根据被注释元素的类型来选择合适的 constraint validator 对数据进行验证。
//ConstraintValidator 接口
public interface ConstraintValidator<A extends Annotation, T> {
void initialize(A constraintAnnotation);
boolean isValid(T value, ConstraintValidatorContext context);
第一个是我们的注解,也就是我们自己定义的注解,第二个是参数的值
ConstraintValidator是一个接口,也就是我们自己定义的校验类要实现这个接口
例如 我们 校验手机号,可以写一个 @Mobile的注解
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import miaosha.validator.MobileValidator;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
/**
* 做一个mobile的注解 注意我们使用的静态导入
*
* @author kaifeng1
*
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { MobileValidator.class })
public @interface Mobile {
boolean required() default true;
String message() default "手机号码格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
需要自定义一个校验器 MobileValidator,它使用 ValidatorUtil来完成校验的具体逻辑
MobileValidator.java
package miaosha.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
import miaosha.annotation.Mobile;
import miaosha.util.ValidatorUtil;
public class MobileValidator implements ConstraintValidator<Mobile, String> {
private boolean required = false;
/**
* 初始化
*/
@Override
public void initialize(Mobile constraintAnnotation) {
required = constraintAnnotation.required();
}
/**
* 校验
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (required) {
return ValidatorUtil.isMobile(value);
} else {
if (StringUtils.isEmpty(value)) {
return true;
} else {
return ValidatorUtil.isMobile(value);
}
}
}
}
ValidatorUtil.java
package miaosha.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
/**
* 校验
*
* @author
*
*/
public class ValidatorUtil {
private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
public static boolean isMobile(String src) {
if (StringUtils.isEmpty(src)) {
return false;
}
Matcher m = mobile_pattern.matcher(src);
return m.matches();
}
}
我们使用该注解的时候:
@Mobile
private String mobile;