一、处理静态资源
在Spring MVC 如果配置了前置控制器DispatcherServlet,若配置的拦截路径为“/”,那么所有的请求都将被它拦截。对静态资源的访问也属于一个请求,那么也会被它拦截,然后进入它的匹配流程,我们知道它是根据HandlerMapping的配置来匹配的。而对于静态资源来说,默认的Spring MVC是没有注册匹配规则的,此时若你去请求一个静态资源,则会报404错误。
这里我们有三个方法可以解决静态资源访问的问题。
1.1 激活Tomcat的defaultServlet来处理静态文件
web.xml
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
注:要写在DispatcherServlet的前面, 让defaultServlet先拦截,这个就不会进入Spring了,我想性能是最好的吧。
Tomcat, Jetty, JBoss, and GlassFish 默认 Servlet的名字 – “default”
Google App Engine 默认 Servlet的名字 – “_ah_default”
Resin 默认 Servlet的名字 – “resin-file”
WebLogic 默认 Servlet的名字 – “FileServlet”
WebSphere 默认 Servlet的名字 – “SimpleFileServlet”
1.2 在spring3.0.4以后版本提供了mvc:resources
springmvc.xml
<mvc:resources mapping="/css/**" location="/css/" cache-period="31536000"/>
<mvc:resources mapping="/js/**" location="/js/" cache-period="31536000"/>
<mvc:resources mapping="/img/**" location="/img/" cache-period="31536000"/>
/images /**映射到 ResourceHttpRequestHandler 进行处理,location指定静态资源的位置.可以是web application根目录下、jar包里面,这样可以把静态资源压缩到jar包中。cache-period可以使得静态资源进行web cache
使用 <mvc:resources /> 元素,会把 mapping 的 URI 注册到 SimpleUrlHandlerMapping 的 urlMap 中,key 为 mapping 的 URI pattern 值,而 value 为 ResourceHttpRequestHandler,这样就巧妙的把对静态资源的访问由 HandlerMapping 转到 ResourceHttpRequestHandler 处理并返回,所以就支持 classpath 目录,jar 包内静态资源的访问。
1.3 使用<mvc:default-servlet-handler />
<mvc:default-servlet-handler />
<mvc:annotation-driven></mvc:annotation-driven>
<mvc:default-servlet-handler /> 将在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler,它会对进入DispatcherServlte的请求进行赛选,如果发现是没有经过映射的请求,就将该请求交有WEB服务器默认的Servlet进行处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
注:这里需要将mvc:annotation-driven加上,如果不加其它Handler访问将不再工作。
多个HandlerMapping的执行顺序问题:
DefaultAnnotationHandlerMapping 的 order 属性值是:0
自动注册的 SimpleUrlHandlerMapping 的 order 属性值是: 2147483646
<mvc:default-servlet-handler/>自动注册的 SimpleUrlHandlerMapping 的 order 属性值是:2147483647
spring 会先执行 order 值比较小的。
二、数据绑定
数据绑定是将用户输入绑定到领域模型的一种特性,在Spring MVC的controller和view数据传递中,基于HTTP请求的特性,所有HTTP请求参数的类型均为字符串,如果模型领域需要绑定的类型为double或int,则需要手动进行类型转换,而有了数据绑定后,就不需要手动将HTTP请求中的String类型转换为模型需要的类型了,数据绑定的另一个好处是,当输入验证失败时,会重新生成一个HTML表单,无需重新填写输入字段。
数据绑定的流程
数据绑定方法的核心是DataBinder
- SpringMVC的主框架将RequestServlet和目标方法的入参传递给WebDataBInderFactory实例,已创建DataBinder实例对象。
- DataBinder调用装配在SpringMVC上下文中的ConversionService
组件进行数据类型转换,数据格式化工作。将Servlet中的请求信息填充到目标方法的入参对象中。 - 调用Validator组件对已经绑定了请求信息的入参对象进行数据合法性校验,并生成数据绑定结果BindingData对象。
- SpringMVC抽取BindingResult中的入参对象和校验错误对象,将他们赋给目标方法的响应入参。
2.1 数据转换
SpringMVC上下文中内建了很多转换器,可完成大多数Java类型的转换工作。
- ConversionService是spring类型转换体系的核心接口,位于org.springframework.core.convert包中
- 可以利用 ConversionServiceFactoryBean 在spring上下文中定义一个ConversionSerivce。spring 自动识别出上下文中的ConversionService,并在Bean属性配置及springmvc处理方法入参绑定等场合使用它进行数据的转换。
- 可以通过 ConversionServiceFactoryBean 的 converters 属性绑定自定义的类型转化器。
SpringMVC 定义了3种自定义的转换器接口,实现任意一个转换器接口都可以作为自定义转化器注册到ConversionServiceFactoryBean中。
- Converter<S, T>:将S类型转换为T类型对象。
- ConverterFactory:将相同系列多个“同质”Converter封装在一起。例如 StringToNumberConverterFactory(将String转换为Number或Number子类对象Long、Integer等) 。
- GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换。
/**
* 自定义转换器
* @author cye
*
*/
@Component
public class EmployeeConverter implements Converter<String, Employee>{
@Autowired
private DepartmentDao departmentDao;
@Override
public Employee convert(String source) {
if (source != null) {
String[] vals = source.split("-");
if (vals != null && vals.length == 4) {
String lastName = vals[0];
String email = vals[1];
Integer gender = Integer.parseInt(vals[2]);
Integer depId = Integer.parseInt(vals[3]);
Employee employee = new Employee(null, lastName, email, gender, departmentDao.getDepartment(depId));
System.out.println(source + "---converter---" + employee);
return employee;
}
}
return null;
}
}
这里我们以实现 Converter<S, T> 为例,将GG-gg@atguigu.com-0-105格式的字符串转换为Employee对象。
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
关于mvc:annotation-driven:
- </mvc:annotation-driven>会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver三个bean。
- 还将提供以下支持
- 支持使用ConversionService对表单参数进行类型转换
- 支持使用@NumberFormat、@DateTimeFormat注解完成数据类型的格式化
- 支持使用@Valid注解对JavaBean实例进行JSR 303验证
- 支持使用 @RequestBody和@ResponseBody注解
- 如果使用了mvc:default-servlet-handler 静态资源的配置,不添加该配置,RequestMappingHandlerMapping不会加载,注解将不再工作。
@InitBinder:
- @InitBinder标识的方法,可以对WebDataBinder对象进行初始化,WebDataBinder是DataBinder的子类,用于完成表单字段到JavaBean属性的绑定。
- @InitBinder标注的方法不能有返回值
- @InitBinder标注的方法参数都是WebDataBinder
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new MyDateEditor());
}
在Handler里面添加上面方法后,该处理类中所有处理方法使用 MyDateEditor 对日期进行格式化
public class MyDateEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = format.parse(text);
} catch (ParseException e) {
format = new SimpleDateFormat("yyyy-MM-dd");
try {
date = format.parse(text);
} catch (ParseException e1) {
}
}
setValue(date);
}
}
其他方法:
- binder.setAllowedFields(“id”) : 设置允许的字段,比如我只想设置id,那么可以调用这个方法,那么其他属性会忽略;
- binder.setDisallowedFields(“id”) : 设置不允许的自动,比如我不想设置id,那么可以调用此方法,这个属性将不设置;
- binder.setRequiredFields() : 表示哪些字段是必填的;
- binder.setValidator() :设置自定义的验证器,如果如JSR-303不适合,可以使用这个。
2.2 数据格式化
Spring 在格式化模块定义了一个 ConversionService 的实现类 FormattingConversionService,因此它既具有格式化的功能又具有数据转换的功能。FormattingConversionService拥有一个FormattingConversionServiceFactoryBean的工厂类。
FormattingConversionServiceFactoryBean 内部已经注册了:
- NumberFormatAnnotationFormatterFactory 支持对数值类型的属性使用 @NumberFormat 注解
- JodaDateTimeFormatAnnotationFormatterFactory 支持对日期类型的属性使用 @DateTimeFormat 注解
装配了FormattingConversionServiceFactoryBean后就可以在
SpringMVC入参绑定及模型数据输出时使用注解驱动。<mvc:annotation-dirven/>默认创建的 ConversionService 就是FormattingConversionServiceFactoryBean。
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 配置 conversionService -->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
@DateTimeFormat 可以对java.util.Date、java.util.Calendar、java.util.Long时间类型进行标注。
- pattern属性:指定解析/格式化字段数据的模式,如”yyyy-MM-dd hh:mm:ss”
- iso属性:类型为DateTimeFormat.ISO,指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用)–默认,ISO.DATE(yyyy-MM-dd),ISO.TIME(hh:mm:ss.SSSZ),ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss:SSSZ)
- style属性:字符串类型,通过样式指定日期时间的格式。由两位字符组成,第一个表示日期,第二位表示时间。
@NumberFormat 可以对类似数字类型的属性进行标注,它拥有两个互斥的属性
- style:类型为NumberFormat.Style,用于指定样式类型。包括三种:Style.NUMBER(正常数字类型),Style.CURRENCY(货币类型),Style.PERCNET(百分数类型)
- pattern:类型为String,自定义样式,如”###,##”
@NumberFormat(pattern="##.#")
public Float getSalay() {
return salay;
}
@Past//之前的时间
@DateTimeFormat(pattern="yyyy-MM-dd")
public Date getBirth() {
return birth;
}
2.3 数据校验
JSR 303是Java为Bean数据合法性校验提供的标准框架,它已经包含在 Java 6.0中。
JSR 303通过在属性上面标注类似@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
Spring 拥有自己的数据校验框架,同时支持 JSR 303标准的验证框架,但是Spring 本身没有实现 JSR 303。所以必须引用其他实现了JSR 303的jar包。
Hibernate Validator是 JSR 303 的一个参考实现:
注解 | 运行时检查 |
---|---|
@AssertFalse | 被注解的元素必须为false |
@AssertTrue | 被注解的元素必须为true |
@DecimalMax(value) | 被注解的元素必须为一个数字,其值必须小于等于指定的最小值 |
@DecimalMin(Value) | 被注解的元素必须为一个数字,其值必须大于等于指定的最小值 |
@Digits(integer=, fraction=) | 被注解的元素必须为一个数字,其值必须在可接受的范围内 |
@Future | 被注解的元素必须是日期,检查给定的日期是否比现在晚 |
@Max(value) | 被注解的元素必须为一个数字,其值必须小于等于指定的最小值 |
@Min(value) | 被注解的元素必须为一个数字,其值必须大于等于指定的最小值 |
@NotNull | 被注解的元素必须不为null |
@Null | 被注解的元素必须为null |
@Past(java.util.Date/Calendar) | 被注解的元素必须过去的日期,检查标注对象中的值表示的日期比当前早 |
@Pattern(regex=, flag=) | 被注解的元素必须符合正则表达式,检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配 |
@Size(min=, max=) | 被注解的元素必须在制定的范围(数据类型:String, Collection, Map and arrays) |
@Valid | 递归的对关联对象进行校验, 如果关联对象是个集合或者数组, 那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验 |
@CreditCardNumber | 对信用卡号进行一个大致的验证 |
被注释的元素必须是电子邮箱地址 | |
@Length(min=, max=) | 被注解的对象必须是字符串的大小必须在制定的范围内 |
@NotBlank | 被注解的对象必须为字符串,不能为空,检查时会将空格忽略 |
@NotEmpty | 被注释的对象必须为空(数据:String,Collection,Map,arrays) |
@Range(min=, max=) | 被注释的元素必须在合适的范围内 (数据:BigDecimal, BigInteger, String, byte, short, int, long and 原始类型的包装类 ) |
@URL(protocol=, host=, port=, regexp=, flags=) | 被注解的对象必须是字符串,检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件 |
上面我简单罗列了一些常用校验的注解。
这里我们引入Hibernate Validator的jar包。
<mvc:annotation-driven></mvc:annotation-driven>
Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的Validator接口。只要在 Spring 的容器中定义一个 LocalValidatorFactoryBean,就可以将需要的验证注入Bean中使用。<mvc:annotation-driven/>会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上添加一个 @Valid 注解即可让 SpringMVC 在完成数据绑定后执行校验工作。
/**
* 添加员工
* @param employee
* @return
*/
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
System.out.println("save: " + employee);
if (result.getErrorCount() > 0) {
System.out.println("出错了!");
for(FieldError error:result.getFieldErrors()){
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
//若验证出错, 则转向定制的页面
map.put("departments", departmentDao.getDepartments());
return "input";
}
employeeDao.save(employee);
return "redirect:/emps";
}
Spring 在进行数据绑定同时可以调用校验框架进行数据校验。在SpringMVC中可以直接使用注解的方式进行校验。
SpringMVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型。Errors接口提供了获取错误信息的方法,getErrorCount()、getFieldError(String field)、getFieldErrors(), getFieldValue(String field),BindingResult 扩展了Errors接口
注:需校验的Bean对象和其校验结果或错误对象是成对出现的,它们之间不允许声明其他入参。
SpringMVC还会将错误信息保存到“隐含模型”中,可以在jsp页面上通过<form:error path=”userName”/>显示错误信息。