一、Spring MVC概念
MVC即是模型(Model)、视图(View)、控制器(Controller)的简称。其中M负责对应用数据进行封装,并向外提供应用功能的接口;V负责通过向控制器发送请求,得到响应结果,并向用户展示处理好的的应用数据;C这是用于定义应用的功能,接收用户的动作,并选择相应的视图。Spring MVC就相当于Spring的一个处理模型、视图和控制器的框架,下面我们来分析Spring MVC是如何处理请求,封装数据,再以特定的视图展示给用户的。
二、使用ContextConfigLocation在Web容器中使用IoC容器建立上下文
首先看基本的配置:
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- spring mvc的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
先讲讲两个很重要的类,DispatcherServlet这个是Spring MVC的关键类,起着分发请求的作用,ContextConfigLocation是一个用于完成IoC容器在Web环境中的启动工作。通过ContextConfigLocation在Web环境中建立起一个上下文环境,然后把DispatcherServlet作为Spring MVC处理Web请求的转发器,从而完成相应前端http请求的准备工作。
在ContextConfigLocation可以看到两个核心的方法:
//在Web环境中初始化上下文
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
//销毁上下文
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
其中ContextConfigLocation启动的上下文为根上下文,同时还会创建一个用于保存DispatcherServlet所需要的MVC对象的子上下文,从而构成一个完成的上下文体系。这个上下文体系的建立是交给ContextConfigLocation的基类ContextLoader完成:
//创建子上下文
this.context = this.createWebApplicationContext(servletContext);
//加载并创建根上下文
ApplicationContext parent = this.loadParentContext(servletContext);
在ContextLoader中,完成了两个IoC容器(应用上下文)建立的基本过程,一个是在Web容器中建立起的双亲IoC容器,另一个是生成相应的WebApplicationContext并将其初始化。
三、DispatcherServlet的启动和初始化
DispatcherServlet作为一个Servlet,自然启动和初始化的过程与init()方法密切相关,下面示其基类HttpServletBean中定义的init()方法部分代码:
//获取Servlet的基本配置,同时对bean的属性进行设置
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
//对bean进行初始化
this.initServletBean();
接下来,对前文中所提到的DispatcherServlet所持有的子上下文进行初始化,根上下文是与整个的Web应用相对应着的一个上下文,而子上下文则是和一个Servlet相对应的上下文。同时,由于IoC容器中获取bean时,会先去其双亲上下文中getBean(),因此在根上下文中所定义的bean会在子下文中得到共享。至此完成了整个MVC的上下文建立过程。
四、HandlerMapping的实现原理(M与C的设计原理)
在前文的初始化过程中,在上下文环境中已定义的HandlerMapping都已被加载了,这些都被有序的放在在一个List中,其中存储这Http请求对应的映射数据。
private List<HandlerMapping> handlerMappings;
this.handlerMappings = new ArrayList(matchingBeans.values());
//排序处理
AnnotationAwareOrderComparator.sort(this.handlerMappings);
以SimpleUrlHandlerMapping为例,在SimpleUrlHandlerMapping中维持着一个LinkedHashMap去持有URL请求和控制器的映射关系,因此Spring MVC可以通过一个Http请求去找到对应的Controller。
private final Map<String, Object> urlMap = new LinkedHashMap();
在HandlerMapping中定义了一个getHandler()方法,用于获取与http请求对应的HandlerExecutionChain:
HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
其中HandlerExecutionChain持有一个Interceptor链和一个handler对象,而handler对象即是http请求所对应着的Controller,通过拦截器链上的拦截器可以对handler对象的功能提供增强处理,类似于AOP中拦截器链的功能:
public class HandlerExecutionChain {
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
//Controller对象
private final Object handler;
//拦截器链
private HandlerInterceptor[] interceptors;
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex;
//将配置好的Handler和拦截器链转化为HandlerExecutionChain的内部属性
public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
this.interceptorIndex = -1;
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain)handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
} else {
this.handler = handler;
this.interceptors = interceptors;
}
}
下面来分析是如何将http请求与handler对象注册在handlerMap当中,以SimpleUrlHandlerMapping为例:
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
String url;
Object handler;
if (urlMap.isEmpty()) {
this.logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
} else {
//对urlMap中的bean进行解析,并调用基类的registerHandler完成注册
for(Iterator var2 = urlMap.entrySet().iterator(); var2.hasNext(); this.registerHandler(url, handler)) {
Entry<String, Object> entry = (Entry)var2.next();
url = (String)entry.getKey();
handler = entry.getValue();
if (!url.startsWith("/")) {
url = "/" + url;
}
if (handler instanceof String) {
handler = ((String)handler).trim();
}
}
}
}
基类AbstractUrlHandlerMapping中的registerHandler():
//持有映射关系
private final Map<String, Object> handlerMap = new LinkedHashMap();
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException("Cannot map " + this.getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + this.getHandlerDescription(mappedHandler) + " mapped.");
}
}
//处理URL是"/"的映射,并把映射的Controller设置到rootHandler中
else if (urlPath.equals("/")) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Root mapping to " + this.getHandlerDescription(handler));
}
this.setRootHandler(resolvedHandler);
}
//处理是"/*"的映射,并把响应的Controller设置到DefaultHandler中
else if (urlPath.equals("/*")) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Default mapping to " + this.getHandlerDescription(handler));
}
this.setDefaultHandler(resolvedHandler);
}
//处理正常的映射
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (this.logger.isInfoEnabled()) {
this.logger.info("Mapped URL path [" + urlPath + "] onto " + this.getHandlerDescription(handler));
}
}
至此通过Spring MVC中通过上文的handlerMap持有http请求与Controller的映射关系,每当有请求到来,都可以找到相应的handler了。而对于请求的处理,是在doService方法中完成的:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
Map<String, Object> attributesSnapshot = null;
...
//对http请求进行处理,设置上下文,解析器...
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
//正式处理和分发请求的入口
this.doDispatch(request, response);
}
...
}
下面示doDispatch()方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//从http请求中获取相应的handler
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//选择适应的HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//处理handler并将处理结果封装到ModelAndView对象中
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
至此,M生成完成,下面看看C如何生成:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//遍历Map中的HandlerMapping
Iterator var2 = this.handlerMappings.iterator();
HandlerExecutionChain handler;
do {
if (!var2.hasNext()) {
return null;
}
HandlerMapping hm = (HandlerMapping)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
//根据http请求从HandlerMapping之中取得相对应的Handler即Controller对象,并将其和拦截器链一起封装到HandlerExecutionChain中
handler = hm.getHandler(request);
} while(handler == null);
return handler;
}
到此C也实现完成了。
五、视图呈现的实现原理(V的设计原理)
对于视图的呈现和处理,依旧是从doDispatch()为入口,具体的实现又是在render()方法之中完成,如下:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
//根据ModelAndView中设置的视图名称进行解析,得到对应的视图对象
//ModelAndView中的仅仅包含的View对象的名字,View还未解析过
if (mv.isReference()) {
view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
//已经解析过,直接取出View对象
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
//调用view的render()方法对数据进行呈现,并通过HttpResponse对象把视图呈现给客户端
view.render(mv.getModelInternal(), request, response);
} catch (Exception var7) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var7);
}
throw var7;
}
}
那么又是如何结合解析和生成View对象的,如下:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
//遍历已有的视图解析器
Iterator var5 = this.viewResolvers.iterator();
View view;
do {
if (!var5.hasNext()) {
return null;
}
ViewResolver viewResolver = (ViewResolver)var5.next();
//选择对应的视图解析器进行解析
view = viewResolver.resolveViewName(viewName, locale);
} while(view == null);
return view;
}
以BeanNameViewResolver为例,直接从已有上下文中通过名称的对应关系将作为View对象的Bean取出:
public View resolveViewName(String viewName, Locale locale) throws BeansException {
//获取上下文
ApplicationContext context = this.getApplicationContext();
if (!context.containsBean(viewName)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No matching bean found for view name '" + viewName + "'");
}
return null;
} else if (!context.isTypeMatch(viewName, View.class)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found matching bean for view name '" + viewName + "' - to be ignored since it does not implement View");
}
return null;
} else {
//从上下文中根据配置的Bean名称取出相应的Bean对象,并转化为View对象
return (View)context.getBean(viewName, View.class);
}
}
至此,View对象生成以及通过render()方法对数据进行呈现,并通过HttpResponse对象把视图呈现给客户端完成。
六、总结
本文分析了Spring MVC中三个模块的实现原理,以及如何处理请求,找到相应的Handler,最后将视图呈现给客户端。以后将详细总结Spring MVC具体的请求流程
参考文献:《Spring技术内幕》计文柯