SpringMVC源碼分析(1) HandlerMapping

源碼分析(1):HandlerMapping

當用戶在瀏覽器輸入一個URL地址,瀏覽器發起一個http請求,請求到達服務器後,首先會被SpringMVC註冊在web.xml中的前端轉發器DispatcherServlet接收,DispatcherServlet是一個標準的Servlet,它的作用是接受和轉發web請求到內部框架處理單元.

 

HandlerMapping

public abstract interface HandlerMapping {

public abstract HandlerExecutionChain getHandler(HttpServletRequest paramHttpServletRequest);

}

 

DispatcherServlet接收到web請求後,由標準Servlet類處理方法doGet或者doPost,經過幾次轉發後,最終註冊在DispatcherServlet類中的HandlerMapping實現類組成的一個List會在一個循環中被遍歷.以該web請求的HttpServletRequest對象爲參數,依次調用其getHandler方法,第一個不爲null的調用結果,將被返回.

 

dispatcher-servlet.xml中可配置多個HandlerMapping實現類,比如HelloWorld配置的BeanNameUrlHandlerMapping,還有其他HandlerMapping實現類SimpleUrlHandlerMapping,DefaultAnnotationHandlerMapping.

 

DispatcherServlet.doDispatch() 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {

    HttpServletRequest processedRequest = request;

    HandlerExecutionChain mappedHandler = null;

mappedHandler = getHandler(processedRequest, false);

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

}

DispatcherServlet.getHandler()

protected HandlerExecutionChain getHandler(HttpServletRequest request) {

for (HandlerMapping hm : this.handlerMappings) {

    HandlerExecutionChain handler = hm.getHandler(request);

       if (handler != null) {

        return handler;

       }

    }

   return null;

}

handlerMappings變量就是dispatcher-servlet.xml中配置的HandlerMapping的實現類列表.

 

HandlerMapping  HandlerExecutionChain  Handler  HandlerInterceptors

一個request請求過來,會經由dispatcher-servlet.xml中配置的多個HandlerMapping實現類.

HandlerMapping實現類中還可以配置多個攔截器HandlerInterceptor

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> 

        <property name="interceptors"> 

            <list> 

                <ref bean="measurementInterceptor" /> 

            </list> 

        </property> 

    </bean>

通過上面的配置,上述四個類的關係:

HandlerMapping通過request請求獲得HandlerExecutionChain,HandlerExecutionChain包含了該URL請求映射的Handler實例,以及一系列的HandlerInterceptors.攔截器會作用在Handler的調用前後,類似AOP的功能.

 

重點分析:HandlerExecutionChain handler = hm.getHandler(request)

dispatcher-servlet.xml會配置多個HandlerMapping實現類,這些實現類都繼承了AbstractHandlerMapping抽象類.

 

AbstractHandlerMappingHandlerMapping的抽象類實現

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport

 implements HandlerMapping, Ordered {

private Object defaultHandler;

   private UrlPathHelper urlPathHelper;

   private PathMatcher pathMatcher;

   private final List<Object> interceptors;

   private final List<HandlerInterceptor> adaptedInterceptors;

private final List<MappedInterceptor> mappedInterceptors;

 

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {

     Object handler = getHandlerInternal(request);

     if (handler == null) {

       handler = getDefaultHandler();

        }

     if (handler == null) {

       return null;

     }

 

     if (handler instanceof String) {

       String handlerName = (String)handler;

       handler = getApplicationContext().getBean(handlerName);

     }

     return getHandlerExecutionChain(handler, request);

   }

 

   protected abstract Object getHandlerInternal(HttpServletRequest paramHttpServletRequest);

 

   protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request){

     HandlerExecutionChain chain = new HandlerExecutionChain(handler); 

     chain.addInterceptors(getAdaptedInterceptors());

 

     String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);

     for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {

       if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {

         chain.addInterceptor(mappedInterceptor.getInterceptor());

       }

     }

     return chain;

   }

}

抽象方法getHandlerInternal獲得Handler對象,留給子類實現

子類獲得的handler如果爲null,則取默認的Handler(什麼時候設置默認的見下文),如果默認的也爲null則返回null.

 

如果獲得的handler是字符串類型,說明是Handler的這個Bean的名稱而已,需要從BeanFactory中取出真正對象.

 

取得Handler,getHandler()方法返回的不是Handler,而是HandlerExecutionChain,這個對象包裝了該Handler,以及給一系列的攔截器.

 

AbstraUrlHandlerMappingAbstractHandlerMapping的子類,根據請求URL獲取映射的Handler對象

public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {

  private Object rootHandler;

  private boolean lazyInitHandlers;

  private final Map<String, Object> handlerMap;

 

  protected Object getHandlerInternal(HttpServletRequest request)  {

     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

     Object handler = lookupHandler(lookupPath, request);

     if (handler == null) {

       Object rawHandler = null;

       if ("/".equals(lookupPath)) {

         rawHandler = getRootHandler();

       }

       if (rawHandler == null) {

         rawHandler = getDefaultHandler();

       }

       if (rawHandler != null){

         if (rawHandler instanceof String) {

           String handlerName = (String)rawHandler;

           rawHandler = getApplicationContext().getBean(handlerName);

         }

         validateHandler(rawHandler, request);

         handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);

       }

     }

     return handler;

   }

  protected Object lookupHandler(String urlPath, HttpServletRequest request){

     Object handler = this.handlerMap.get(urlPath);

     if (handler != null) {

       if (handler instanceof String) {

         String handlerName = (String)handler;

         handler = getApplicationContext().getBean(handlerName);

        }

       validateHandler(handler, request);

       return buildPathExposingHandler(handler, urlPath, urlPath, null);

     }

}

}

lookupHandler根據urlPathhandlerMap中獲取到Handler,如果是字符串,再通過SpringIoc容器獲得Handler實例.Handler還不是最終返回的對象,最終返回的是HandlerExecutionChain,Handler包裝上Interceptors:

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, 

     String pathWithinMapping, Map<String, String> uriTemplateVariables){

     HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);

     chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));

     if (!(CollectionUtils.isEmpty(uriTemplateVariables))) {

     chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));

     }

     return chain;

}

 

HelloWorld中使用BeanNameUrlHandlerMapping,handlerMap.get(urlPath)獲取到的handler/hello字符串,Web容器啓動的時候會初始化配置的所有的Bean,即實例化/hello對應的HelloWorld,所以通過bean的名稱就能獲取到對應的Bean,HandlerHelloWorld.

 

get就一定有地方會把對應的HandlerurlPath放入handlerMap,找到handlerMap.put的地方

urlPath和對應的Handler註冊到handlerMap中以便lookupHandler獲取

protected void registerHandler(String urlPath, Object handler) {

   Object resolvedHandler = handler;

    if ((!(this.lazyInitHandlers)) && (handler instanceof String)) {

    String handlerName = (String)handler;

       if (getApplicationContext().isSingleton(handlerName)) {

         resolvedHandler = getApplicationContext().getBean(handlerName);

       }

    }

 

    Object mappedHandler = this.handlerMap.get(urlPath);

    if (mappedHandler != null) {

       if (mappedHandler != resolvedHandler) {

         throw new IllegalStateException("Cannot map " + urlPath + "]: There is already mapped.");

       }

    } else if (urlPath.equals("/")) {

       setRootHandler(resolvedHandler);

    } else if (urlPath.equals("/*")) {

       setDefaultHandler(resolvedHandler);

    } else {

       this.handlerMap.put(urlPath, resolvedHandler);

    }

}

調用者調用registerHandler方法,傳遞urlPathhandler.urlPathrequest請求的URL相關,handler跟這個URL的處理器相關.註冊是發生在Web容器啓動的時候,先根據handler如果是字符串,說明是Handler的名稱,則從BeanFactory中取出Handler實例.然後判斷handlerMap中是否有urlPath對應的Handler實例.如果從map get出來的value有值,說明已經註冊過了,不需要再註冊.如果handlerMap中沒有urlPath對應的Handler實例,

有兩種特殊情況urlPath如果是/,則設置RootHandler,如果是/*,則設置DefaultHandler.

如果不是這兩種特殊情況,則把urlPath和對應的實例化好的Handler放入handlerMap.

 

registerHandler的調用者是AbstractDetectingUrlHandlerMapping.detectHandlers(),再到initApplicationContext()

或者是SimpleUrlHandlerMapping.registerHandlers,再到initApplicationContext()


 

BeanNameUrlHandlerMappingDefaultAnnotationHandlerMapping,AbstractDetectingUrlHandlerMapping初始化initApplicationContext上下文的時候會根據URL來註冊相應的HandlerhandlerMap.

 

Web容器啓動時檢測Handler並調用registerHandler來註冊HandlerhandlerMap

public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {

   private boolean detectHandlersInAncestorContexts;

   public AbstractDetectingUrlHandlerMapping(){

     this.detectHandlersInAncestorContexts = false;

}

   public void initApplicationContext() {

     super.initApplicationContext();

    detectHandlers();

   }

 

   protected void detectHandlers(){

     String[] beanNames = (this.detectHandlersInAncestorContexts) ? 

         BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : 

         getApplicationContext().getBeanNamesForType(Object.class);

     for (String beanName : beanNames) {

       String[] urls = determineUrlsForHandler(beanName);

       if (!(ObjectUtils.isEmpty(urls))) {

         registerHandler(urls, beanName);

       }

     }

   }

   protected abstract String[] determineUrlsForHandler(String paramString);

}

detectHandlersInAncestorContexts是否從父上下文檢測Handler,默認爲否.根據註冊的類型名稱來獲取beanNames.

SpringIoc知識,我們知道可以通過bean的名稱,也可以加上類型來獲得一個bean實例.比如:

applicationContext.getBean("beanName")

applicationContext.getBean("beanName",BeanType.class)

因爲beanName一定是唯一的.通過getBeanNamesForType能從BeanFactory獲取所有註冊的BeanNames數組.

 

又一個抽象方法determineUrlsForHandler,因爲我們能斷定AbstractDetectingUrlHandlerMapping的子類肯定會實現這個方法.BeanNameUrlHandlerMapping類很簡單

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping{

   protected String[] determineUrlsForHandler(String beanName){

     List urls = new ArrayList();

     if (beanName.startsWith("/")) {

       urls.add(beanName);

     }

     return StringUtils.toStringArray(urls);

   }

}

方法參數beanNameSpringMVC配置文件指定的name的值,比如HelloWorld中的"/hello".

Web容器啓動的時候,配置了BeanNameHandlerMapping,會把所有以/開頭的Bean通過registerHandler(urls, beanName)都註冊到handlerMap.

 

DefaultAnnotationHandlerMapping註冊到handlerMap的方法有點複雜,因爲註解不僅在Bean類上,在方法上也會有@RequestMapping註解.

 

再來看看SimpleUrlHandlerMapping的做法:

SimpleUrlHandlerMappingWeb容器啓動時取得mappings配置也註冊到handlerMap

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {

   private final Map<String, Object> urlMap;

   public SimpleUrlHandlerMapping() {

     this.urlMap = new HashMap();

   }

   public void setMappings(Properties mappings) {

     CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);

   }

   public void initApplicationContext(){

     super.initApplicationContext();

     registerHandlers(this.urlMap);

   }

   protected void registerHandlers(Map<String, Object> urlMap) {

       for (Map.Entry entry : urlMap.entrySet()) {

         String url = (String)entry.getKey();

         Object handler = entry.getValue();

         if (!(url.startsWith("/"))) {

           url = "/" + url;

         }

         registerHandler(url, handler);

       }

   }

}

SpringMVC配置SimpleUrlHandlerMapping,並手動配置映射關係,如果URL/hello,則映射到helloController.

<bean id="helloController" class="com.xuyuan.spring3.mvc.helloworld.HelloController"></bean>

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">  

        <property name="mappings">  

            <props>  

                <prop key="/hello">helloController</prop>       

            </props>  

        </property>  

    </bean>

SimpleUrlHandlerMapping通過set方法注入mappings的值,這樣registerHandlers的參數urlMap就是配置文件中mappings配置的所有key,value.如果配置的key沒有以/開頭則自動加上/.比如上面的例子

registerHandler(url, handler)=registerHandler("/hello""helloController")

 

注意:registerHandler的調用者AbstractAnnotationHandlerMappingSimpleUrlHandlerMapping傳遞的參數value不是Handler的真正類型,而是Handler的名稱.而在AbstractUrlHandlerMapping的真正註冊到handlerMap中的.

 

上文AbstractUrlHandlerMapping說到DefaultHandler,如果配置文件或者註解有配置/*對應的Handler.

那麼如果瀏覽器的請求都不會映射到系統中註冊的Handler,則採用這個默認的Handler.如果沒有配置默認的Handler,我們就認爲客戶端的請求不會被系統接受,可以拋出404頁面.

 

 

 

 

參考文檔:

SpringMVC源碼剖析 http://my.oschina.net/lichhao/blog?catalog=285356 

Spring MVC 教程,快速入門,深入分析http://elf8848.iteye.com/blog/875830

跟我學SpringMVC http://jinnianshilongnian.iteye.com/blog/1752171 

發佈了30 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章