当发送一个请求时,第一站是Spring的DispatcherServlet。与大多数基于Java的Web框架一样,Spring MVC所有的请求都会通过一个前端控制器Servlet。前端控制器是常用的Web应用程序模式.Spring MVC中,DispatcherServlet就是前端控制器。DispatcherServlet的任务是将请求发送给Spring MVC控制器。控制器是一个用于处理请求的Spring组件。DispatcherServlet以会查询一个或多个处理器映射(handler mapping)来确定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策。一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示,这些信息被称为模型(model)。信息需要发送给一个视图(view),通常是JSP。控制器所做的最后一件事就是将模型数据打包,并且表示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet。这样,控制器就不会与特定的视图相耦合,传递个DispatcherServlet的视图名并不直接表示某个特定的JSP/html。它仅仅传递一个逻辑名称,这个名称将会用来查找产生结果的真正试图。DispatcherServlet将会使用试图解析器(view resolver)来将逻辑试图名匹配为一个特定的试图实现。最后一站是试图的实现。在这里DispatcherServlet交付模型数据。试图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。
配置DispatcherServlet
Spring提供了AbstractAnnotationConfigDispatcherServletInitializer的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,也就是AbstractAnnotationConfigDispatherServletInitializer。
AbstractAnnotationConfigDispatherServletInitializer有三个方法:
getServletMappings():将一个或多个路径映射到DispatcherServlet上
getServletConfigClasses():要求DispatcherServlet加载应用上下文
当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。
getRootConfigClasses():返回配置ContextLoaderListener创建的应用上下文中的bean
ContextLoaderListener会加载包含Web组件的bean,如控制器,视图解析器以及处理器映射import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] {RootConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] {WebConfig.class}; } @Override protected String[] getServletMappings() { return new String[] {"/"}; }
@Override
protected Filter[] getServletFilters()[
return new Filter[]{new MyFilter[]};
} }
通过调用DefaultServletHanlderConfigurer的enable()方法,要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本来来处理此类请求。
Spittr应用有两个基本的领域概念:Spitter(应用的用户)和Spittle(用户发布的简短状态更新)。
SpringMVC允许以多种方式将客户端中国的数据传送到控制器的处理器方法中,包括:查询参数(Query Parameter),表单参数(Form Parameter)和路径变量(Path Variable)。
Spring支持Java校验API(Java Valildation API,又称JSR-303)。在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java API的实现即可。
Java校验API定义了多个注解
@AssertFalse 所注解的元素必须是Boolean类型,且值为false
@AssertTrue 所注解的元素必须是Boolean类型,且值为true
@DecimalMax 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString值
@DecimalMin 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString值
@Digits 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素的值必须是一个将来的日期
@Max 所注解的元素必须是数字,并且他的值要小于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull 所注解元素的值必须不能为null
@Null 所注解元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Pattern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String,集合或数组,并且它的长度要符合给定的范围
AbstractAnnotationConfigDispatcherServletInitializer可以做一些额外的配置
在AbstractAnnotationConfigDispatcherServletInitilizer将DispatcherServlet注册到Servlet容器后,会调用customizeRegistration(),并将Servlet注册后得到的Registration.Dynamic传递进来,通过重载customizeRegistration()方法,可以对DispatcherServlet进行额外的配置。
@Override protected void customizeRegistration(Dynamic registration){ registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads")); }
AbstractAnnotationConfigDispatcherServletInitializer会创建DispathcerServlet和ContextLoaderListener。当我们想向Web容器中注册其他组件时,只需要创建一个新的初始化容器就可以了,最简单的方式就是实现Spring的WebApplicationInitializer接口。
public class MyServletInitializer implements WebApplicationInitializer{ @Override public void onStartup(ServletContext servletContext) throws ServletException { Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class); Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class); myServlet.addMapping("/custom/**");
filter.addMappingForUrlPatterns(null, false, "/custom/**"); } }
在web.xml中搭建Spring MVC
<?xml version="1.0" encoding="UTF-8" ?> <web-app version="2.5" xmlns="http://java.sun.com/xml/nx/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <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> </web-app>
contextLoaderListener和DispatcherServlet会各自加载一个Spring应用上下文。上下文参数contextConfigLocation指定了一个XML文件(root-context.xml),它会被ContextLoaderListener加载。
DispatcherServlet会根据Servlet的名字找到一个文件,并基于该文件加载应用上下文。Servlet的名字是appServlet,因为DispatcherServlet会从“/WEB-INF/appServlet.xml”文件中加载器应用上下文。也可在Servlet上指定一个contextConfigLocation初始化参数。
<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>
设置web.xml使用基于Java的配置
<?xml version="1.0" encoding="UTF-8" ?> <web-app version="2.5" xmlns="http://java.sun.com/xml/nx/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name> <param-value>true</param-value> </context-param> <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>cherry.config.RootConfig</param-value> </context-param> <listener> <listener-class>org.springframework.web.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>cherry.config.WebConfig</param-value> <load-on-startup>1</load-on-startup> </init-param> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
DispatcherServlet并没有实现任何解析multipart请求数据的功能。它将该任务委托给了Spring中MultipartResolver策略接口的实现,通过这个实现类来解析multipart请求中的内容。从Spring3.1开始,Spring内置了两个MultipartResolver的实现供我们选择:
CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求
StandardServletMultipartResolver:依赖于Servlet3.0对multipart请求的支持
使用Servlet3.0解析multipart请求
1.在Spring应用上下文中声明bean
@Bean public MultipartResolver multipartResolver() throws IOException(){ return new StandardServletMultipartResolver(); }
2.在AnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer中重载customizeRegistration()方法来配置multipart具体细节--临时路径,上传文件最大容量,整个multipart请求最大容量,如果文件达到了一个指定最大容量,将会写入到临时路径中
@Override protected void customizeRegistration(Dynamic regsitration){ registration.setMultipartConfig(new MultipartConfigElement("tmp/spittr/uploads", 2097152, 4194304, 0)); }
3.使用web.xml配置MultipartConfigElement
<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>
配置Jakarta Commons FileUpload multipart解析器
在Spring应用上下文中声明bean并对其进行配置
@Bean public MultipartResolver multipartResolver(){ CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setUploadTempDir("tmp/spittr/uploads");
multipartResolver.setMaxUploadSize(2097152);
multipartResolver.setMaxInMemorySize(0);
retrun multipartResolver;
}
处理异常
Servlet请求的输出是一个Servlet相应,在处理异常时,输出仍是Servlet响应,一场必须要以某种方式转换为响应。
Spring提供了多种方式将异常转换为响应:
特定的Spring异常将会自动映射为指定的HTTP状态码
异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码
在方法上可以添加@ExceptionHandler注解,使其用来处理异常
异常处理的最简单方式是将其映射到HTTP状态码上,进而放到响应之中。
Spring会将自身的一些异常自动转换为合适的状态码
BindException 400 - Bad Request
HttpMessageNotReadbleException 400 - Bad Request
MethodArgumentNotValidException 400 - Bad Request
MissingServletRequestParameterException 400 - Bad Request
MissingServletRequestPartException 400 - Bad Request
TypeMismatcheException 400 - Bad Request
NoSuchRequestHandlingMethodException 404 - Not Found
HttpRequestMethodNotSupportedException 405 - Method Not Allowed
HttpMediaTypeNotAcceptableException 406 - Not Acceptable
HttpMediaTypeNotSupportedException 415 - Unsupported Media Type
ConversionNotSupportedException 500 - Internal Server Error
HttpMessageNotWritableException 500 - Internal Server Error
DispatcherServlet处理过程中或执行检验时出现问题时,Spring会抛出异常。如果DispatcherServlet无法找到合适处理请求的控制器方法,将会抛出NoSuchRequestHandlingMethodException异常,产生404状态码的响应
@ResponseStatus:将异常映射为特定的状态码
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found") public class SpittleNotFoundException extends RuntimeException{}
@ExceptionHandler会处理同意控制器中所有处理器方法所抛出的异常
@ExceptionHandler public String handleSpittleNotFoundException(){ return "error/spittleNotFound"; }
控制器通知(controller advice)是任意带有@ControllerAdvice注解的类。这个类会包含一个或多个如下类型的方法:
@ExceptionHandler注解标注的方法;
@InitBinder注解标注的方法;
@ModelAttribute注解标注的方法;
在带有ControllerAdvice注解的类中,上述方法会运用都整个应用程序素有控制器中带有@RequestMapping注解的方法上
@ControllerAdvice public class AppWebExceptionHandler { @ExceptionHandler(DuplicateSpittleException.class) public String handleDuplicationSpittle(){ return "error/duplicate"; } }
重定向
在处理完POST请求后,执行重定向可以防止用户点击浏览器的刷新按钮或后腿箭头时,客户端重新执行危险的POST请求。当控制器方法返回的String值以"redirect:"开头时,这个String不是用来查找视图的,而是用来知道浏览器进行重定向的路径。
当一个处理器方法完成之后,该方法所指定的模型数据将会复制到请求中,并作为请求中的属性,请求会转发(forward)到视图上进行渲染。因为控制器方法和视图所处理的是同一个请求,所有在转发的过程中,请求属性能够得以保存。
对于发起重定向的方法处理数据有两种方式:
使用URL模板以路径和/或查询参数的形式传递数据
通过flash属性发送数据
通过URL模板进行重定向
使用String直接连接是很危险的,如 return "redirect:/spitter/{username}";
Spring提供了使用模板的方法来定义重定向URL。
username作为占位符填充到了URL模板中,而不是直接连接到重定向String中。firstName属性没有匹配重定向URL中的任何占位符,所以它会自动以查询参数的形式附加到重定向URL上。所以下面URL的路径是"/spitter/username?firstName=firstName"
@RequestMapping(value = "register", method = RequestMethod.POST) public String processRegistration(Spitter spitter, Model model){ spitterRepository.save(spitter); model.addAttribute("username", spitter.getUsername); model.addAttribute("firstName", spitter.getFirstName); return "redirect:/spitter/{username}"; }
使用flash
Spring提供了将数据发送为flash属性的功能。flash属性会一直携带这些数据知道下一次请求,然后会消失。在重定向执行之前,所有的flash属性都会复制到会话中。在重定向后,存在会话中的flash属性会被取出,并从会话转移到模型之中。
@RequestMapping(value = "register", method = RequestMethod.POST) public String processRegistration(Spitter spitter, RedirectAttributes model){ spitterRepository.save(spitter); model.addAttribute("username", spitter.getUsername()); model.addFlashAttribute("spitter", spitter); return "redirect:/spitter/{username}"; } @RequestMapping(value = "/{username}" , method = RequestMethod.GET) public String showSpitterProfile(@PathVariable String username. Model model){ if(!model.containsAttribute("spitter")){ model.addAttribure(new Spitter()); } return "profile"; }