DispatcherServlet源碼解析

前言

一次為了解決跨域問題屹逛,采用了CORS方法汛骂。根據(jù)官方解釋
香缺,只需要在響應頭里設置
1、Access-Control-Allow-Origin
2图张、Access-Control-Allow-Methods
3、Access-Control-Allow-Headers
三個值就可以了祸轮,于是想到在HandlerInterceptor#preHandle()里去攔截跨域請求(options),然后再根據(jù)自定義注解判斷請求的controller是否支持跨域請求柄错,再設置對應的響應頭苦酱。(項目基于spring3.2.x)但是發(fā)現(xiàn)請求死活無法進入preHandle里(項目里只有一個自定義的preHandle,不存在提前被別的HandlerInterceptor返回的情況)颂跨。于是利用debug大法扯饶,發(fā)現(xiàn)spring獲取攔截器時是根據(jù)url和請求類型進行判斷的池颈,由于跨域請類型是options钓丰,無法獲取對應的handler和HandlerInterceptor,導致直接就返回了携丁,沒有進入攔截器里。(spring4.x后有個默認的handler支持處理options)则北。于是把debug過程中學習到的知識痕慢,下次排查問題可以更快。

Dispathcher處理請求的流程概覽

image-20191201184710800.png
組件 說明
Dispatcher 負責接收用戶請求掖举,并且協(xié)調內(nèi)部的各個組件完成請求的響應
HandlerMapping 通過request獲取handler和interceptors
HandlerAdapter 處理器的適配器。Spring 中的處理器的實現(xiàn)多變方篮,可以通過實現(xiàn) Controller 接口励负,也可以用 @RequestMapping 注解將方法作為一個處理器等,這就導致調用處理器是不確定的继榆。所以這里需要一個處理器適配器,統(tǒng)一調用邏輯集币。
ViewResolver 解析視圖翠忠,返回數(shù)據(jù)

Dispathcer的繼承圖

image-20191127200505690.png

從繼承視圖可以看出,Dispatcher是Servlet的一個實現(xiàn)類当娱。也就是遵循了J2EE規(guī)范的處理器考榨。

Servlet是一個接口,包含以下方法

public interface Servlet {
   
    /**
    * 對配置文件(web.xml)的解析董虱,初始化
    */
    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    /**
    * 業(yè)務邏輯實現(xiàn)在該方法內(nèi)
    * 該方法會被Web容器(如:Tomcat)調用
    */
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

HttpServlet這個類是和 HTTP 協(xié)議相關申鱼。該類的關注點在于怎么處理 HTTP 請求云头,比如其定義了 doGet 方法處理 GET 類型的請求,定義了 doPost 方法處理 POST 類型的請求等匣砖。我們?nèi)粜枰?Servlet 寫 Web 應用昏滴,應繼承該類,并覆蓋指定的方法谣殊。所有的處理get請求姻几、post請求都是由service 方法進行調用的宜狐。如下:

public abstract class HttpServlet extends GenericServlet
        implements java.io.Serializable {
  
  /**
    *實現(xiàn)Servlet的service方法,并且將請求轉為http請求
    *調用內(nèi)部方法service(HttpServletRequest req, HttpServletResponse resp)抚恒,處理http請求
    *
    */
  public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
  
  /**
    *http請求的分發(fā)
    */
   protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req, resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req, resp);

        }
    }
  
    //其他方法
}
        

Dispatcher沒有直接實現(xiàn)servlet,而是繼承了HttpServlet络拌。對于http請求的處理流程:

HttpServlet.service -> FrameworkServlet.service -> FrameworkServlet.processRequest -> DispatcherServlet.doService -> DispatcherServlet.doDispatch

Dispatcher#doDispatch

Dispatcher對請求進行處理在doDispatch方法里

// 省略了內(nèi)部實現(xiàn)俭驮,只看核心的地方
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   //S1 先獲取到請求的處理器Handler和攔截器interceptors
   HandlerExecutionChain mappedHandler = getHandler(processedRequest, false);
        if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
        }
  /*
   * S2
   * 執(zhí)行攔截器,一般自定義的HandlerInterceptor#preHandle就是在這里執(zhí)行的
   * 里面也很簡單春贸,就是一個for循環(huán)表鳍,不停的執(zhí)行preHandle方法,直到某個攔截器返回false
   * 或者循環(huán)結束
   */
  if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
     }
  
  //S3 獲取Handler對于的HandlerAdapter,負責調用Handler獲取結果
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  
  //S4 執(zhí)行handler#handle,返回ModelAndView
  ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  
  //S5 同理祥诽,一個for循環(huán)執(zhí)行HandlerInterceptor#postHandle
  mappedHandler.applyPostHandle(processedRequest, response, mv);
  
  //S6 解析并渲染視圖
  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

以上比較核心的三步是:

1譬圣、獲取HandlerExecutionChain,也就是處理器和攔截器

2雄坪、獲取handler的adapter

3厘熟、執(zhí)行handler#handle,返回結果

下面分別看下三個步驟的實現(xiàn)

獲取HandlerExecutionChain

//HandlerExecutionChain mappedHandler = getHandler(processedRequest, false);
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;
}

邏輯很簡單:for循環(huán)去匹配request對應的HandlerExecutionChain维哈,其中handlerMappings被定義為List<HandlerMapping>绳姨。HandlerMapping是一個接口阔挠,繼承關系如下:


image-20191130142505374.png

HandlerMapping的getHandler方法如下:

//省略內(nèi)部實現(xiàn)
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 
   Object handler = getHandlerInternal(request);
   return getHandlerExecutionChain(handler, request);
}

1飘庄、通過getHandlerInternal獲取handler,是一個模板方法购撼,由子類具有去實現(xiàn)跪削,主要有兩個實現(xiàn)

1.1谴仙、AbstractHandlerMethodMapping#getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    }

1.2、AbstractUrlHandlerMapping#getHandlerInternal

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        Object handler = lookupHandler(lookupPath, request);
    }

尋找handler的方法都是獲取request的請求url碾盐,然后根據(jù)url去獲取controller了晃跺。這里也就是使用@RequestMapping注解的方法。

以lookupHandler為例

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        // 能直接匹配就返回毫玖,比如 "/test" matches "/test"
        Object handler = this.handlerMap.get(urlPath);
        if (handler != null) {
            validateHandler(handler, request);
            return buildPathExposingHandler(handler, urlPath, urlPath, null);
        }
        // "/t*" matches both "/test" and "/team"
        List<String> matchingPatterns = new ArrayList<String>();
        for (String registeredPattern : this.handlerMap.keySet()) {
            if (getPathMatcher().match(registeredPattern, urlPath)) {
                matchingPatterns.add(registeredPattern);
            }
        }
    // spring官方解釋掀虎,按照最長路徑進行匹配
        String bestPatternMatch = null;
        Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            Collections.sort(matchingPatterns, patternComparator);
            if (logger.isDebugEnabled()) {
                logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
            }
            bestPatternMatch = matchingPatterns.get(0);
        }
        if (bestPatternMatch != null) {
            handler = this.handlerMap.get(bestPatternMatch);
            validateHandler(handler, request);
    }

這里的核心是this.handlerMap.get(urlPath),所以的操作都是為了從map從獲取數(shù)據(jù)付枫。map是怎么被初始化的呢烹玉?

map是通過registerHandler方法初始化的,每個子類都可以覆蓋該方法阐滩,實現(xiàn)自己的數(shù)據(jù)初始化二打,但是最終的匹配handler過程是由父類統(tǒng)一實現(xiàn)的狰右。實現(xiàn)了數(shù)據(jù)和操作的分離。

registerHandler也很簡單浮定,先根據(jù)url從map中取handler叔锐,如果存在多個handler則報錯(一個url無法對應多個handler)。

沒有則存入handler很魂。

2、找到攔截器,將處理器和攔截器封裝后返回饱溢。

/**
 * 獲取攔截器的邏輯比較簡單,也是url匹配
 */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : 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;
    }
public class HandlerExecutionChain {

    private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

    private final Object handler;

    private HandlerInterceptor[] interceptors;

    private List<HandlerInterceptor> interceptorList;

    private int interceptorIndex = -1;
}

這里不太理解為什么同時需要interceptors 和 interceptorList走芋,都是同樣的類型绩郎。

獲取HandlerAdapter

image-20191130173648691.png
public interface HandlerAdapter {
    boolean supports(Object handler);

    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    long getLastModified(HttpServletRequest request, Object handler);

}

HandlerAdapter接口定義很簡單,通過support判斷是否支持該handler翁逞,通過handler執(zhí)行handler的方法肋杖。

@ResponseBody 和@RequestBody的使用

? 一般controller的入?yún)⒑统鰠⒍际莏son的形式,只需要使用注解@ResponseBody 和 @RequestBody就可以完成http請求報文和pojo對象之間的轉化挖函。消息的轉化都是通過HttpMessageConverter實現(xiàn)的状植。

public interface HttpMessageConverter<T> {

 // 當前轉換器是否能將對象類型轉換為HTTP報文
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // 轉換器能支持的HTTP媒體類型
    List<MediaType> getSupportedMediaTypes();

    // 轉換HTTP報文為特定類型
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    // 將特定類型對象轉換為HTTP報文
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

read方法即是讀取HTTP請求轉換為參數(shù)對象,write方法即是將返回值對象轉換為HTTP響應報文怨喘。Spring定義了參數(shù)解析器HandlerMethodArgumentResolver返回值處理器HandlerMethodReturnValueHandler統(tǒng)一處理津畸。

// 參數(shù)解析器接口
public interface HandlerMethodArgumentResolver {

    // 解析器是否支持方法參數(shù)
    boolean supportsParameter(MethodParameter parameter);

    // 解析HTTP報文中對應的方法參數(shù)
    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

// 返回值處理器接口
public interface HandlerMethodReturnValueHandler {

    // 處理器是否支持返回值類型
    boolean supportsReturnType(MethodParameter returnType);

    // 將返回值解析為HTTP響應報文
    void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

整體的處理流程:


image-20191201173401005.png

而對于@ResponseBody和@RequestBody都由RequestResponseBodyMethodProcessor統(tǒng)一進行處理。也就是RequestResponseBodyMethodProcessor即實現(xiàn)了HandlerMethodArgumentResolver 也實現(xiàn)了 HandlerMethodReturnValueHandler 必怜。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  // 支持RequestBody注解參數(shù)
  @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

  // 支持ResponseBody注解返回值
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));
    }
  
  //解析參數(shù)
  @Override
    protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
            Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
        Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
        return arg;
    }

  // 解析返回值
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }
}

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肉拓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子梳庆,更是在濱河造成了極大的恐慌暖途,老刑警劉巖卑惜,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丧肴,居然都是意外死亡残揉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門芋浮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抱环,“玉大人,你說我怎么就攤上這事纸巷≌虿荩” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵瘤旨,是天一觀的道長梯啤。 經(jīng)常有香客問我,道長存哲,這世上最難降的妖魔是什么因宇? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮祟偷,結果婚禮上察滑,老公的妹妹穿的比我還像新娘。我一直安慰自己修肠,他們只是感情好贺辰,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嵌施,像睡著了一般饲化。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吗伤,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天吃靠,我揣著相機與錄音,去河邊找鬼足淆。 笑死撩笆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的缸浦。 我是一名探鬼主播夕冲,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼裂逐!你這毒婦竟也來了歹鱼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤卜高,失蹤者是張志新(化名)和其女友劉穎弥姻,沒想到半個月后南片,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡庭敦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年疼进,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秧廉。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡伞广,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疼电,到底是詐尸還是另有隱情嚼锄,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布蔽豺,位于F島的核電站区丑,受9級特大地震影響,放射性物質發(fā)生泄漏修陡。R本人自食惡果不足惜沧侥,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魄鸦。 院中可真熱鬧宴杀,春花似錦、人聲如沸号杏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盾致。三九已至,卻和暖如春荣暮,著一層夾襖步出監(jiān)牢的瞬間庭惜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工穗酥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留护赊,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓砾跃,卻偏偏與公主長得像骏啰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抽高,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容