Spring MVC的高级技术
7.1.1 自定义DispatcherServlet配置
在SpittrWebAppInitializer中重载的三个方法是必须要重载的abstract方法。但是实际上有更多的方法进行重载,从而实现额外的配置。如:
customizeRegistration(Dynamic registration)
借助此方法中Dynamic对象,我们能够完成多项任务,包括设置load-on-startup优先级,设置初始化参数,配置对multipart的支持等
7.1.2 添加其他的Servlet和Filter
基于java的初始化器的好处就在于我们可以定义任意数量的初始化器类。因此,如果我们想往Web容器中注册其他组件的话,只需要创建一个新的初始化器就可以了。最简单的方式就是实现Spring的WebApplicationInitializer接口。
package com.myapp.config;
import *;
public class MyServletInitializer implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//创建新的Servlet
Dynamic myServlet = servletContext.addServlet("myServlet",myServlet.class);
myServlet.addMapping("/custom/**");
//创建新的Filter
javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter",MyFilter,class);
filter.addMappingForUrlPatterns(null,false,"/custom/**");
}
}
7.1.3 在web.xml中声明DispatcherServlet
在之前的描述中,AbstractAnnotationConfigDispatcherSetvletInitializer会自动注册DispatcherServlet和ContextLoaderListener.如果采用在web.xml中注册的话,需要我们手动完成这项任务。
<!-- 设置根上下文配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- 注册ContextLoaderListener -->
<listener>
<listener-class>
org.springframework.wen.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 注册DispatcherServlet,并映射到“/”-->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
以上xml配置的目的是让ContextLoaderListener和DispatcherServlet都从xml配置文件中加载各自的上下文:
+ ContextLoaderListener—>/WEB-INF/spring/root-context.xml
+ DispatcherServlet—>/WEB-INF/appServlet-context.xml
其中,/WEB-INF/appServlet-context.xml为根据servlet的名字自动加载的默认值,没错,可以通过配置进行修改:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/appServlet/servlet-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
上述操作中,两个上下文的配置信息都是从xml配置文件中读取的,但是,我们更倾向于使用java配置,方法是设置contextClass上下文参数以及DispatcherServlet的初始化参数,下面给出具体写法:
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.habuma.spitter.config.RootConfig</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.wen.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
com.habuma.spitter.config.WebConfig
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
7.2 处理multipart形式的数据
7.2.1 配置multipart解析器
multipart解析器:用来告诉DispatcherServlet该如何读取multipart请求
从Spring3.1开始,Spring内置了两个MultipartResolver的实现供我们选择:
+ CommonMultipartResolver
+ StandardServletMultipartResolver(依赖servlet3.0,始于Spring3.1)
StandardServletMultipartResolver
@Bean
public MultipartResolver multipartResolver() throws IOException{
return new StandardServletMultipartResolver();
}
bean定义方法没有属性和构造器参数,StandardServletMultipartResolver的配置不能在Spring中进行,而是需要在servlet中指定multipart的配置。事实上,如果不指定文件上传过程中的临时路径,StandardServletMultipartResolver是无法正在工作的。因此,我们要在web.xml或Servlet初始化类中,将multipart的具体细节作为DispatcherServlet配置的一部分。
@Override
protected void customizeRegistration(Dynamic registration){
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads",2097152,4194304,0)
);
}
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
<multipart-config>
<location>/tmp/spittr/uploads</location>
<max-file-size>2097152</max-file-size>
<max-request-size>4194304</max-request-size>
</multipart-config>
</servlet>
CommonMultipartResolver
7.2.2 处理multipart请求
7.3 处理异常
异常必须要以某种方式转换为响应。Spring提供了多种方式将异常转换为响应:
+ 特定的Spring异常会自动映射为指定的HTTP状态码
+ 异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码
+ 在方法上可以添加@ExceptionHandler注解,使其用来处理异常
7.3.1 将异常映射为HTTP状态码
将异常映射为特定的状态码:
package spottr.web;
import *;
@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="Spittle not found")
public class SpittleNotFoundException extends RuntimeException{}
7.3.2 编写异常处理的方法
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle(){
return "error/duplicate";
}
对于@ExceptionHandler注解标注的方法来说,比较有意思的一点在于它能够处理同一个控制器中所有处理器方法所抛出的异常。
7.4 为控制器添加通知
控制器通知:任意带有@ControllerAdvice注解的类,这个类会包含一个或多个如下类型的方法:
+ @ExceptionHandler注解标注的方法
+ @InitBinder注解标注的方法
+ @ModelAttribute注解标注的方法
package spitter.web;
import *;
@ControllerAdvice
public class AppWideExceptionHandler{
@ExceptionHandler(DuplicateSpittleException.class)
public String uplicateSpittleHandled(){
return "error/duplicate";
}
}
现在,如果任意的控制器方法抛出了DuplicateSpittleException,不管这个方法位于哪个控制器中,都会调用这个uplicateSpittleHandled()方法来处理异常
7.5 跨重定向请求传递数据
除了”redirect:String”,Spring为重定向功能还提供了一些其他的辅助功能。
7.5.1 通过URL模板进行重定向
@RequestMapping(value="/",method=POST)
public String processRegistration(Spitter spitter,Model model){
spitterRepository.save(spitter);
model.addAttribute("username",spitter.getUsername());
model.addAttribute("spitterId",spitter.getId());
return "redirect:/spitter/{username}";
}
因为模型中的spitterId属性没有匹配重定向URL中的任何占位符,所以它会自动以查询参数的形式附加到重定向URL上。
7.5.2 使用flash属性
@RequestMapping(value="/",method=POST)
public String processRegistration(Spitter spitter,RedirectAttributes model){
spitterRepository.save(spitter);
model.addAttribute("username",spitter.getUsername());
model.addFlashAttribute("spitter",spitter);
return "redirect:/spitter/{username}";
}
我们传递了一个Spitter对象给addFlashAttribute()方法,在重定向之前,所有的flash属性都会复制到会话中,在重定向之后,存在会话中的flash属性会被取出,并从会话转移到模型之中。处理重定向的方法就能从模型中访问Spitter对象了