1. 配置
springMVC的核心是DispatcherServlet,它实现了HttpServlet类,在web.xml中引入配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springMVC.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Servlet容器tomcat在初始化DispatcherServlet时,会创建一个新的applicationContext(默认是XmlWebApplicationContext
)去加载参数 contextConfigLocation
指定的spring xml配置文件。
在基于注解的springMVC配置中,需要在spring配置文件中引入如下配置:
<context:component-scan base-package="..."/>
<mvc:annotation-driven/>
注解<mvc:annotation-driven/>
默认会在spring上下文中引入以下这些bean:
RequestMappingHandlerMapping
-
RequestMappingHandlerAdapter
两者配合处理基于注解的(@Controller
,@RequestMapping
等等)spring mvc使用RequestMappingHandlerMapping会读取spring上下文中所有使用
@Controller
或者@RequestMapping
注解的bean并解析他的方法作为handler -
BeanNameUrlHandlerMapping
处理Struct风格的url,即将url作为bean name获取到bean,这种时候一个url请求对应一个bean(即你的controller类),这个时候你的controller类可以实现AbstractController
抽象类BeanNameUrlHandlerMapping会读取spring上下文中所有名字以'/'开始的bean作为handler
SimpleControllerHandlerAdapter
配合BeanNameUrlHandlerMapping
一起使用。
2. SpringMVC工作流程
如下图:
DispatcherServlet作为前端控制器,请求会先经过它,进入到其
doDispatch
方法中,DispatcherServlet可以有多个HandlerMapping和HandlerAdapter,DispatcherServlet根据按序排列它们。DispatcherServlet会遍历所有HandlerMapping,并但返回第一个能够处理当前request的HandlerMapping对像,并调用其getHandler方法返回HandlerExecutionChain
HandlerExecutionChain由一个handler和若干HandlerInterceptor组成,其中handler其实就是用户的业务逻辑实现,由创建这个HandlerExecutionChain的HandlerMapping设置,比如RequestMappingHandlerMapping的handler其实就是Contorller的某个映射当前request url的method的封装。
DispatcherServlet调用所有HandlerInterceptor的preHandle处理request
-
HandlerAdaptor是HandlerExecutionChain的handler的适配器,对于有多个HandlerAdaptor的情况,返回第一个能够适配handler的适配器。
不同的适配器完成的功能不一样,一般都是在激活handler进入用户业务逻辑前做一些预处理,然后进入用户业务逻辑,最终返回ModelAnView对象。
RequestMappingHandlerAdapter
这种比较复杂的会处理@PathVariable
,@ModelAttribute
这个注解完成参数handler的参数的设置,然后调用handler的方法进入用户业务逻辑, 接下来调用HandlerExecutionChain中所有interceptor的postHandle
将ModelAndView交给ViewResolver处理
3. HandlerMapping
接口HandlerMapping只有一个接口方法:
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
getHandler里一般是判断能不能处理这个request,不能返回null,否则返回HandlerExecutionChain,类成员如下:
class HandlerExecutionChain{
// handler一般是用户业务逻辑的封装
private final Object handler;
// 拦截器
private HandlerInterceptor[] interceptors;
...
// 上面第2节中步骤 4,它依次调用interceptors中每一个interceptor的preHandle
// 只要有一个interceptor的preHandle返回false就不再走下去,直接返回false,本次对request处理结束,不再继续第2节中其他后续流程
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
// 上面第2节中步骤 6
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
}
-------拦截器HandlerInterceptor----------
public interface HandlerInterceptor {
// 返回false就不会调用后续拦截器,意味着本次request处理结束
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler);
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
// preHandler返回false,或者出现异常,或者正常返回时调用
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
3.1 HandlerMapping的一些实现类
1. RequestMappingHandlerMapping
它是用来处理使用了@Controller
或者@RequestMapping
注解的用户controller层面的业务逻辑类的。
像下面这样:
@Controller
@RequestMapping("/api/echo")
public class EchoTime {
@RequestMapping(value = "/currentDate", method = RequestMethod.GET, produces = "application/json")
public Date currentDate(){
Date d = new Date();
System.out.println("in currentDate");
return d;
}
}
RequestMappingHandlerMapping获取当前applicationContext中所有使用了@Controller
或者@RequestMapping
注解的bean,然后解析这些bean中使用了@RequestMapping
注解的方法,将这些方法封装成HandlerMethod
,然后建立url到这个HandlerMethod
的映射。
- RequestMappingHandlerMapping # getHandler返回的就是包装了HandlerMethod对象HandlerExecutionChain
- RequestMappingHandlerMapping返回的HandlerExecutionChain中interceptor处理用户在spring的配置文件中显示指定以外,应该默认会包含当前上下文中所有实现了MappedInterceptor的bean
2. BeanNameUrlHandlerMapping
它将url映射到bean。和在RequestMappingHandlerMapping中,一个url映射到一个contorller的方法不同。
同时BeanNameUrlHandlerMapping要求bean name必须是以'/'开始的。BeanNameUrlHandlerMapping默认会加载当前applicationContext中所有bean name以‘/’开始bean,然后以bean name作为url建立到bean的映射。 这种方式中一般要求用户的controller类实现Controller
接口,或者AbstractController
抽象类。如下所示:
// bean name要以‘/’开始,
@Controller("/test.do")
public class EchoController extends AbstractController {
protected ModelAndView handleRequestInternal(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date", "date");
return modelAndView;
}
}
BeanNameUrlHandlerMapping返回的HandlerExecutionChain的handler就是匹配url的bean的实例。
3. SimpleUrlHandlerMapping
这是一种很灵活的Handler Mapping,能够直接指定url到bean的映射, 可以在配置文件中指定:
<bean id="echoContorller" class="me.eric.springmvc.controller.EchoController"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<props>
<!--将url:/test.do映射到echoController处理-->
<prop key="/test.do">echoContorller</prop>
</props>
</property>
</bean>
4 HandlerAdaptor
接口HandlerAdaptor如下:
// 参数handler即HandlerExecutionChain中的handler, supports判断当前adaptor是否适配handler
boolean supports(Object handler);
// 使用handler处理请求
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
4.1 HandlerAdaptor实现类
1. RequestMappingHandlerAdapter
它能适配RequestMappingHandlerMapping返回的handler,即MethodHandler的实例(它封装了处理当前请求url的bean以及更加具体method的信息)
RequestMappingHandlerAdapter会处理@PathVariable
,@ModelAttribute
等注解。
它会先完成使用了@ModelAttribute
注解的方法的调用。然后调用url映射的具体方法的调用(调用前完成方法参数的绑定)。
2. SimpleControllerHandlerAdapter
它能够适配BeanNameUrlHandlerMapping
和SimpleUrlHandlerMapping
返回的handler(也就是controller bean的实例)
5. HandlerInterceptor
在HandlerAdaptor # handle前后调用,也就是在用户业务逻辑代码前后提供预处理和后处理的能力。
自定义interceptor需要实现接口HandlerInterceptor,然后在spring的xml文件中配置:
<bean id="myInterceptor" class="me.eric.springmvc.interceptors.MyInterceptor"/>
<mvc:interceptors>
<mvc:interceptor>
// 拦截这个地址
<mvc:mapping path="/test.do"/>
//不拦截这个地址
<mvc:exclude-mapping path="/test2.do"
<ref bean="myInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
上面这种写法,自定义拦截器myInterceptor会被包装成MappedInterceptor,看看MappedInterceptor的成员就明白了:
// url匹配这些模式的拦截
private final String[] includePatterns;
// url匹配这些模式的不拦截
private final String[] excludePatterns;
// 真正的拦截器
private final HandlerInterceptor interceptor;
所以如果你不使用<mvc:interceptors>
的形式定义拦截器,也可以直接使用MappedInterceptor作为bean,如下:
<bean class="org.springframework.web.servlet.handler.MappedInterceptor">
<constructor-arg index="0">
<array>
<value>/test.do</value>
</array>
</constructor-arg>
<constructor-arg index="1">
<ref bean="timerInterceptor"/>
</constructor-arg>
</bean>
上面不管哪种定义拦截器的方式,都会被RequestMappingHandlerMapping
,BeanNameUrlHandlerMapping
,SimpleUrlHandlerMapping
加载。
要想单独只被某个HandlerMapping加载,应该做如下配置:
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="myInterceptor"/>
</list>
</property>
</bean>
这样myInterceptor这个拦截器只会被这个BeanNameUrlHandlerMapping使用到。
注:如果按上面配置,同时xml文件里又存在<mvc:annotation-driven/>
,那么可能发现不生效,原因是因为<mvc:annotation-driven/>
会默认加载一个BeanNameUrlHandlerMapping
,而且它的优先级是2(越小越高),而你在配置文件里的BeanNameUrlHandlerMapping
默认优先级很低,前面说到DispatcherServlet会对HandlerMapping排序,所以它默认加载的总是会先使用。