- Spring MVC通常的执行流程是:当一个Web请求被发送给Spring MVC Application,Dispatcher Servlet接收到这个请求,通过HandlerMapping找到Controller,将这个请求委派给Controller的某个Handler Method处理,这个Handler Method处理完这个请求,返回一个ModelAndView给Dispatcher Servlet,Dispatcher Servlet利用View Name,请求View Resolver,View Resolver返回相应的view;Dispatcher Servlet拿到View后,将Model传递给View,然后将Response返回;
- 对于每一个Controller里面的Handler Method,它可以接收以下的参数:
- HttpServletRequest或HttpServletResponse类型的对象;
- 任意类型的请求参数,使用@RequestParam标注,这些请求参数的值来自于Url中的request parameter;
- 任意类型的Path Variable,使用@PathVariable来标注,这些参数的值来自于请求Path中的一部分,在使用这个的前提是你必须在这个Handler Method标注的@RequestMapping里面使用占位符;
- Model类型的对象;你可以向这个对象里面添加attributes;
- 任意类型的Cookie属性值,使用@CookieValue标注;
- Map或者ModelMap类型的对象,Handler Method可以用来向Model里面添加属性;
- Errors或者BindingResult类型的对象,Handler Method用来访问Model对象的验证结果;
- SessionStatus类型的对象,Handler Method可以用来设置一个Session的完成;
Handler Method可以有两种类型的返回值:
- 一种是String,表示视图名;
- 另外一种是void,这要取决于Handler Method接收的参数,如果其接收了ModelAndView类型的参数,并在这个参数中设置了视图名,那么这个视图名将被以后的View Resolver所使用,否则Spring会根据在@RequestMapping中配置的URL,将URL最后那部分默认的视图名,如果@RequestMapping中配置的URL最后那部分是一个通配符(*),则会将当前Handler Method的方法名作为View Name;
- 在一个基于SpringMVC的Web应用程序里面,一般有两种配置文件:
- 一个是Web Application的配置文件(web.xml),你需要在里面指定一个或者多个servlet实例以及url到servlet的映射关系:
<servlet>
<servlet-name>servlet name</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servlet name</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>在比较大的应用程序里,指定多个DispatcherServlet实例尤其重要,这样可以让每个DispatcherServlet服务于不同的功能,拥有不同的并完全隔离的Spring Container;
- 另外一个是Spring MVC配置文件,在没指定的情况下,每一个DispatcherServlet都会去WEB-INF目录下加载一个名为{servlet name}-servlet.xml的Spring配置文件;你也可以通过一个名为contextConfigLocation的servlet parameter来指定;
你还可以指定一个Root Application的配置文件,这个配置文件中的bean会被每一个DispatcherServlet的配置文件中的bean访问或者复写,这个配置文件必须通过一个ContextLoaderListener类型的Listener来加载,你可以通过一个名为contextConfigLocation的context parameter来指定这个配置文件名,默认情况下这个文件是/WEB-INF/applicationContext.xml。
- 一个是Web Application的配置文件(web.xml),你需要在里面指定一个或者多个servlet实例以及url到servlet的映射关系:
- 在你在创建Web Application的Controller之前,你需要在Application Context配置文件中添加<context:component-scan base-package="…" />去Scan所有的标记有@Controller和@RequestMapping annotations的类;或者在你的配置类上加上@ComponentScan annotation,并设置basePackages属性;
除此之外,我们需要让Spring MVC能预注册一些Bean,所以我们需要在Application Context配置文件中加上<mvc:annotation-driven />;你也可以在你的配置类上加上@EnableWebMvc annotation达到相同的效果; - 在Handler Method返回视图名之后,你需要在Application Context配置文件中添加一个ViewResolver的Bean,这个Bean负责将一个View name解析为一个View的实现;
- 如果我们要将一个请求映射到Controller中的一个方法,我们必须要将这个方法用@RequestMapping annotation来标注,并在这个annotation中指定映射的路径,这个路径可以是一个明确的路径,也可以包含通配符,也可以包含一些Path Variable的占位符;如果在@RequestMapping annotation中没有指定路径,则默认这个Handler Method将使用其所在Controller上配置的@RequestMapping annotation中指定的路径,你也可以在@RequestMapping中限定Request Type;
Servlet API提供了Servlet Filter在Servlet被执行之前或者执行之后去拦截Web请求,Spring MVC也提供了名为Handler interceptor的组件,它用来在一个请求被Handler Method处理之前或者之后,或者在视图已经被渲染之后去拦截这个请求;Handler interceptor相对于Servlet Filter来说,它被配置在Application Context里面,所以它能利用很多Spring Container的特征,比如说可以用来引用Spring Container中的bean;为了定义一个Handler interceptor,我们要么是实现HandlerInterceptor接口,要么是继承HandlerInterceptorAdapter类;它包含3个Method:
- preHandle()这个方法用于在Handler Method被执行之前被调用,一般返回true,否则DispatcherServlet会认为这个方法已经处理了这个请求,可以立刻将Response返回给用户;
- postHandler()方法用于在Handler Method被执行之后被调用,在这个方法里面我们能够访问或者修改在Handler Method中生成的ModelAndView;
- afterCompletion()方法用于在整个请求流程完成之后被调用;
为了注册一个Interceptor到Spring Container中,我们要做一些特殊的操作:
- 首先,我们需要将这个自定义的Handler interceptor在配置类中定义为一个bean;
- 让配置类继承自WebMvcConfigurerAdapter类,并复写addInterceptors方法,该方法包含一个InterceptorRegistry类型的参数;
- 调用InterceptorRegistry.addInterceptor()方法传入Handler interceptor Bean;
默认情况下Handler Interceptor会应用到所有@Controller中的所有Handler Methods,我们也可以在addInterceptor()方法后调用addPathPatterns()去指明这个Handler Interceptor应该用到哪些URL,当然我们也可以调用excludePathPatterns()方法用于指明哪些URLs它不应该用到;
- AcceptHeaderLocaleResolver:该Resolver通过读取Http Request中的accept-language header信息来获取客户端Locale信息;
- SessionLocaleResolver:从Session中读取一个预定义的属性值,如果这个属性不存在,则从Http Request中的accept-language header中读取;你也可以通过SessionLocaleResolver的DefaultLocale属性来设置一个默认值;
- CookieLocaleResolver:从用户浏览器的Cookie中读取一个属性值,如果这个属性不存在,则从Http Request中的accept-language header中读取;你可以通过设置CookieLocaleResolver的cookieName和cookieMaxAge属性来设置这个Cookie的属性名和最大的过期时间;你也可以通过SessionLocaleResolver的DefaultLocale属性来设置一个默认值;
如何修改一个Request中的Locale,你可以通过直接调用LocaleResolver.setLocale()方法,也可以注册一个LocaleChangeInterceptor并将其应用到所有的请求,在LocaleChangeInterceptor中你可以通过一个paramName属性指定一个参数名,当LocaleChangeInterceptor在当前请求中检测到有这个参数出现时,则会使用这个参数的值作为当前请求的Locale;
- InternalResourceViewResolver:通过指定一个Prefix和Sufix属性将Handler Method返回的View name拼凑成一个View文件的路径,然后再找到该视图文件编译后生成一个类型为JstlView的对象,我们也可以通过设置InternalResourceViewResolver的viewClass属性来设置该对象的类型;InternalResourceViewResolver比较简单,但是只能在找到整个应用程序中内部存在的资源视图;InternalResourceViewResolver还可以解析Redirect视图,如果某个Handler Method返回一个redirect:XXX的视图名,InternalResourceViewResolver会将这个视图名解析为到redirect:后面那一部分代表的URL的请求;
- XmlViewResolver:我们可以将视图以bean的形式创建在Spring Container中,然后我们可以使用XmlViewResolver将视图名解析为一个View Bean;这些View Bean可以定义在当前的配置文件中,也可以定义在一个独立的Spring配置文件中,我们可以通过设置XmlViewResolver的location属性指定这个配置文件;默认是/WEB-INF/views.xml;
- ResourceBundleViewResolver:我们可以通过ResourceBundleViewResolver来将一个Viewname解析为一个View对象,需要为ResourceBundleViewResolver对象的baseName属性指定一个Resource Bundle,这个Resource Bundle文件的格式应该是:
{View name}.(class)={View class full name}
{View name}.url={View file path}
在一个SpringMVC的Web Application中可以创建多个ViewResolver的Bean,如果这种情况存在,你需要指定每一个ViewResolver的在Resolve View时的顺序,具体的做法是设置ViewResolver的order属性,这个属性的值越小代表具有越高的优先级;
- 用户请求的URL中的扩展名;
- 用户请求中的HTTP Accept Header。
在视图页面,异常能通过能通过变量${exception}来访问;
为了将Exception Resolver注册到Spring Container中,我们需要让配置类继承自WebMvcConfigurerAdapter,然后通过复写configureHandlerExceptionResolvers方法把Exception Resolver注册到这个添加到这个方法的参数里;
注意,通过Exception Resolver方式能捕获Spring Container中所有Handler Method抛出的异常,如果仅仅想针对某一个Controller中所有Handler Method抛出的异常,我们可以在Controller中创建一个public方法,并标记上@ExceptionHandler annotation,这个annotation中可以指定要捕获的异常,如果不指定,则默认是所有异常;这个Public方法可以包含一个异常类型的参数,并返回一个字符串值,用于指定异常发生后要渲染的View Name;尽管使用@ExceptionHandler标记方法是非常的灵活和强大,但是缺点是你不得不把他们放在Controller里,并且仅仅针对controller中的Handler Method有效;如果我们想让它对其它所有的Controller中的Handler Method起作用,我们可以将它们提取到一个标记有@ControllerAdvice annotation的类中;