Spring MVC的高级技术
学习内容
Spring MVC配置的替代方案
处理文件上传
在控制器中处理异常
使用flash属性
Spring MVC配置的替代方案
尽管对很多Spring应用来说,使用AbstractAnnotationConfigDispatcherServletInitializer基本能够满足我们的需求,但并不一定总能满足我们的要求,除了DispatcherServlet以外,我们可能还需要额外的Servlet和Filter;我们可能还需要对DispatcherServlet本身做一些额外的配置;或者,如果我们需要将应用部署到Servlet3.0之前的容器中,那么还需要将DispatcherServlet配置到传统的web.xml中。
1、自定义DispatcherServlet配置
AbstractAnnotationConfigDispatcherServletInitializer的方法之一customizeRegistration()。在AbstractAnnotationConfigDispatcherServletInitializer将DispatcherServlet注册到Servlet容器中之后,就会调用customizeRegistration(),并将Servlet注册后得到的Registration.Dynamic传递进来。通过重载customizeRegistration()方法,我们可以对DispatcherServlet进行额外的配置。
Spring MVC中处理multipart请求和文件上传。如果计划使用Servlet3.0对multipart配置的支持,那么需要使用DispatcherServlet的Registration来启用multipart请求。我们可以重载customizeRegistration()方法来设置MultipartConfigElement。
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
}
借助customizeRegistration()方法中的ServletRegistration.Dynamic,我们能够完成多项任务,包括通过调用setLoadOnStartup()设置load-on-startup优先级,通过setInitParameter()设置初始化参数,通过调用setMutipartConfig()配置Servlet 2.0对mutipart的支持。在前面的样例中,我们设置了对Multipart的支持,将上传文件的临时存储目录设置在“/tmp/spittr/uploads”中。
2、添加其他的Servlet和Filter
基于Java的初始化器的一个好处就在于我们可以定义任意数量的初始化器类。因此,如果我们想往web容器中注册其他组件的话,只需要创建一个新的初始化器就可以了。最简单的方式就是实现Spring的WebApplicationInitializer接口。
通过实现WebApplicationInitializer来注册Servlet
package spittr.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
public class MyServletInitializer implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
Dynamic myServlet =
servletContext.addServlet("myServlet", MyServlet.class);
myServlet.addMapping("/custom/**");
}
}
他注册了一个Servlet并将其映射到一个路径上。
类似地,我们还可以创建新的WebApplicationInitializer实现来注册Listener和Filter。
注册Filter的WebApplicationInitializer
package spittr.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
public class MyServletInitializer implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
Dynamic myServlet =
servletContext.addServlet("myServlet", MyServlet.class);
myServlet.addMapping("/custom/**");
javax.servlet.FilterRegistration.Dynamic filter =
servletContext.addFilter("myFilter", MyFilter.class);
filter.addMappingForUrlPatterns(null, false, "/custom/**");
}
}
如果要将应用部署到支持Servlet3.0的容器中,那么WebApplicationInitializer提供了一种通用的方式,实现在Java中注册Servlet、Filter和Listener。不过,如果你只是注册Filter,并且该Filter只会映射到DispatcherServlet上的话,那么在AbstractAnnotationConfigDispatcherServletInitializer中还有一种快捷方式。
为了注册Filter并将其映射到DispatcherServlet,所需要做的仅仅是重载AbstractAnnotationConfigDispatcherServletInitializer的getServletFilters()方法。
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new MyFilter() };
}
3、在web.xml中声明DispatcherServlet
如下是一个基本的web.xml文件,他按照传统的方式搭建了DispatcherServlet和ContextLoaderListener
处理Multipart形式的数据
1、背景
在web应用中,允许用户上传内容是很常见的需求。Multipart格式的数据会将一个表单拆分为多个部分(part),每个部分对应一个输入域。在一般的表单输入域中,他所对应的部分中会放置文本型数据,但是如果上传文件的话,他所对应的部分可以是二进制。
尽管Multipart请求看起来很复杂,但在Spring MVC中处理他们却很容易。在编写控制器方法处理文件上传之前,我们必须要配置一个Multipart解析器,通过他来告诉DispatcherServlet该如何读取Multipart请求。
2、配置Multipart解析器
DispatcherServlet并没有实现任何解析Multipart请求数据的功能。他将该任务委托给了Spring中MultipartResolver策略接口的实现,通过这个实现类来解析Multipart请求中的内容。从Spring3.1开始,Spring内置了两个MultipartResolver的实现供我们选择:
CommonsMultipartResolver:使用Jakarta Commons FileUpload解析Multipart请求;
StandardServletMultipartResolver:依赖于Servlet3.0对Multipart请求的支持(始于Spring3.1)。
一般来讲,StandardServletMultipartResolver可能会是优选的方案。
使用Servlet3.0解析Multipart请求
在Spring应用上下文中,声明Bean
@Bean
public MultipartResolver multipartResolver() throws IOException {
return new StandardServletMultipartResolver();
}
要在Servlet中指定Multipart的配置
采用Servlet初始化类的方式来配置DispatcherServlet的话,实现了WebApplicationInitializer
public class MyServletInitializer implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
DispatcherServlet ds = new DispatcherServlet();
Dynamic myServlet =servletContext.addServlet("appServlet", ds);
myServlet.addMapping("/");
myServlet.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploades"));
}
}
如果我们配置DispatcherServlet的Servlet初始化类继承了AbstractAnnotationConfigDispatcherServletInitializer的话:
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
}
3、处理Multipart请求
Thymeleaf在很大程度上就是HTML文件,与JSP不同,他没有什么特殊的标签或标签库。Thymeleaf之所以能够发挥作用,是因为他通过自定义的命名空间,为标准的HTML标签集合添加Thymeleaf属性。
命名空间为:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
th:href属性的特殊之处在于他的值中可以包含Thymeleaf表达式,用来计算动态的值。他会渲染成一个标准的href属性,其中会包含在渲染时动态创建得到的值,这是Thymeleaf命名空间中很多属性的运行方式:他们对应标准的HTML属性,并且具有相同的名称,但是会渲染一些计算后得到的值。
借助Thymeleaf实现表单绑定
<form method="POST" th:object="${spitter}">
<div class="errors" th:if="${#fields.hasErrors('*')}">
<ul>
<li th:each="err : ${#fields.errors('*')}"
th:text="${err}">Input is incorrect</li>
</ul>
</div>
<label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>:
<input type="text" th:field="*{firstName}"
th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('lastName')}? 'error'">Last Name</label>:
<input type="text" th:field="*{lastName}"
th:class="${#fields.hasErrors('lastName')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('email')}? 'error'">Email </label>:
<input type="text" th:field="*{email}"
th:class="${#fields.hasErrors('email')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('username')}? 'error'">Username </label>:
<input type="text" th:field="*{username}"
th:class="${#fields.hasErrors('username')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('password')}? 'error'">Password </label>:
<input type="password" th:field="*{password}"
th:class="${#fields.hasErrors('password')}? 'error'" /><br/>
<input type="submit" value="Register" />
</form>
${}表达式是变量表达式,
*{}表达式是选择表达式,
变量表达式是基于整个SpEL上下文计算的,而选择表达式是基于某一个选中对象计算的。