SpringMVC在使用過程中,大多是使用注解,對它的實現接口之類的關系理解變得模糊, 通過對XML配置的理解,可以理清各個類的關系,譬如控制器類要實現Controller接口。
接觸SpringMVC,對它的xml文件配置一直比較模模糊糊,最近花了一點時間稍微看了下源代碼,再加上調試,開始逐漸理解它,網上的類似的內容有很多,寫本文主要是自己加深一下理解。本文適合用過SpringMVC的開發者,言歸正傳,首先搭建一個最簡單的工程體驗一下。
該工程是基於maven的,pom配置不再說明,所使用的spring版本4.0.5。
首先是web.xml文件配置,最簡單的配置
- <!DOCTYPE web-app PUBLIC
- "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd" >
- <web-app>
- <display-name>Archetype Created Web Application</display-name>
- <servlet>
- <servlet-name>mvc</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>mvc</servlet-name>
- <url-pattern>/*</url-pattern>
- </servlet-mapping>
- </web-app>
然后是mvc-servlet.xml文件的配置,上面配置DispatcherServlet會默認加載[servlet-name]-servlet.xml文件。對於我的配置,會去加載mvc-servlet.xml文件。
mvc-servlet.xml文件的內容:
- <?xml version="1.0" encoding="UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
- http://www.springframework.org/schema/mvc
- http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
- http://www.springframework.org/schema/util
- http://www.springframework.org/schema/util/spring-util-2.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.2.xsd">
- <bean name="/index" class="com.lg.mvc.HomeAction"></bean>
- <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
- <property name="templateLoaderPath" value="/WEB-INF/views" />
- <property name="defaultEncoding" value="utf-8" />
- <property name="freemarkerSettings">
- <props>
- <prop key="locale">zh_CN</prop>
- </props>
- </property>
- </bean>
- <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
- <property name="suffix" value=".html" />
- <property name="contentType" value="text/html;charset=utf-8" />
- <property name="requestContextAttribute" value="request" />
- <property name="exposeRequestAttributes" value="true" />
- <property name="exposeSessionAttributes" value="true" />
- </bean>
- </beans>
在該配置中定義了一個HomeAction的Bean。內容為:
- package com.lg.mvc;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.web.servlet.ModelAndView;
- import org.springframework.web.servlet.mvc.Controller;
- public class HomeAction implements Controller{
- @Override
- public ModelAndView handleRequest(HttpServletRequest request,
- HttpServletResponse response) throws Exception {
- return new ModelAndView("hello");
- }
- }
這是最原始的mvc做法,要繼承Controller接口,先從原始的說起,最后再過渡到@Controller和@RequestMapping注解式的配置。它在mvc-serlet.xml文件中的配置有一個關鍵的屬性name="/index"。
WEB-INF/view目錄下有一個簡單的hello.html,內容為:
- <html>
- <head>
- </head>
- <body>
- hello lg !
- </body>
- </html>
至此該工程就寫完了,部署到tomcat中,項目路徑為/,運行一下。
訪問 http://localhost:8080/index
至此整個工程就算搭建成功了。
下面就要說說原理了。
用過python Django框架的都知道Django對於訪問方式的配置就是,一個url路徑和一個函數配對,你訪問這個url,就會直接調用這個函數,簡單明了。對於java的面向對象來說,就要分兩步走。第一步首先要找到是哪個對象,即handler,本工程的handler則是HomeAction對象。第二步要找到訪問的函數,即HomeAction的handleRequest方法。所以就出現了兩個源碼接口 HandlerMapping和HandlerAdapter,前者負責第一步,后者負責第二步。借用網上的SpringMVC架構圖。

HandlerMapping接口的實現(只舉了我認識的幾個) :
-
BeanNameUrlHandlerMapping :通過對比url和bean的name找到對應的對象
-
SimpleUrlHandlerMapping :也是直接配置url和對應bean,比BeanNameUrlHandlerMapping功能更多
-
DefaultAnnotationHandlerMapping : 主要是針對注解配置@RequestMapping的,已過時
-
RequestMappingHandlerMapping :取代了上面一個
HandlerAdapter 接口實現:
-
HttpRequestHandlerAdapter : 要求handler實現HttpRequestHandler接口,該接口的方法為 void handleRequest(HttpServletRequest request, HttpServletResponse response)也就是 handler必須有一個handleRequest方法
-
SimpleControllerHandlerAdapter:要求handler實現Controller接口,該接口的方法為ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response),也就是本工程采用的
-
AnnotationMethodHandlerAdapter :和上面的DefaultAnnotationHandlerMapping配對使用的,也已過時
-
RequestMappingHandlerAdapter : 和上面的RequestMappingHandlerMapping配對使用,針對@RequestMapping
先簡單的說下這個工程的流程,訪問http://localhost:8080/index首先由DispatcherServlet進行轉發,通過BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),找到了HomeAction,然后再拿HomeAction和每個adapter進行適配,由於HomeAction實現了Controller接口,所以最終會有SimpleControllerHandlerAdapter來完成對HomeAction的handleRequest方法的調度。然后就順利的執行了我們想要的方法,后面的內容不在本節中說明。
了解了大概流程,然后就需要看源代碼了。
首先就是SpringMVC的入口類,DispatcherServlet,它實現了Servlet接口,不再詳細說DispatcherServlet的細節,不然又是一大堆的內容。每次請求都會調用它的doService->doDispatch,我們關注的重點就在doDispatch方法中。
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HttpServletRequest processedRequest = request;
- HandlerExecutionChain mappedHandler = null;
- boolean multipartRequestParsed = false;
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- try {
- ModelAndView mv = null;
- Exception dispatchException = null;
- try {
- processedRequest = checkMultipart(request);
- multipartRequestParsed = (processedRequest != request);
- //這個是重點,第一步由HandlerMapping找到對應的handler
- // Determine handler for the current request.
- mappedHandler = getHandler(processedRequest);
- if (mappedHandler == null || mappedHandler.getHandler() == null) {
- noHandlerFound(processedRequest, response);
- return;
- }
- // Determine handler adapter for the current request.
- //這是第二步,找到合適的HandlerAdapter,然后由它來調度執行handler的方法
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- // Process last-modified header, if supported by the handler.
- String method = request.getMethod();
- boolean isGet = "GET".equals(method);
- if (isGet || "HEAD".equals(method)) {
- long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
- if (logger.isDebugEnabled()) {
- logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
- }
- if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
- return;
- }
- }
- if (!mappedHandler.applyPreHandle(processedRequest, response)) {
- return;
- }
- try {
- // Actually invoke the handler.
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- }
- finally {
- if (asyncManager.isConcurrentHandlingStarted()) {
- return;
- }
- }
- applyDefaultViewName(request, mv);
- mappedHandler.applyPostHandle(processedRequest, response, mv);
- }
- catch (Exception ex) {
- dispatchException = ex;
- }
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- }
- catch (Exception ex) {
- triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
- }
- catch (Error err) {
- triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
- }
- finally {
- if (asyncManager.isConcurrentHandlingStarted()) {
- // Instead of postHandle and afterCompletion
- mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
- return;
- }
- // Clean up any resources used by a multipart request.
- if (multipartRequestParsed) {
- cleanupMultipart(processedRequest);
- }
- }
- }
第一步詳細查看:
- protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- for (HandlerMapping hm : this.handlerMappings) {
- if (logger.isTraceEnabled()) {
- logger.trace(
- "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
- }
- HandlerExecutionChain handler = hm.getHandler(request);
- if (handler != null) {
- return handler;
- }
- }
- return null;
- }
可以看到就是通過遍歷所有已注冊的HandlerMapping來找到對應的handler,然后構建出一個HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些攔截器,如下
- public class HandlerExecutionChain {
- private final Object handler;
- private HandlerInterceptor[] interceptors;
- private List<HandlerInterceptor> interceptorList;
- //其他代碼省略
- }
其中HandlerMapping的getHandler實現:
- public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- Object handler = getHandlerInternal(request);
- if (handler == null) {
- handler = getDefaultHandler();
- }
- if (handler == null) {
- return null;
- }
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
- return getHandlerExecutionChain(handler, request);
- }
這里的getHandlerInternal(request)是個抽象方法,由具體的HandlerMapping來實現,獲取到的handler如果為空,則獲取默認配置的handler,如果handler為String類型,則表示這個則會去Spring容器里面去找這樣名字的bean。
再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具體實現(通過一系列的接口設計,之后再好好看看這個設計,到BeanNameUrlHandlerMapping這只用實現該方法中的一部分),如下
- public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
- /**
- * Checks name and aliases of the given bean for URLs, starting with "/".
- */
- @Override
- protected String[] determineUrlsForHandler(String beanName) {
- List<String> urls = new ArrayList<String>();
- if (beanName.startsWith("/")) {
- urls.add(beanName);
- }
- String[] aliases = getApplicationContext().getAliases(beanName);
- for (String alias : aliases) {
- if (alias.startsWith("/")) {
- urls.add(alias);
- }
- }
- return StringUtils.toStringArray(urls);
- }
- }
這里面注釋說,bean的name必須以/開頭,它才處理,將信息存儲在Map<String, Object> handlerMap中,對於本工程來說就是{'/index':HomeAction對象}。
至此這里完成了第一步,下面開始第二步,即方法HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());的具體實現:
- protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
- for (HandlerAdapter ha : this.handlerAdapters) {
- if (logger.isTraceEnabled()) {
- logger.trace("Testing handler adapter [" + ha + "]");
- }
- if (ha.supports(handler)) {
- return ha;
- }
- }
- throw new ServletException("No adapter for handler [" + handler +
- "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
- }
遍歷所有的HandlerAdapter,判斷他們是否支持這個handler。
我們來看下HttpRequestHandlerAdapter的supports(handler)方法:
- public class HttpRequestHandlerAdapter implements HandlerAdapter {
- @Override
- public boolean supports(Object handler) {
- //就是判斷handler是否實現了HttpRequestHandler接口
- return (handler instanceof HttpRequestHandler);
- }
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- //若handler實現了HttpRequestHandler接口,則調用該接口的方法,執行我們在該方法中寫的業務邏輯
- ((HttpRequestHandler) handler).handleRequest(request, response);
- return null;
- }
- @Override
- public long getLastModified(HttpServletRequest request, Object handler) {
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
- }
同理SimpleControllerHandlerAdapter也是這樣類似的邏輯
- public class SimpleControllerHandlerAdapter implements HandlerAdapter {
- @Override
- public boolean supports(Object handler) {
- return (handler instanceof Controller);
- }
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- return ((Controller) handler).handleRequest(request, response);
- }
- @Override
- public long getLastModified(HttpServletRequest request, Object handler) {
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
- }
剩余兩個AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就比較復雜,我也沒看。
按照本工程的配置,則SimpleControllerHandlerAdapter是支持HomeAction的,然后就會執行SimpleControllerHandlerAdapter的handle(processedRequest, response, mappedHandler.getHandler())方法。本質上就會調用HomeAction實現Controller接口的方法。至此就分析完了。
了解過程了之后,然后就是最重要的也是經常配置出問題的地方。DispatcherServlet的handlerMappings和handlerAdapters的來源問題。
DispatcherServlet初始化的時候,會調用一個方法如下:
- protected void initStrategies(ApplicationContext context) {
- initMultipartResolver(context);
- initLocaleResolver(context);
- initThemeResolver(context);
- //初始化一些HandlerMapping
- initHandlerMappings(context);
- //初始化一些HandlerAdapter
- initHandlerAdapters(context);
- initHandlerExceptionResolvers(context);
- initRequestToViewNameTranslator(context);
- initViewResolvers(context);
- initFlashMapManager(context);
- }
這里可以看到,它會初始化一些HandlerMapping和HandlerAdapter,這兩個方法非常重要,理解了這兩個方法你就會知道,配置不對問題出在哪里,下面具體看下這兩個方法:
- private void initHandlerMappings(ApplicationContext context) {
- this.handlerMappings = null;
- if (this.detectAllHandlerMappings) {
- // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
- Map<String, HandlerMapping> matchingBeans =
- BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
- if (!matchingBeans.isEmpty()) {
- this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
- // We keep HandlerMappings in sorted order.
- OrderComparator.sort(this.handlerMappings);
- }
- }
- else {
- try {
- HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
- this.handlerMappings = Collections.singletonList(hm);
- }
- catch (NoSuchBeanDefinitionException ex) {
- // Ignore, we'll add a default HandlerMapping later.
- }
- }
- // Ensure we have at least one HandlerMapping, by registering
- // a default HandlerMapping if no other mappings are found.
- if (this.handlerMappings == null) {
- this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
- if (logger.isDebugEnabled()) {
- logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
- }
- }
- }
detectAllHandlerMappings是DispatcherServlet的一個屬性,你是可以在web.xml中配置的,默認是true,如果為true,則會去從本工程mvc-servlet.xml文件中去探測所有實現了HandlerMapping的bean,如果有,則加入DispatcherServlet的handlerMappings中。如果detectAllHandlerMappings為false,則直接去容器中找id="handlerMapping"且實現了HandlerMapping的bean.如果以上都沒找到,則會去加載默認的HandlerMapping。
- /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
- private boolean detectAllHandlerMappings = true;
本工程由於沒有配置HandlerMapping,所以它會去加載默認的,下面看看默認的配置是什么
- protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
- String key = strategyInterface.getName();
- //defaultStrategies存儲了默認的配置
- String value = defaultStrategies.getProperty(key);
- if (value != null) {
- String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
- List<T> strategies = new ArrayList<T>(classNames.length);
- for (String className : classNames) {
- try {
- Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
- Object strategy = createDefaultStrategy(context, clazz);
- strategies.add((T) strategy);
- }
- catch (ClassNotFoundException ex) {
- throw new BeanInitializationException(
- "Could not find DispatcherServlet's default strategy class [" + className +
- "] for interface [" + key + "]", ex);
- }
- catch (LinkageError err) {
- throw new BeanInitializationException(
- "Error loading DispatcherServlet's default strategy class [" + className +
- "] for interface [" + key + "]: problem with class file or dependent class", err);
- }
- }
- return strategies;
- }
- else {
- return new LinkedList<T>();
- }
- }
繼續看看defaultStrategies是如何初始化的:
- private static final Properties defaultStrategies;
- static {
- // Load default strategy implementations from properties file.
- // This is currently strictly internal and not meant to be customized
- // by application developers.
- try {
- //這里的DEFAULT_STRATEGIES_PATH就是DispatcherServlet.properties
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
- }
- }
這里使用靜態代碼塊來加載配置文件DispatcherServlet.properties,它所在位置就是和DispatcherServlet同一目錄下面的,如下圖所示:

該默認的配置文件的內容如下:
- # Default implementation classes for DispatcherServlet's strategy interfaces.
- # Used as fallback when no matching beans are found in the DispatcherServlet context.
- # Not meant to be customized by application developers.
- org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
- org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
- #這里就是默認的HandlerMapping的配置
- org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
- org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
- #這里就是默認的HandlerAdapter的配置
- org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
- org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
- org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
- org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
- org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
- org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
- org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
- org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
- org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager