1. 程式人生 > >SpringMVC原始碼系列:AbstractHandlerMapping

SpringMVC原始碼系列:AbstractHandlerMapping

AbstractHandlerMapping是實現HandlerMapping介面的一個抽象基類。支援排序,預設處理程式,處理程式攔截器,包括由路徑模式對映的處理程式攔截器。所有的HandlerMapping都繼承自AbstractHandlerMapping。
另外,此基類不支援PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE的暴露,此屬性的支援取決於具體的子類,通常基於請求URL對映。

前面說到,HandlerMapping的作用就是通過request查詢Handler和Interceptors。具體的獲取均是通過子類來實現的。

1.AbstractHandlerMapping 的類定義

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
 implements HandlerMapping, Ordered {

AbstractHandlerMapping繼承了WebApplicationObjectSupport,初始化時會自動呼叫模板方法initApplicationContext;AbstractHandlerMapping的建立也就是在這個方法裡面完成的。同時實現了HandlerMapping和Ordered介面,這也就是上面提到的支援排序的原因。

2.AbstractHandlerMapping屬性分析

  • 排序值 order

    預設值為Integer的最大值,後面註釋的意思是和沒有排序是一樣的,因為只有理論上才可能超過Integer.MAX_VALUE。

    private int order = Integer.MAX_VALUE;  // default: same as non-Ordered
  • 預設處理器 defaultHandler

    private Object defaultHandler;
  • Spring工具類 urlPathHelper

    Helper類用於URL路徑匹配。提供對RequestDispatcher中URL路徑的支援,包括並支援一致的URL解碼。

    private UrlPathHelper urlPathHelper = new UrlPathHelper();
  • spring工具類 PathMatcher(AntPathMatcher)

    用於基於字串的路徑匹配的策略介面。

    private PathMatcher pathMatcher = new AntPathMatcher();
  • 攔截器列表 interceptors

    用於配置SpringMVC的攔截器,配置方式由兩種:

    • 1.註冊HandlerMapping時通過屬性設定
    • 2.通過子類的extendInterceptors鉤子方法進行設定(extendInterceptors方法是在initApplicationContext中呼叫的)

    interceptors並不會直接使用,二是通過initInterceptors方法按照型別分配到mappedInterceptors和adaptedInterceptors中進行使用,*interceptors只用於配置。*

    private final List<Object> interceptors = new ArrayList<Object>();
  • adaptedInterceptors

    被分配到adaptedInterceptors中的型別的攔截器不需要進行匹配,在getHandler中會全部新增到返回值HandlerExecutionChain裡面。他 只能從 interceptors中獲取。

    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();
  • corsProcessor

    CorsProcessor作用是接受請求和CorsConfiguration並更新響應的策略。
    此元件不關心如何選擇CorsConfiguration,而是採取後續操作,例如應用CORS驗證檢查,並拒絕響應或將CORS頭新增到響應中。

    private CorsProcessor corsProcessor = new DefaultCorsProcessor();
  • corsConfigSource

    根據路徑模式上對映的CorsConfiguration集合提供每個請求的CorsConfiguration例項。支援精確的路徑對映URI(如“/ admin”)以及Ant樣式的路徑模式(如“/ admin / **”)

    private final UrlBasedCorsConfigurationSource corsConfigSource 
    = new UrlBasedCorsConfigurationSource();
  • 跨域相關問題

CorsConfiguration 具體封裝跨域配置資訊的pojo
CorsConfigurationSource request與跨域配置資訊對映的容器
CorsProcessor 具體進行跨域操作的類

3.AbstractHandlerMapping 中的get&set方法

3.1 setOrder

指定此HandlerMapping bean的排序值。

public final void setOrder(int order) {
  this.order = order;
}

3.2 setDefaultHandler

指定此HandlerMapping bean的排序值。
設定此處理程式對映的預設處理程式。
如果沒有找到特定的對映,這個處理程式將被返回。
預設值為null,表示沒有預設處理程式。

public void setDefaultHandler(Object defaultHandler) {
    this.defaultHandler = defaultHandler;
}

3.3 getDefaultHandler

返回此處理程式對映的預設處理程式,如果沒有,則返回null。

public Object getDefaultHandler() {
    return this.defaultHandler;
}

3.4 setAlwaysUseFullPath

如果URL查詢始終使用當前servlet上下文中的完整路徑,請進行設定。 否則,如果適用,則使用當前servlet對映中的路徑(即,在web.xml中“… / *”servlet對映的情況下)。
預設是“false”。setAlwaysUseFullPath中的實現具體是委託給urlPathHelper和corsConfigSource來完成的。

public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
    this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
    this.corsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath);
}

3.5 setUrlDecode

如果上下文路徑和請求URI應該被URL解碼,則設定。兩者都是由Servlet API返回“undecoded”,與servlet路徑相反。根據Servlet規範(ISO-8859-1)使用請求編碼或預設編碼。setUrlDecode中的實現具體是委託給urlPathHelper和corsConfigSource來完成的。

public void setUrlDecode(boolean urlDecode) {
    this.urlPathHelper.setUrlDecode(urlDecode);
    this.corsConfigSource.setUrlDecode(urlDecode);
}

3.6 setRemoveSemicolonContent

如果“;” (分號)內容應該從請求URI中去除,則設定。預設值是true。setRemoveSemicolonContent中的實現具體是委託給urlPathHelper和corsConfigSource來完成的。

public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
    this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
    this.corsConfigSource.setRemoveSemicolonContent(removeSemicolonContent);
}

3.7 setUrlPathHelper

設定UrlPathHelper以用於解析查詢路徑。
使用它可以用自定義子類覆蓋預設的UrlPathHelper,或者跨多個HandlerMappings和MethodNameResolvers共享通用的UrlPathHelper設定。

public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
    Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
    this.urlPathHelper = urlPathHelper;
    this.corsConfigSource.setUrlPathHelper(urlPathHelper);
}

3.8 getUrlPathHelper

返回UrlPathHelper實現以用於解析查詢路徑。

public UrlPathHelper getUrlPathHelper() {
    return urlPathHelper;
}

3.9 setPathMatcher

將PathMatcher實現設定為用於匹配註冊的URL模式的URL路徑。 預設是AntPathMatcher。

public void setPathMatcher(PathMatcher pathMatcher) {
    Assert.notNull(pathMatcher, "PathMatcher must not be null");
    this.pathMatcher = pathMatcher;
    this.corsConfigSource.setPathMatcher(pathMatcher);
}

3.10 setInterceptors

設定攔截器以應用此處理程式對映對映的所有處理程式。
支援的攔截器型別是HandlerInterceptor,WebRequestInterceptor和MappedInterceptor。
對映攔截器僅適用於請求與其路徑模式相匹配的URL。對映的攔截器Bean在初始化期間也會按型別檢測到。

ublic void setInterceptors(Object... interceptors) {
    this.interceptors.addAll(Arrays.asList(interceptors));
}

其他幾個get&set方法就不列出來了,有興趣的小夥伴可以自行閱讀...

4. AbstractHandlerMapping的建立

因為AbstractHandlerMapping繼承了WebApplicationObjectSupport類,因此AbstractHandlerMapping的建立就是依託於模板方法initApplicationContext來完成的。

@Override
protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

從方法結構可以瞭解到,initApplicationContext中包括三個子處理方法。
* extendInterceptors:這也是一個模板方法,在AbstractHandlerMapping中並沒有具體實現(方法體是空的),主要是用於給子類提供一個新增(修改)Interceptors的入口(現有的SpringMVC實現中均未使用)。
* detectMappedInterceptors:用於將SpringMVC容器及父容器中的所有MappedInterceptor型別的Bean新增到MappedInterceptors屬性中。

`檢測MappedInterceptor型別的bean,並將它們新增到對映的攔截器列表中。
除了可能通過setInterceptors提供的任何MappedInterceptors之外,還會呼叫此方法,預設情況下將從當前上下文及其祖先中新增所有MappedInterceptor型別的Bean。子類可以覆蓋和優化這個策略。`
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
            BeanFactoryUtils.beansOfTypeIncludingAncestors(
                    getApplicationContext(), MappedInterceptor.class, true, false).values());
}
  • initInterceptors:初始化指定的攔截器,檢查MappedInterceptors並根據需要調整HandlerInterceptors和WebRequestInterceptors。(當前Spring版本時4.3.6)
protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            if (interceptor == null) {
                throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
            }
            this.adaptedInterceptors.add(adaptInterceptor(interceptor));
        }
    }
}

這個是4.1.5版本的initInterceptors方法:

protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            if (interceptor == null) {
                throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
            }
            if (interceptor instanceof MappedInterceptor) {
                this.mappedInterceptors.add((MappedInterceptor) interceptor);
            }
            else {
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }
    }

在4.1.5中版本中,initInterceptors的工作是將interceptors屬性裡面所包含的物件按照型別新增到adaptedInterceptors或者mappedInterceptors中。在4.1.5版本中mappedInterceptors是AbstractHandlerMapping的屬性之一。主要原因是因為,springMVC自4.2開始添加了跨域的支援,也就是上面屬性中的後兩個。PS:在閱讀Spring相關原始碼時需要關注不同版本的變更及區別,不要只關注某一個版本,另外就是個人覺得閱讀原始碼的關注點應該在編碼方式、設計模式使用、設計思想及理念,而不僅僅是知道他是如何實現的】

這裡順便說下mappedInterceptors的作用:mappedInterceptors中的攔截器在使用時需要與請求的url進行匹配,只有匹配成功後才會新增到getHandler的返回值HandlerExecytionChain裡。

adaptInterceptor方法:

使給定的攔截器物件適配HandlerInterceptor介面。預設情況下,支援的攔截器型別是HandlerInterceptor和WebRequestInterceptor。每個給定的WebRequestInterceptor將被封裝在WebRequestHandlerInterceptorAdapter中。可以在子類中重寫。

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    if (interceptor instanceof HandlerInterceptor) {
        return (HandlerInterceptor) interceptor;
    }
    else if (interceptor instanceof WebRequestInterceptor) {
        return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    }
    else {
        throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    }
}

5.Handler和Interceptor的獲取

HandlerMapping是通過getHandler方法來獲取Handler和Interceptor的。因此在抽象基類AbstractHandlerMapping中提供了具體的實現。並且在AbstractHandlerMapping中,getHandler使用final關鍵字修飾的,也就是說,子類不能再進行對此方法進行覆蓋重寫了。

getHandler的作用就是查詢給定請求的handler,如果找不到特定請求,則返回到預設handler。

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    //通過getHandlerInternal方法來獲取handler
    Object handler = getHandlerInternal(request);
    //如果前一個方法沒有獲取到,則使用預設的handler
    if (handler == null) {
        //預設的Handler就是AbstractHandlerMapping中的handler屬性通過set得到的值
        handler = getDefaultHandler();
    }
    //如果還是沒有找到Hander,則直接返回Null
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    //如果找到的handler是String型別的,
    if (handler instanceof String) {
        //則以它為名到spring Mvc的容器中查詢相應的Bean
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    //先根據handler和request建立一個HandlerExecutionChain物件,
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

getHandlerInternal

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

查詢給定請求的handler,如果找不到特定請求,則返回null。 這個方法被getHandler呼叫; 如果設定了null返回值,將導致預設handler。
在CORS pre-flight請求上,這個方法應該返回一個不匹配飛行前請求的匹配項,而是根據URL路徑,“Access-Control-Request-Method”頭中的HTTP方法和標頭檔案 從“Access-Control-Request-Headers”頭部獲得,從而允許CORS配置通過getCorsConfigurations獲得,
注意:這個方法也可以返回一個預先構建的HandlerExecutionChain,將一個處理程式物件與動態確定的攔截器組合在一起。狀態指定的攔截器將被合併到這個現有的鏈中。

getHandlerExecutionChain

getLookupPathForRequest:返回給定請求的對映查詢路徑,如果適用的話,在當前的servlet對映中,或者在web應用程式中返回。如果在RequestDispatcher中呼叫include請求,則檢測包含請求URL。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    //如果handler是HandlerExecutionChain型別則直接強轉為HandlerExecutionChain型別,
        //如果不是則根據handler建立一個新的HandlerExecutionChain例項物件
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    //返回給定請求的對映查詢路徑
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    //遍歷當前adaptedInterceptors連結串列
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        //如果是MappedInterceptor型別則
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            //攔截器是否應用於給定的請求路徑,如果是則返回true
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

為給定的handler構建一個HandlerExecutionChain,包括可用的攔截器。預設實現用給定的handler,handler對映的通用攔截器以及與當前請求URL相匹配的任何MappedInterceptors構建標準的HandlerExecutionChain。攔截器按照他們註冊的順序新增。為了擴充套件/重新排列攔截器列表,子類可以覆蓋它。

需要注意的是,傳入的handler物件可能是原始handler或預構建的HandlerExecutionChain。這個方法應該明確地處理這兩種情況,建立一個新的HandlerExecutionChain或者擴充套件現有的鏈。為了簡單地在自定義子類中新增攔截器,可以考慮呼叫super.getHandlerExecutionChain(handler,request)並在返回的鏈物件上呼叫HandlerExecutionChain#addInterceptor。

getCorsHandlerExecutionChain

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
        HandlerExecutionChain chain, CorsConfiguration config) {
    //通過請求頭的http方法是否options判斷是否預請求,
    if (CorsUtils.isPreFlightRequest(request)) {
        HandlerInterceptor[] interceptors = chain.getInterceptors();
        //如果是使用PreFlightRequest替換處理器
        chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
    }
    else {
        //如果是普通請求,新增一個攔截器CorsInterceptor。
        chain.addInterceptor(new CorsInterceptor(config));
    }
    return chain;
}

更新HandlerExecutionChain進行與CORS(HTTP訪問控制:跨域資源共享)相關的處理。
* 對於pre-flight請求,預設實現用一個簡單的HttpRequestHandler來替換選擇的handler,該HttpRequestHandler呼叫已配置的setCorsProcessor。(將處理器替換為內部類PreFlightHandler)
* 對於普通的請求,預設實現插入一個HandlerInterceptor,它執行與CORS有關的檢查並新增CORS頭。(新增CorsInterceptor攔截器)

AbstractHandlerMapping中的兩個內部類

這兩個內部類就是用來校驗request是否cors,並封裝對應的Adapter的。

  • PreFlightRequest是CorsProcessor對於HttpRequestHandler的一個介面卡。這樣HandlerAdapter直接使用HttpRequestHandlerAdapter處理。
  • CorsInterceptor 是CorsProcessor對於HandlerInterceptorAdapter的介面卡。

具體的類資訊如下:

PreFlightHandler

private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {

    private final CorsConfiguration config;

    public PreFlightHandler(CorsConfiguration config) {
        this.config = config;
    }

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        corsProcessor.processRequest(this.config, request, response);
    }

    @Override
    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
        return this.config;
    }
}

CorsInterceptor

private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {

    private final CorsConfiguration config;

    public CorsInterceptor(CorsConfiguration config) {
        this.config = config;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return corsProcessor.processRequest(this.config, request, response);
    }

    @Override
    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
        return this.config;
    }
}

至此AbstractHandlerMapping中的一些原始碼就結束了,AbstractHandlerMapping為HandlerMapping的功能提供的一些具體的模板描述,但是具體的細節實現還需要從其子類中來慢慢分析。關於這部分中涉及到的如HandlerExecutionChain,cors跨域等問題,後面會根據實際情況另開篇幅來學習。

大家如果有什麼意見或者建議可以在下方評論區留言,也可以給我們發郵件([email protected])!歡迎小夥伴與我們一起交流,一起成長。