源碼分析系列之Spring MVC路由機制

下面是網上的一張流程圖session

clipboard.png

全部請求通過Dispatcher Servlet再通過HandlerMapping尋找指定的Controller
咱們看看DispatcherServlet的部分源碼app

... 
private List<HandlerMapping> handlerMappings;
...
  protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        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() + "'");
            }

            handler = hm.getHandler(request);
        } while(handler == null);

        return handler;
    }

關鍵就是handler = hm.getHandler(request);這句根據request取得對應的Handler。咱們接下來再看看HandlerMapping裏是如何實現request的註冊與查找的。ide

先看一張繼承圖this

clipboard.png

1.咱們先從頂層的HandlerMapping看起
如下是它的源碼:url

public interface HandlerMapping {
 String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping" ;

 String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

 String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping" ;

 String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

 String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

 String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

 /**
  * Return a handler and any interceptors for this request. The choice may be made
  * on request URL, session state, or any factor the implementing class chooses.
  * <p>The returned HandlerExecutionChain contains a handler Object, rather than
  * even a tag interface, so that handlers are not constrained in any way.
  * For example, a HandlerAdapter could be written to allow another framework's
  * handler objects to be used.
  * <p>Returns <code> null</code> if no match was found. This is not an error.
  * The DispatcherServlet will query all registered HandlerMapping beans to find
  * a match, and only decide there is an error if none can find a handler.
  * @param request current HTTP request
  * @return a HandlerExecutionChain instance containing handler object and
  * any interceptors, or <code>null</code> if no mapping found
  * @throws Exception if there is an internal error
  */
 HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
 }

着重看註釋中的這兩段話spa

  • The DispatcherServlet will query all registered HandlerMapping beans to find a match, and only decide there is an error if none can find a handler.
    Dispatcher會在HandlerMapping裏註冊的每一個Beans裏去找與之匹配的,若沒有就返回error.
  • Return a handler and any interceptors for this request. The choice may be made on request URL, session state, or any factor the implementing class chooses.
    getHandler方法會根據request的一些因素去決定返回interceptor仍是handler.

咱們再看看具體的實現類(AbstractUrlHandlerMapping)裏是如何註冊request與獲取handler的code

...
    ...
    private final Map<String, Object> handlerMap = new LinkedHashMap();
    ...
    ...
    protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;
        if(!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String)handler;
            if(this.getApplicationContext().isSingleton(handlerName)) {
                resolvedHandler = this.getApplicationContext().getBean(handlerName);
            }
        }

        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.");
            }
        } else if(urlPath.equals("/")) {
            if(this.logger.isInfoEnabled()) {
                this.logger.info("Root mapping to " + this.getHandlerDescription(handler));
            }

            this.setRootHandler(resolvedHandler);
        } 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));
            }
        }

    }
    ...
    ...
    protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
        Object handler = this.lookupHandler(lookupPath, request);
     ...
     ...
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        Object handler = this.handlerMap.get(urlPath);
        if(handler != null) {
            if(handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.getApplicationContext().getBean(handlerName);
            }
    ...
    ...

註冊的關鍵代碼是以下兩行繼承

  • resolvedHandler = this.getApplicationContext().getBean(handlerName);
  • this.handlerMap.put(urlPath, resolvedHandler);

從上下問中獲取相應的Bean,而後以url爲key,handler爲value添加到Map中。ip

獲取handler也很簡單,直接再Map里根據查找。ci

相關文章
相關標籤/搜索