静动态资源处理管理

我们最初在配置 DispatcherServlet 来作为处理方法映射的时候,因为静态资源无法找到对应的处理方法,所以无法访问,针对这个问题,使用 <mvc:default-servlet-handler/> 之后,SpringMVC 将在容器中中定义一个 SimpleUrlhandlerMapping,它的作用就是将请求交给 WEB 应用服务器默认的 Tomcat 处理,这样静态资源的访问就得到了解决。

但是我们标注了 <mvc:default-servlet-handler/> 之后,新加入的 SimpleUrlHandlerMapping 会覆盖我们原本用来处理方法映射的 DefaultAnnotationHandlerMapping。这样处理动态资源访问的映射关系就没有了,因此动态资源无法访问。

解决方法就是:使用 <mvc:annotation-driven/>,添加这个设置之后,就会多出来一个优先级最高的 RequestMappingHandlerMapping,专门处理动态资源访问和处理方法之间的映射关系。只有这个 Mapping 无法处理的资源(静态资源),才会交给后面的 SimpleUrlHandlerMapping 处理(交给 Tomcat)。

<mcv:anntation-driven>

这个配置的字面意思就是 MVC 注解驱动。那么这个配置有什么用呢?

  • <mvc:annotation-driven/> 会自动注册: RequestMappingHandlerMapping 、 RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个 bean。

  • 还将提供以下支持:

  1. 支持使用 ConversionService 实例对表单参数进行类型转换
  2. 支持使用 @NumberFormat、@DateTimeFormat 注解完成数据类型的格式化
  3. 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
  4. 支持使用 @RequestBody 和 @ResponseBody 注解实现 AJAX

数据绑定

数据绑定指的是从请求的数据到处理方法中的参数这个过程,在 SpringMVC 中,数据绑定的流程大概如下:

  1. SpringMVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
  2. DataBinder 调用装配在 SpringMVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
  3. 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
  4. SpringMVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的参数。

也就是过程如下:

301.jpg

在 SpringMVC 的 ConversionService 数据转化服务中,有大量的转化器 Converter 可以作为用于将 String 类型的数据转化为 Java 常间的类型,但是对于我们自己设置的类型,如果需要转化则需要我们手动实现 Converter 接口。

比如我们想要将 String 类型转化为 User 类型;"Xorex-Tempest-Xorex@Tempest.com" -> User{username=Xorex,password=Tempest,email=Xorex@Tempest.com} 需要这样实现接口:

1
2
3
4
5
6
7
public class StringToUserConverter implements Converter<String, User> {
@Override
public User convert(String s) {
String[] datas=s.split("-");
return new User(datas[0],datas[1],datas[2]);
}
}

实现了 Converter 之后,需要将我们自己实现的 Converter 放入执行转化服务的 ConversionService 中。而麻烦之处在于 ConversionService 是由 ConversionServiceFactoryBean 生成的,那么我们最终其实是将 Converter 交给 FormattingConversionServiceFactoryBean,由这个工厂来生成含有自定义 Converter 的 ConversionService。

所以我们需要 SpringMVC 配置文件中,声明:将自定义 Converter 交给 FormattingConversionServiceFactoryBean 并告诉 SpringMVC 用这个含有我们自定义 Converter 的 FactoryBean 来生成 ConversionService:

1
2
3
4
5
6
7
8
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="space.xorex.SpringMVC_01.Conversion.StringToUserConverter"/>
</set>
</property>
</bean>

数据格式化

前面介绍了通过实现 Converter 来实现自定义数据和请求数据的绑定。下面来介绍一下请求数据的格式化,也就是将请求发过来的数据转化为格式化的数据,比如将 2021-06-01 转化为 Date 对象。将 $12,123,234.3242 转化为 Double 对象等等。

这里使用的 Format 注解:@DateTimeFormat@NumberFormat 等,只需要标注到需要接收格式化的参数或者字段前面,被 SpringMVC 自动填充的时候,就会根据 ConversionService 里面定义的 Format 规则进行格式化了。

而我们上面配置的 FormattingConversionServiceFactoryBean 这个工厂生成的 ConversionService 就包含一系列 Format 类型的 Class 用于数据格式化,而不同的注解也有不同的格式化方法,需要在注解参数 pattern 中填写。举个例子:

1
2
3
4
5
6
7
8
@RequestMapping("/date")
public void getDate(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
System.out.println(date);
}
@RequestMapping("/number")
public void getNumber(@NumberFormat(pattern = "$#,###.##") Double money) {
System.out.println(money);
}

时间用 yyyy-MM-dd 规定传入的时间字符串格式,浮点数用 $#,###.## 规定传入的金钱的格式,对于 JavaBean 里面的数据使用格式化,则直接在 Bean 里面的字段加上注解即可,SpringMVC 进行参数填充的时候会读取上面的注解的。

数据校验

JSR-303

对于数据在服务端进行的数据校验工作,我们自己写真的是太麻烦了,于是在 JAVA6 里面推出了一种规范:JSR-303,JSR 是 Java Specification Requests 的缩写,意思是 Java 规范提案,又叫做 Bean Validation。JSR 303 是 Java 为 bean 数据合法性校验提供的标准框架。

使用 hibernate 实现的 JSR-303

需要导入一下包:一个是 hibernate.validator 的 hibernate-validator 和 fasterxml 的 classmate 共两个包。其中前者提供 JSR-303 接口的实现,后者为前者的依赖包(会自动加入另外一个依赖 jboss-logging)。

然后就可以使用很多 hibernate 自己实现的接口了,挺多挺好用的。

JavaBean 校验

然后我们就可以利用 JSR-303 定义的注解,去去进行数据校验了,这里的所有注解可以查看文档。对于自定义 Bean ,在 Bean 的声明处向内部标校验注解,然后参数处标注 @Valid 表示需要要校验这个 Bean,并紧接着被校验的 Bean 声明一个参数:BindingResult result 或者 Errors errors,这个 两个类可以记录校验的结果。

1
2
3
4
5
6
7
8
9
10
11
12
public class User {

@NotNull
private String userName;
@NotNull
private String password;
@Email
private String email;
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
}
1
2
3
4
5
6
7
8
@RequestMapping("/valid")
public void testValid(@Valid User user,BindingResult result) {
if(result.hasErrors()) { //从校验结果中查看是否校验成功
System.out.println("Error!");
} else {
System.out.println("Success!");
}
}

自定义错误消息

最简单的方法就是在任何一个校验的注解里面添加 message 属性,里面填写需要自定错误的消息。这样我们取出来的 Message 就是我们自己设置的错误了。

1
2
3
4
5
6
@NotNull(message = "老哥不能空啊!")
private String userName;
@NotNull(message = "老哥不能空啊!")
private String password;
@Email(message = "老哥你邮箱不对啊!")
private String email;

但是如果需要实现国际化的话,具体的流程和 JstlView 实现国际化的流程都是一样的,唯一的不同是对国际化文件的 Key 进行一定的规则限制。

因为我们在回显错误信息的时候,比如使用 <form:errors path="email"> 来现实隐含模型中属性为 email 的校验错误的时候,他会根据一定的规则从国际化文档中寻找对应的 Key:

1
2
3
4
Email.user.email=XXXX //在隐含模型的 user 对象的 email 字段使用 @Email 校验时,匹配此国际化错误信息
Email.email=XXXX //在隐含模型任意对象中的 email 字段使用 @Email 校验时,匹配此国际化错误信息
Email.java.lang.String=XXXX //字段为 String 的值使用 @Email 校验时,匹配此国际化错误信息
Email=XXXX //任意字段使用 @Email 校验时,匹配此国际化错误信息

当有多个国际化 Key 得到匹配的时候,同样按照精确度决定匹配优先级。