天天看點

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

作者:Java碼農之路
一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

上圖是SpringMVC的架構結構圖示,中間涉及到多個元件,根據元件所處定位和功能,我把他們分為三個類别:中心元件、核心元件、功能元件。 中心元件DispatcherServlet是整個MVC的核心,負責接收用戶端的請求,排程元件處理請求,作為一個中心的排程控制,DispatcherServlet有效降低元件間的耦合性。核心元件是被DispatcherServlet直接調用的元件,而功能元件則依附于核心元件,構成并豐富了核心元件的功能

tips:關于元件的分類,你在網上搜尋可能每一篇文章都有所不同。不必過于糾結詳細的分類,關鍵能夠了解記憶這個東西即可。

中心元件:前端控制器DispatcherServlet:處于SpringMVC架構的核心位置,實際上是個Servlet。它負責協調組織不同的元件完成請求處理并傳回響應工作

核心元件(部分):

  • 處理器映射器HandlerMapping:它的内部維護了請求的通路路徑和處理器(controller這種)的對應關系。當請求過來的時候,通過請求的url找到對應的處理器。
  • 處理器擴充卡HandlerAdapter:用來執行處理器。因為處理器的類别不止一種,并且他們執行處理器調用的方法也不同。處理器擴充卡通過一個接口來對執行處理器的方法進行規範(handle方法)
  • 處理器Handler:由開發人員自己編寫的代碼,涉及業務請求。
  • 視圖解析器ViewResolver:進行視圖解析,根據邏輯視圖名解析成真正的視圖(本文不會涉及相關的内容)

功能元件(部分):

  • 參數解析器ArgumentResolver:用于解析request請求參數并綁定資料到Controller的入參上,有時候直接參數不是我們想要,可以自定義參數解析器轉化為我們想要的參數
  • 傳回值處理器ReturnValueHandler:用于将處理器執行完後的傳回值轉化為發送請求的用戶端能夠接收的形式
  • 内容協商管理器ContentNegotiationManager:能夠确定用戶端請求所需要的傳回類型。
  • 消息轉換器MessageConveter:對不同類型的對象進行轉換
  • 攔截器HandlerInterceptor:類似過濾器的功能,做的事情與請求響應相關,例如對特定請求做攔截并進行額外處理。

在SpringMVC内部一次web請求會涉及到各式各樣的元件,例如處理器映射器、處理器擴充卡、參數解析器、傳回值處理器、消息轉換器等,這些功能作為SpringMVC架構的基礎設施豐富了MVC架構,使其具有完整的Web請求解析處理的功能,當然這些元件我們也可以自己定義。實際上SpringMVC就是這樣一種模式,架構(DispatcherServlet)先給你搭好,對于架構的每個部件則給與開發者充分的發揮空間。

拿台式電腦做比喻:主機闆就相當于SpringMVC的架構(中心元件DispatcherServlet),CPU、記憶體條、硬碟器件相當于元件(包括核心元件和功能元件),元件+架構組成了一個可用的電腦。我們可以對各個元件進行定制,例如更換可以發光的神光同步記憶體條(記憶體這個核心元件沒變,定制化了帶光效外殼這個功能元件)、更大容量的硬碟以及水冷散熱等。我們從品牌商購買的整機相當于使用了SpringBoot的自動配置,幫我們配置好了基礎的Web開發功能。

上面提到,DispatcherServlet是SpringMVC的核心,就SpringMVC而言,DispatcherServlet是用戶端請求的入口,是以我們的源碼閱讀也應該從這裡開始。本文主要分為三個章節:1.元件引入Spring容器的過程是怎樣的?2.DispatcherServlet的初始化 3.請求在DispatcherServlet中的執行過程。

1. 元件引入Spring容器的過程是怎樣的?

DispatcherServlet涉及元件的排程,被DispatcherServlet排程的元件是在DispatcherServlet初始化時從Spring容器中擷取的,那麼思考下,這些元件如如何引入Spring容器中的?關于這個問題将在本章中得到解答。

先來看一個知識擴充:設計模式之組合模式

組合模式是一種結構型模式,樹形結構:樹枝節點和葉子節點都擁有相似的功能,樹枝節點可以有自己的葉子節點或樹枝節點

樹枝節點(Composite)和葉子節點(Leaf)都實作相同的接口或繼承相同的類(抽象構件component),樹枝節點裡面有一個清單 List<Component>,儲存子節點

組合模式又分為安全組合模式和透明組合模式,他們的具體差別可參考這篇文章進行擴充 juejin.cn/post/708521… ,以下代碼以安全組合模式為例,涉及以下三個構件:Component抽象構件(樹枝節點和葉子節點都具備的屬性)、Leaf葉子節點構件(葉子節點下面不具備其他分支)、Composite樹枝構件。

java複制代碼// ----------------------------------- 抽象構件--------------------------------------

    public interface Component {
        void doSomething();
    }

// --------------------------------------樹枝構件--------------------------------------

    public static class Composite implements Component {

        private String name;

        Composite(String name){
            this.name = name;
        }

        // 構件容器
        private List<Component> components = new ArrayList<Component>();

        // 增加一個葉子構件或樹枝構件
        public void add(Component component) {
            this.components.add(component);
        }
        @Override

        public void doSomething() {

            //容器構件具體業務方法的實作,将遞歸調用成員構件的業務方法
            System.out.println("父節點:"+name);

            for (Component c : components) {
                c.doSomething();
            }

        }

    }

// --------------------------------------葉子構件--------------------------------------

    public static class Leaf implements Component {

        private String name;

        Leaf(String name){
            this.name = name;
        }

        @Override

        public void doSomething() {

            // 葉子節點執行具體的業務
            System.out.println("葉子節點:"+name);

        }

    }

// --------------------------------------調用示例--------------------------------------

    public static void main(String[] args) {

        Composite root = new Composite("root");
        Composite branchOne = new Composite("branchOne");
        Composite branchTwo = new Composite("branchTwo");
        
        Leaf leafOne = new Leaf("LeafOne");
        Leaf leafTwo = new Leaf("leafTwo");
        Leaf leafThree = new Leaf("leafThree");
        Leaf leafFour= new Leaf("leafFour");

        root.add(branchOne);
        root.add(branchTwo);
        branchOne.add(leafOne);
        branchOne.add(leafTwo);
        branchTwo.add(leafThree);
        branchTwo.add(leafFour);

        root.doSomething();

    }
           

那麼組合模式有什麼優勢呢?組合模式使得調用者可以一緻地處理單個對象群組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了調用者代碼的處理。在Spring源碼中經常見到xxxComposite ,就是應用了這種設計模式,例如下面将要提到的WebMvcConfigurerComposite,這個類是組合模式的簡單應用,因為它隻有兩層,子節點全為葉子節點,并且葉子節點的泛型類型都是:WebMvcConfigurer。

我們知道,通常配置或加入一些MVC元件都是通過這種方式:

  1. 實作WebMvcConfigurer接口
  2. 加入@Configuration 注解标記為配置類交給Spring進行管理

例如下面示例是Spring中配置HandlerInterceptor攔截器的一種方式

java複制代碼// 實作WebMvcConfigurer 接口、加入@Configuration注解标記為配置類
@Configuration
public class MyConfig implements WebMvcConfigurer {

    @Resource
    private MyInterceptormyInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(myInterceptor);
    }
}
           

WebMvcConfigurerComposite實作了對系統中WebMvcConfigurer的統一處理 下面是WebMvcConfigurerComposite 的部分源碼内容,WebMvcConfigurerComposite運用了組合模式,葉子節點和樹枝節點都實作了WebMvcConfigurer 接口。

java複制代碼package org.springframework.web.servlet.config.annotation;

    // WebMvcConfigurerComposite 是樹枝構件  WebMvcConfigurer 是通用抽象構件
class WebMvcConfigurerComposite implements WebMvcConfigurer {

    //delegates 是葉子構件清單
    private final List<WebMvcConfigurer> delegates = new ArrayList<>();   

    public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.delegates.addAll(configurers);
        }
    }

            //--- 省略代碼 ---

            // 以添加攔截器為例,外部調用者需要添加攔截器時,傳入一個InterceptorRegistry 注冊對象,然後周遊葉子構件,将系統中定義的滿足要求的攔截器添加到InterceptorRegistry 對象内部維護的清單上面。這裡有點繞=-=
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addInterceptors(registry);
        }

    }
            //--- 省略代碼 ---
}
           

總結一下以上小節内容:WebMvcConfigurer接口裡面定義了許多方法讓我們能夠添加或者配置定制化的元件,我們可以根據自己的需求,建立多個實作了WebMvcConfigurer的配置類,例如MyConfig或者TheirConfig... 。然後這些配置類在SpringMVC内部被類WebMvcConfigurerComposite 統一管理,WebMvcConfigurerComposite 實作了設計模式中的組合模式。

當需要批量添加某一類元件,例如一個核心元件HandlerMapping需要将系統中的功能元件HandlerInterceptor(攔截器)添加到自己内部維護的HandlerInterceptor清單供後續的業務使用,HandlerMapping實作類的源碼中是這樣操作的(這個過程需要了解,在本章後半部分也會有展現):

  1. 定義一個InterceptorRegistry 注冊器對象registry,調用WebMvcConfigurerComposite的addInterceptors(InterceptorRegistry registry)方法,參數傳入registry
  2. 在addInterceptors()方法中周遊WebMvcConfigurer實作類清單( List delegates),逐個調用WebMvcConfigurer實作類的addInterceptors()方法。
  3. registry進入WebMvcConfigurer實作類自己實作的addInterceptors方法中,根據使用者自定義的業務邏輯,添加攔截器
  4. HandlerMapping擷取registry的所有攔截器添加自己内部維護的攔截器清單裡面。後續進行自己的業務處理

簡單總結下這個步驟:A元件需要B類元件,A元件内部維護了一個B類元件清單。注冊器R内部也維護了一個B類元件清單。A元件建立注冊器R,R通過WebMvcConfigurerComposite将系統中所有滿足要求的B類元件添加到,R自己内部維護的B類元件清單中。然後這個R内部維護的B類元件清單指派給A内部維護的B類元件清單。

上述步驟是SpringMVC自己内部的調用管理,通常不需要我們關心,單純使用的話,了解如何定制元件并加入Spring管理即可

現在還有一個問題:WebMvcConfigurerComposite用來管理系統中的WebMvcConfigurer,它的addWebMvcConfigurers方法是用來添加系統中WebMvcConfigurer的,那麼這個add方法又是何時被調用的?

至此引出了一個同樣較為重要的類DelegatingWebMvcConfiguration,下面是此類的部分代碼。我們可以看到,這個類繼承了WebMvcConfigurationSupport,并且内部維護了一個上面提到的WebMvcConfigurerComposite對象。類中其他方法基本都是直接調用了内部WebMvcConfigurerComposite對象進行處理,這種方式将WebMvcConfigurerComposite 對象與外部隔離開來,對WebMvcConfigurerComposite 對象的調用在中間加了一層,也呼應了DelegatingWebMvcConfiguration名稱的開頭:Delegating->委派、代理。

DelegatingWebMvcConfiguration 類是一個被Spring管理的配置類,并且其中有個@Autowired注解标記的setConfigurers()方法,就是在這裡,Setter注入方式将系統中實作了WebMvcConfigurer的Bean自動注入,并且添加到WebMvcConfigurerComposite對象身上。

java複制代碼package org.springframework.web.servlet.config.annotation;

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

   private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    // setter方式注入系統中的WebMvcConfigurer
   @Autowired(required = false)
   public void setConfigurers(List<WebMvcConfigurer> configurers) {
      if (!CollectionUtils.isEmpty(configurers)) {
         this.configurers.addWebMvcConfigurers(configurers);
      }
   }
    // --- --- 省略部分代碼
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
	this.configurers.addInterceptors(registry);
    }

    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
	this.configurers.addArgumentResolvers(argumentResolvers);
    }
    // --- --- 省略部分代碼
}
           

DelegatingWebMvcConfiguration類繼承了WebMvcConfigurationSupport類,那麼它就擁有了WebMvcConfigurationSupport類的全部能力,而WebMvcConfigurationSupport類在SpringMVC裡面是一個非常重要的類,它裡面定義了web場景下相關的核心元件,這些元件包括但不限于:HandlerMapping(處理器映射)、HandlerAdapter(處理器擴充卡)、HandlerExceptionResolver(處理器異常解析器)等等。以及建立這些核心元件時用到的功能元件,包括但不限于:ArgumentResolver(參數解析器)、ReturnValueHandler(傳回值處理器)等等。

以下是WebMvcConfigurationSupport類中定義核心元件處理器映射器的方法,以及其中為處理器映射器添加功能元件攔截器時調用的方法:

java複制代碼package org.springframework.web.servlet.config.annotation;

public RequestMappingHandlerMapping requestMappingHandlerMapping(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

	RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
	mapping.setOrder(0);
        // 設定攔截器
	mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	mapping.setContentNegotiationManager(contentNegotiationManager);
	mapping.setCorsConfigurations(getCorsConfigurations());

	PathMatchConfigurer pathConfig = getPathMatchConfigurer();
	if (pathConfig.getPatternParser() != null) {
		mapping.setPatternParser(pathConfig.getPatternParser());
	}
	else {
		mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
		mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());

		Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
		if (useSuffixPatternMatch != null) {
			mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
		}
		Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();
		if (useRegisteredSuffixPatternMatch != null) {
			mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
		}
	}
	Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
	if (useTrailingSlashMatch != null) {
		mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
	}
	if (pathConfig.getPathPrefixes() != null) {
		mapping.setPathPrefixes(pathConfig.getPathPrefixes());
	}

	return mapping;
}

// ---- 擷取攔截器

protected final Object[] getInterceptors(
		FormattingConversionService mvcConversionService,
		ResourceUrlProvider mvcResourceUrlProvider) {
	if (this.interceptors == null) {
                // 增加一個注冊器registry ,調用子類實作的addInterceptors方法,将子類中找到的攔截器加入注冊器中
		InterceptorRegistry registry = new InterceptorRegistry();
		addInterceptors(registry);
		registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
		registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
                // 擷取注冊器registry 中的攔截器并設定給内部對象,并傳回
		this.interceptors = registry.getInterceptors();
	}
	return this.interceptors.toArray();
}
// 空方法實作,交給子類實作
protected void addInterceptors(InterceptorRegistry registry) {
}
           

WebMvcConfigurationSupport的requestMappingHandlerMapping方法擷取攔截器時調用的getInterceptors方法是一個典型的模闆方法,常見的設計模式之模闆方法模式:模闆方法通常是一個final修飾的方法,子類無法重寫(override)。在這個final修飾的方法裡面有至少一個抽象方法或者空方法讓子類去實作。

java複制代碼 // 模闆方法模式示意
public abstract class AbstractClass{
    // 由子類實作具體業務
    public abstract void doSomething();
    public abstract void doAnything();
    
    // 模闆方法
    public final void templateMethod(){
        this.doSomething();
        this.doAnything();
    }
}
    
           

上面介紹了核心元件HandlerMapping初始化并添加功能元件Interceptor的過程,其他核心元件添加其他功能元件的過程大同小異,感興趣的話可以對自行閱讀源碼(位置:package org.springframework.web.servlet.config.annotation; 的WebMvcConfigurationSupport類)

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖2 這是一張類的繼承關系圖,我們根據這張圖來對前面兩小節做一個總結:

  1. 我們開發人員通過實作WebMvcConfigurer接口,定制化元件(攔截器、參數解析器、傳回值處理器... ...),
  2. 類WebMvcConfigurerComposite 對實作了WebMvcConfigurer接口的配置類進行統一管理。(組合模式)
  3. 類WebMvcConfigurationSupport 中實作了對Web場景下核心元件的預設配置,并且使用者自定義功能元件的擷取是一個空實作,需要由子類重寫**(模闆方法模式)**
  4. DelegatingWebMvcConfiguration 類中維護了一個WebMvcConfigurerComposite對象,并繼承了WebMvcConfigurationSupport ,且重寫了父類的空實作方法(例如父類的抽象方法 addInterceptors 在這裡重寫),并且在重寫方法裡面調用了WebMvcConfigurerComposite 的相關方法擷取元件(例如調用 addInterceptors,不要跟上一句的 addInterceptors 搞混了!)。另外,DelegatingWebMvcConfiguration 類中有個setConfigurers方法,Setter注入的方式将spring容器中的WebMvcConfigurer進行導入。

如果閱讀源碼你會發現, WebMvcConfigurationSupport中的配置的處理器擴充卡 RequestMappingHandlerAdapter設定參數解析器和傳回值處理器這兩個元件時調用的setCustomArgumentResolvers和setCustomReturnValueHandlers方法,他們名字中都有個單詞Custom:定制的。這說明這裡設定的功能元件是使用者自定義的。這兩個功能元件既然有使用者自定義的肯定也有預設實作的,如下面代碼所示HandlerMethodArgumentResolverComposite和HandlerMethodReturnValueHandlerComposite 這兩個對象存儲了預設的參數解析器和傳回值處理器。那麼這兩個對象是在哪裡初始化的呢?

afterPropertiesSet方法,這個方法來自實作的接口InitializingBean。InitializingBean是Spring提供的拓展性接口,InitializingBean接口為bean提供了屬性初始化後的處理方法,它隻有一個afterPropertiesSet方法,凡是繼承該接口的類,在bean的屬性初始化後都會執行該方法。RequestMappingHandlerAdapter 的afterPropertiesSet方法裡面建立了預設的參數解析器和傳回值處理器。建立方式是通過直接new的形式。

java複制代碼
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
//                        ...  ...
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;

@Nullable
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;

@Nullable
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

//                        ...  ...
@Override
public void afterPropertiesSet() {
	// Do this first, it may add ResponseBody advice beans
	initControllerAdviceCache();

	if (this.argumentResolvers == null) {
                // 
		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	if (this.initBinderArgumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
		this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	if (this.returnValueHandlers == null) {
		List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
		this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
	}
}
//                        ...  ...
}
           

OK,如果以上内容都明白了,那就應該知道WebMvcConfigurationSupport 這個類擁有SpringMVC的預設配置,而他的子類DelegatingWebMvcConfiguration更進一步将開發者定制化的元件也加入架構中,那麼我們來思考下一個問題:在SpringBoot環境下,DelegatingWebMvcConfiguration這個配置類是如何交給Spring管理的?

我們都知道SpringBoot基本的自動配置原理:啟動時掃描掃描所有jar路徑下META-INF/spring.factories檔案,這個檔案裡面是需要加載的配置。并且可能你已經注意到,圖2-1 繼承關系圖底部還有一個類EnableWebMvcConfiguration。

spring-boot-autoconfigurer-xxx.jar的spring.factories檔案裡面配置了一個類叫:WebMvcAutoConfiguration。這個類是自動裝配webMVC的入口。而其中有個内部類正是EnableWebMvcConfiguration。如繼承圖2所示,它繼承了DelegatingWebMvcConfiguration ,也就擁有了DelegatingWebMvcConfiguration 的全部功能。EnableWebMvcConfiguration中對WebMvcConfigurationSupport 中定義的一些元件的建立和配置進行了擴充,具體可以閱讀源碼

現在回到本章最開始的問題:這些元件是如何引入到Spring容器中的? 想必答案已經呼之欲出~~

總結下,元件加入Spring容器的過程實際上有一條線索:加入功能元件-->加入核心元件-->交給Spring管理

再詳細一點就是:加入功能元件(1、類實作WebMvcConfigurer接口定制化元件,2、Setter注入元件配置類)-->加入核心元件(1、建立核心元件、同時配置核心元件上的功能元件)-->交給Spring管理(通過SpringBoot的自動配置功能實作)

至此,DispatcherServlet在排程過程中需要用到的元件就已經添加到Spring容器當中...

2. DispatcherServlet的初始化

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖 3

觀看DispatcherServlet的繼承體系,會發現它與它的父類都跟Servlet有關。這一條繼承體系裡面,每一層都有它獨有的功能規範。一個抽象類或接口隻負責一類事情,這種編碼方式符合設計模式中的SRP(Single Responsibility Principle )原則,也就是單一職責原則。

Servlet接口

從頂層的Servlet接口開始,它是Servlet的架構的核心。所有的Servlet都必須實作這一接口。在Servlet接口中定義了5個方法,其中有3個方法代表了Servlet的生命周期

  • init方法,負責初始化Servlet對象
  • service方法,負責響應客戶的請求,最重要的方法,請求響應走的這裡。外部的調用者是Servlet容器
  • destory方法,當Servlet對象退出生命周期時,負責釋放占有的資源
java複制代碼package javax.servlet;

public interface Servlet {
    public void init(ServletConfig config) throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
}
           

GenericServlet抽象類(預設生命周期管理)

GenericServlet實作Servlet、ServletConfig接口。提供了預設的生命周期管理,是一個通用的、協定無關的Servlet。并且包括一些額外的功能,比如說log、servlet的name,這部分能力是因為實作了ServletConfig接口

HttpServlet抽象類(Http協定的Servlet實作)

HttpServlet 是一個抽象類,它進一步繼承并封裝了 GenericServlet,使得使用更加簡單友善,由于是擴充了 Http 的内容,是以還需要使用 HttpServletRequest 和 HttpServletResponse,這兩個類分别是 ServletRequest 和 ServletResponse 的子類

HttpServlet 中對原始的 Servlet 中的方法都進行了預設的操作,不需要顯式的銷毀初始化以及 service(),在 HttpServlet 中,實作了父類的service抽象方法,在其中将ServletRequest和ServletResponse封裝為HttpServletRequest 和HttpServletResponse,然後再調用自定義的一個新的 service() 方法,其中通過 getMethod() 方法判斷請求的類型,進而調用 doGet() 或者 doPost() 等方法處理 get,post等各種類型的http請求,使用者隻需要繼承 HttpServlet,然後重寫 doPost() 或者 doGet() 等方法處理請求即可。

HttpServletBean抽象類

進行一些建立工作,将init-param(servlet的web.xml的配置)參數注入到子類屬性中

FrameworkServlet抽象類(SpringWeb架構的基礎Servlet)

FrameworkServlet中實作了HttpServlet中的各種請求類型(如doGet、doPost、doPut等等),大多數請求類型的處理,都指向了一個模闆方法:processRequest()。模闆方法裡面通常有抽象方法用于子類實作,processRequest中也不例外:doService

DispatcherServlet實作類

核心類,用于請求的處理。doService方法進行一些處理,然後調用類方法doDispatch 進行真正的請求處理!!!

以上是DispatcherServlet繼承體系裡面涉及到Servlet類的一些基本概念,這個繼承體系中主要有兩條路線需要關注: ①處理請求的方法從Servlet接口的service()到DispatcherServlet的doService() ②初始化方法從Servlet的**init()到DispatcherServlet的onRefresh()**方法

路線①:Servlet接口的service()--->HttpServlet抽象類的service() (在這個方法中将請求參數轉化為了HttpServletRequest和HttpServletResponse,并且針對Http請求的類型将請求分發給不同的方法處理:doGet、doPost。并且子類必須實作這些方法否則抛異常)--->FrameworkServlet的doGet()、doPost()等方法。(這些方法中又都調用了一個processRequest方法,進行統一處理,相當于把HttpServlet針對不同Http請求調用不同方法的模式又給還原回去了。所有請求都會調用一個統一的方法:processRequest()。processRequest是一個模闆方法,裡面調用了一個供子類實作的抽象方法:doService())--->DispatcherServlet類的doService()

路線②:Servlet接口的init()--->GenericServlet抽象類的init(此方法存儲了它從servlet容器接收到的ServletConfig對象,以供以後使用)--->HttpServletBean抽象類的init(将init-param參數注入到子類屬性中,末尾預留了initServletBean()方法給子類初始化使用)--->FrameworkServlet抽象類的initServletBean(初始化了目前servlet的應用上下文或者說容器更合理:WebApplicationContext。上下文重新整理後會調用由子類實作的方法onRefresh()。 方法末尾調用了initFrameworkServlet給子類初始化使用)->DispatcherServlet類的onRefresh() 。

這樣,當DispatcherServlet初始化時,onRefresh方法就會被調用,而onRefresh()方法裡面對DispatcherServlet需要的元件進行了注入:通過傳入的參數,spring容器,擷取到對應元件的Bean。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖4 總之,當初始化之後,DispatcherServlet中就會挂上一開始我們注冊到Spring容器中的元件,包括HandlerMapping處理器映射器、HandlerAdapter處理器擴充卡、ViewResolver視圖解析器等,後續業務需要時直接使用。

3. 請求在DispatcherServlet中的執行過程

到這裡,我們應該有了一個概念:第一章講了被DispatcherServlet排程的元件是如何交給Spring容器進行管理的;第二章講了DispatcherServlet的初始化過程,這個過程裡面從Spring容器擷取已經注冊好的元件,添加到DispatcherServlet内部。經過以上過程,萬事俱備,就等待用戶端的請求進入啦。

tips:下面的源碼閱讀不會過于詳細,實際上對于這部分源碼而已個人認為也沒有必要每一句都去刨析讀懂,知曉基本的流程和原理即可。

進入正題之前簡要串一下請求過程中會涉及到的元件:

  • 請求進入doDispatch方法,調用MultipartResolver檔案上傳解析器判斷是否為檔案上傳請求,并保留是否檔案上傳請求的标記,以便請求過後可以進行資源清理操作
  • 根據HandlerMapping處理器映射器找到本次請求要用到的Handler處理器 (一般開發人員自己的業務實作:controller這種),并且再将比對的HandlerInterceptor攔截器和處理器一起組裝成處理器執行鍊。
  • 根據上一步找到的處理器再去找到合适的HandlerAdapter處理器擴充卡
  • 執行攔截器的前置處理,再調用處理器擴充卡的handle方法,執行處理
  • 處理器實作各不相同,此處以@Controller注解修飾的類為例,這種處理器一般比對的是RequestMappingHandlerAdapter處理器擴充卡,在這個處理器擴充卡中會調用ArgumentResolver參數解析器解析參數,參數解析裡面也會使用到MessageConverter消息轉換器來對請求參數進行一個轉化
  • 擷取實際傳入處理器的參數後,使用反射執行處理器
  • 處理器傳回值被ReturnValueHandlers傳回值處理器調用,傳回值處理器内部結合ContentNegotiationManager内容協商管理器和消息轉換器傳回合适的内容給浏覽器

下面是一個普通的controller方法,以這個方法為例,讓我們一步一步的打斷點,看下究竟一次請求過程在SpringMVC内部是如何流轉的

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖5 首先這個方法有兩個參數,HttpServletRequest和一個自定義的DTO,包含了我們業務需要的字段。并且這個DTO被@RequestBody注解修飾。

使用Postman往本地服務發送一個請求,并且在DispatcherServlet的doDispatch()方法入口處打斷點(至于為何在這裡打斷點,第二章中提到過:Servlet接口的service方法負責處理用戶端的請求,而存在一條從Servlet接口的service方法到DispatcherServlet的doService方法的路線。是以用戶端的請求最終都會進入doService方法。doDispatch則是doService方法中真正執行請求處理的方法,是以doDispatch是我們閱讀請求過程源碼的初始位置)

這是doDispatch方法的所有代碼,因為并不算多,是以全部貼在這裡:

java複制代碼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);

         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = HttpMethod.GET.matches(method);
         if (isGet || HttpMethod.HEAD.matches(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}
           

doDispatch方法首先檢查是否檔案上傳請求的方法checkMultipart

processedRequest = checkMultipart(request);

在checkMultipart方法中,會調用DispatcerServletn内部的MultipartResolver檔案上傳解析器,周遊MultipartResolver判斷請求是否為檔案上傳請求,若為檔案上傳請求則調用對應的檔案上傳解析器對請求進行封裝。否則傳回原始請求request

java複制代碼protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
   if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
         ... ...
         try {
            return this.multipartResolver.resolveMultipart(request);
         }
        ... ...
    }
    return request
}
           

比較調用checkMultipart方法後傳回的processedRequest 和參數request是否相同,不同則說明是檔案上傳請求,因為request被重新封裝。multipartRequestParsed 是一個boolean類型的值,用于在finally語句塊裡面判斷是否為檔案上傳請求,如果是則清理請求過程中産生的資源。

java複制代碼multipartRequestParsed = (processedRequest != request);
           

由于我們的請求是一個普通請求,是以multipartRequestParsed 為false

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖6 接着來到了一個較為重要的方法:getHandler() , getHandler方法會傳回一個HandlerExecutionChain對象。我們點開HandlerExecutionChain在源碼,可以看到裡面維護了兩個重要的對象,handler和攔截器清單。實際上HandlerExecutionChain這個對象就是攔截器在SpringMVC中真正執行的地方

java複制代碼public class HandlerExecutionChain {
        ... ...
   private final Object handler;

   private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
        ... ...
}
           

快捷鍵 alt+7 檢視HandlerExecutionChain類中所有方法,發現有幾個貌似認識的方法applyPreHandle、applyPostHandle、triggerAfterCompletion。聯想到我們實作攔截器的時候,需要實作HandlerInterceptor接口,而其中便是有preHandle、postHandle以及afterCompletion方法,是以他們肯定有一些關系存在。關于applyPreHandle這幾個方法的具體内容我們暫且不講,下文不遠就能看到。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖7 現在再回到getHandler方法,它傳回的是一個HandlerExecutionChain對象,也就是說這個方法内部肯定做了一些工作,通過參數傳入的請求對象processedRequest,找到了能夠處理目前請求的handler和攔截器。當然這個handler可能是我們标注了@RequestMapping或者@Controller注解的類,也可能是實作了HttpRequestHandler接口的類(關于handler的種類,可以自行搜尋擴充)等等。下面來看下getHandler的内部實作

java複制代碼protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
       // 周遊内部的處理器映射器清單,如果某個處理器映射器能夠傳回HandlerExecutionChain(處理器執行鍊),就直接傳回
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}
           

斷點打到這裡:可以看到,我的環境下有6個HandlerMapping。這些HandlerMapping都實作了接口方法:getHandler。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖8 周遊第一個HandlerMapping:WebMvcPropertySourcedRequestMappingHandlerMapping。進入它的getHandler方法,會發現進入了一個抽象父類:AbstractHandlerMapping,并且getHandler方法是final修飾的,抽象類+final方法就應該明白這裡可能用了模闆方法模式。果然,可以看到目前getHandler方法裡面有一個抽象方法:getHandlerInternal()。顧名思義,真正擷取Handler的方法是這個,并且由子類實作。并且在getHandler裡面還提供了一個getHandlerExecutionChain方法,将上一步擷取的Handler作為參數傳入,結合内部的攔截器,封裝為HandlerExecutionChain對象。

在AbstractHandlerMapping類名上按下快捷鍵,檢視它的子類有哪些,如圖所示,這些類都直接或者間接實作了getHandlerInternal方法。并且這些子類是否都似曾相識?我們上面周遊的HandlerMapping清單中的類基本都在裡面。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖9 是以我們可以斷言:AbstractHandlerMapping提供了getHandler(傳回的是HandlerExecutionChain)這個方法的主邏輯,它主要有兩個步驟:①擷取Handler(由子類實作),②将①中擷取的Handler和攔截器組裝成一個HandlerExecutionChain對象。

WebMvcPropertySourcedRequestMappingHandlerMapping的getHandler方法隻是做了一些處理,并未傳回Handler,是以繼續周遊HandlerMapping清單,第二個進入的是RequestMappingHandlerMapping對象(),它是我們普通請求擷取handler真正的處理方法,因為它的父類也是AbstractHandlerMapping,是以getHandler執行過程跟上一個handlermapping類似。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖10 來講下RequestMappingHandlerMapping是如何擷取Handler的,不過這裡面的方法調用涉及到類繼承關系,進而跳來跳去的,讀者容易混亂,是以我就按步驟簡要說下RequestMappingHandlerMapping的getHandlerInternal方法裡面具體做了些什麼工作(這個方法是由它的父類AbstractHandlerMethodMapping實作的):

  • 根據傳入的參數request擷取需要查找的路徑,比如說我們的請求是127.0.0.1:9002/login,那麼擷取到的查找路徑就是/login。
  • 根據查找路徑/login 去找到對應的HandlerMethod(HandlerMethod它不是一個接口,也不是個抽象類,且還是public的。HandlerMethod封裝了很多屬性,在通路請求方法的時候可以友善的通路到方法、方法參數、方法上的注解、所屬類等并且對方法參數封裝處理,也可以友善的通路到方法參數的注解等資訊, HandlerMethod它隻負責準備資料,封裝資料,而不提供具體使用的方式方法)。HandlerMethod你可以簡單了解為我們将要執行的controller下的login方法,但是包含了更多的資訊。

現在,我們的getHandlerInternal方法傳回的是一個HandlerMethod對象。

詳細一點,這裡再擴充一下:請求路徑和HandlerMethod的映射關系是什麼時候加入RequestMappingHandlerMapping中的

RequestMappingHandlerMapping繼承的抽象父類AbstractHandlerMethodMapping實作了InitializingBean接口。這個接口在前面建立RequestMappingHandlerAdapter 元件的時候也遇到過,接口方法afterPropertiesSet在bean的屬性初始化後會執行。在這個方法中實作了請求路徑和HandlerMethod的綁定:

java複制代碼@Override // bean的屬性初始化後會執行這個方法
public void afterPropertiesSet() {
   initHandlerMethods();
}

/**
 * Scan beans in the ApplicationContext, detect and register handler methods.
 * @see #getCandidateBeanNames()
 * @see #processCandidateBean
 * @see #handlerMethodsInitialized
 */
protected void initHandlerMethods() {
   for (String beanName : getCandidateBeanNames()) {
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
        // 
         processCandidateBean(beanName);
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}
       
           

在initHandlerMethods方法中,首先擷取了容器中的所有Bean的名稱,然後調用了processCandidateBean方法,這個方法源碼如下,最重要的是它的isHandler()方法,是一個子類實作的抽象方法。由子類來判斷目前Bean是否是一個Handler處理器。

java複制代碼protected void processCandidateBean(String beanName) {
   Class<?> beanType = null;
   try {
      beanType = obtainApplicationContext().getType(beanName);
   }
   catch (Throwable ex) {
      // An unresolvable bean type, probably from a lazy bean - let's ignore it.
      if (logger.isTraceEnabled()) {
         logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
      }
   }
    // 判斷bean是否是一個處理器
   if (beanType != null && isHandler(beanType)) {
      detectHandlerMethods(beanName);
   }
}
           

類RequestMappingHandlerMapping中isHandler方法是這樣判斷的:判斷目前Bean是否有Controller注解或RequestMapping注解。注意:Spring6的新版本中,此處已經改成了僅判斷是否有Controller注解。意思是如果類上隻有RequestMapping注解是無效的了。

java複制代碼/**
 * {@inheritDoc}
 * <p>Expects a handler to have either a type-level @{@link Controller}
 * annotation or a type-level @{@link RequestMapping} annotation.
 */
@Override
protected boolean isHandler(Class<?> beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
----------------------------------------------------------------
// Spring6此處的源碼

/**
 * {@inheritDoc}
 * <p>Expects a handler to have a type-level @{@link Controller} annotation.
 */
@Override
protected boolean isHandler(Class<?> beanType) {
	return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}
           

如果isHandler判斷成功,就會進入進一步的請求路徑整理和生成HanderMethod并且将他們的比對整理成鍵值對的形式存到一個MappingRegistry對象中,MappingRegistry對象維護了多個Map用來儲存這個鍵值對。

回到主題,代碼往下執行到getHandlerExecutionChain處:

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖11 getHandlerExecutionChain方法内容不多,主要是強轉或建立HandlerExecutionChain 對象,并将攔截器注冊到對象内部,代碼貼在下面:

java複制代碼protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    // 周遊内部的攔截器
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
        // 如果是MappedInterceptor,就看是否比對路徑。什麼是MappedInterceptor?就是WebMvcConfigurer實作類在添加攔截器的時候調用了addPathPatterns這類方法,攔截或排除特定路徑。在HandlerMapping初始化時調用了getInterceptors方法設定攔截器,而getInterceptors方法是通過一個InterceptorRegistry對象去周遊系統中的WebMvcConfigurer實作類擷取的。當InterceptorRegistry對象把注冊到的攔截器指派交給調用它的對象時,會對每一個攔截器進行判斷,如果有調用涉及路徑比對的方法,則自動提升為MappedInterceptor
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
         if (mappedInterceptor.matches(request)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
         }
      }
      else {
        // 否則直接添加攔截器
         chain.addInterceptor(interceptor);
      }
   }
   return chain;
}
           

至此我們獲得了一個HandlerExecutionChain 對象,裡面儲存了我們本次請求需要執行的Handler處理器,以及在處理器執行前後要執行的攔截器。

代碼一路往下執行,跳出HandlerMapping的getHandler方法,此時Handler(HandlerExecutionChain 對象)不為null,是以直接傳回

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖12 至此又回到了doDispatch方法裡面,在調用getHandler方法的過程中,我們排程了核心元件:HandlerMapping以及功能元件HandlerInterceptor等等。

經過getHandler方法,我們獲得了一個HandlerExecutionChain對象,這個對象保留了我們請求執行的基本資訊:是誰來處理這個請求,以及處理這個請求前後需要執行的攔截器

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖13 下面将要調用的是SpringMVC中的另一個核心元件:HandlerAdapter。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖14 通常來說帶有xxxAdapter一般是xxx的擴充卡的意思,這裡涉及到設計模式之擴充卡模式 ,先聊下為何要用到處理器擴充卡?springmvc的handler(Controller,HttpRequestHandler,Servlet等)有多種實作方式,例如繼承Controller的,基于注解控制器方式的,HttpRequestHandler方式的。由于實作方式不一樣,調用方式就不确定了。比如RequestMappingHandlerAdapter和HttpRequestHandlerAdapter兩個擴充卡用于兩種handler,這兩種handler的handle方法内部實作天差地别。

擴充卡模式可以用一個簡單的小例子來說明:我們的手機充電頭。家用電220v,直接給手機充電肯定不行,是以需要一個擴充卡,将220->12v 來給手機充電。通常來說擴充卡模式是下面的形式:擴充卡類繼承原有類并實作目标功能的接口。

java複制代碼 // 以電壓轉換為例子
// 建立Target接口,期待得到的插頭:能輸出電壓110v(轉換220v到110v)
public interface Target {
    //将220V轉換輸出110V(原有插頭(Adaptee)沒有的)
    public void Convert_110v();
}
---------------------------------------------------------
//建立源類(原有的插頭)
class PowerPort220V{
//原有插頭隻能輸出220V
    public void Output_220v(){
    }
}
---------------------------------------------------------
// 擴充卡類
class Adapter220V extends PowerPort220V implements Target{
   //期待的插頭要求調用Convert_110v(),但原有插頭沒有
    //是以擴充卡補充上這個方法名
    //但實際上Convert_110v()隻是調用原有插頭的Output_220v()方法的内容
    //是以擴充卡隻是将Output_220v()作了一層封裝,封裝成Target可以調用的Convert_110v()而已
    @Override
    public void Convert_110v(){
      this.Output_220v();
    }
}
---------------------------------------------------------
// 場景類
//通過Adapter類進而調用所需要的方法
public class Client{
    public static void main(String[] args){
        Target mAdapter220V = new Adapter220V();
        ImportedMachine mImportedMachine = new ImportedMachine();
        //使用者拿着進口機器插上擴充卡(調用Convert_110v()方法)
        //再将擴充卡插上原有插頭(Convert_110v()方法内部調用Output_220v()方法輸出220V)
        mAdapter220V.Convert_110v();
        mImportedMachine.Work();
    }
}
           

不過在處理器擴充卡中沒有用到上述模式,它是實作了HandlerAdapter接口,接口中有兩個主要的方法:**support和handle。support用于判斷目前處理器擴充卡是否支援處理這個handler,**handle則是使用目前處理器擴充卡去執行這個handler。這樣做是針對不同類型handler實作的規範調用。

java複制代碼public interface HandlerAdapter {
   boolean supports(Object handler);
   @Nullable
   ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
   @Deprecated
   long getLastModified(HttpServletRequest request, Object handler);
}
           

斷點進入getHandlerAdapter方法,類似getHandler方法,這個方法周遊所有的HandlerAdapter,然後調用他們的support方法看是否支援目前處理器。若支援就傳回目前HandlerAdapter。本次調用中,傳回的是RequestMappingHandlerAdapter。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖15 斷點又回到了doDispatch方法中。繼續往下執行:

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖16 在此處執行攔截器的前置處理,下面是applyPreHandle的源碼:

java複制代碼boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
   for (int i = 0; i < this.interceptorList.size(); i++) {
      HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 依次調用攔截器執行preHandle方法
      if (!interceptor.preHandle(request, response, this.handler)) {
        // 當在某一個攔截器處被攔截,則觸發AfterCompletion
         triggerAfterCompletion(request, response, null);
        // 整個過程執行完以後,傳回false
         return false;
      }
      this.interceptorIndex = i;
   }
   return true;
}

----------------------------------------------------
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
	for (int i = this.interceptorIndex; i >= 0; i--) {
                // AfterCompletion從目前位置攔截器開始
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		try {
                        //反向周遊攔截器并調用他們的afterCompletion方法
			interceptor.afterCompletion(request, response, this.handler, ex);
		}
		catch (Throwable ex2) {
			logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
		}
	}
}
           

上述源碼能夠很清晰的展示攔截器的執行模式,結合下圖中的調用過程進行了解:依次調用攔截器執行preHandle方法,當在某一個攔截器處被攔截,則觸發AfterCompletion,AfterCompletion從目前位置攔截器開始,反向周遊攔截器并調用他們的afterCompletion方法。整個過程執行完以後,傳回false。如果攔截器都通過,則進入後續的處理流程。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖17 我們的請求通過了攔截器,現在進入了這段代碼,ha是我們前面擷取的HandlerAdapter。在本次調用中是:RequestMappingHandlerAdapter。調用它的handle方法進行真正的請求處理。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖18 由于處理器類型有多種:@Controller注解的、實作了Controller接口的、實作HttpRequestHandler接口的等等。每種處理器擴充卡handle方法的内部調用都不盡相同,哪怕兩個使用相同處理器擴充卡的處理器,他們參數有所不同調用的參數解析器也不同(比如@RequestBody修飾的參數和@RequestParam修飾的參數),乃至參數解析器使用的消息轉換器也視具體參數有所差異。此處以RequestMappingHandlerAdapter的實作為例子來講解。也是我們開發過程中使用最頻繁的一種HandlerAdapter 進入handle方法:

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖19 再一路往下、往裡調用,這樣一條路線:handleInternal-->invokeHandlerMethod

invokeHandlerMethod方法中主要做了這樣幾個處理:

①将傳入的HandlerMethod封裝為ServletInvocableHandlerMethod對象 `` java ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

kotlin複制代碼②為ServletInvocableHandlerMethod對象**設定**ArgumentResolver參數解析器和ReturnValueHandler傳回值處理器等元件
``` java 
if (this.argumentResolvers != null) {
   invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
   invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
           

③ServletInvocableHandlerMethod對象調用invokeAndHandle方法執行請求的處理

java複制代碼invocableMethod.invokeAndHandle(webRequest, mavContainer);
           

關于ServletInvocableHandlerMethod此處擴充講解一下,檢視它的類繼承關系圖,發現父類有HandlerMethod和InvocableHandlerMethod。這種繼承關系下,子類是對父類功能的擴充。前面提到HandlerMethod裡面存放了很多方法相關的參數或屬性, 它隻負責準備資料,封裝資料,而不提供具體使用的方式方法。InvocableHandlerMethod在HandlerMethod的基礎上,增加了調用能力,并且能夠在調用的時候,把方法入參的參數都封裝進來,觀看InvocableHandlerMethod的源碼,相比HandlerMethod加入了參數解析、資料綁定等功能。ServletInvocableHandlerMethod又是對InvocableHandlerMethod的進一步擴充,它增加了傳回值和響應狀态碼的處理。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖20 跳過準備工作,直接進入ServletInvocableHandlerMethod的invokeAndHandle方法,

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖21

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖22

invokeAndHandle方法職責比較清晰,重點要執行的兩個方法:

  • invokeForRequest():調用參數解析器解析請求的參數,反射調用真正的業務方法執行
  • this.returnValueHandlers.handleReturnValue():對業務方法的傳回對象,調用傳回值處理器進行處理
java複制代碼public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   setResponseStatus(webRequest);
        // ... ...
   try {
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      if (logger.isTraceEnabled()) {
         logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
   }
}
           

實際上invokeAndHandle方法中的this.returnValueHandlers.handleReturnValue()方法執行完後,傳回上一個調用方法封裝ModelAndView,然後一路傳回到doDispatch方法進行後續的處理,我們正常的Http請求在SpringMVC中的處理就已經接近尾聲。不過invokeAndHandle中涉及的内容依舊很多,且較為重要。是以,下面部分針對invokeForRequest和this.returnValueHandlers.handleReturnValue()兩個方法進行。

首先,來看下invokeForRequest

java複制代碼@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {
    // 擷取方法參數
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
      logger.trace("Arguments: " + Arrays.toString(args));
   }
    // 傳入上一步擷取的參數,執行處理器。處理器執行是通過反射的方式。
   return doInvoke(args);
           

invokeForRequest方法中首先調用了getMethodArgumentValues方法,他的的作用是:調用消息轉換器來對用戶端發過來的request請求内容轉換成了能被反射調用的對象形式。例如args數組裡面存儲的這個LoginDTO已經是一個可以使用的對象,并且對象内部的字段也已經完成資料綁定工作。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖23 斷點進入getMethodArgumentValues,這個方法内容不多,但是他這個參數解析器的調用機制值得我們去學習。主要關注這一部分内容

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖24 執行**this.resolvers.supportsParameter(parameter)**判斷内部的參數解析器是否能夠支援目前參數類型,點進來發現這個方法又調用了一個get開頭的方法,這裡就很值得深究了,按照常理來說,supportsParameter方法内部應該是一個周遊語句,周遊内部的所有參數解析器,然後挨個調用他們的supportXXX方法,來判斷是否支援解析這個類型的參數。但是卻調用了一個getArgumentResolver方法,點選這個getArgumentResolver方法内部,可以看到在開頭做了一個緩存的操作,如果這類參數以及對應的參數解析器已經被緩存過,那麼直接以參數為key擷取對應的參數解析器并傳回。否則的話周遊内部的參數解析器清單,當有支援目前參數的參數解析器,放入緩存并傳回。

java複制代碼@Override
public boolean supportsParameter(MethodParameter parameter) {
	return getArgumentResolver(parameter) != null;
}

-----------------------------------------------------------------------------
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
			if (resolver.supportsParameter(parameter)) {
				result = resolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}
           

這種方式能夠在下次調用的時候避免重新周遊的操作,加快運作速度。

緊接着下面的this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)方法,調用了getArgumentResolver方法,由于存在緩存,是以直接擷取到了對應的參數解析器,然後解析參數。

參數類型的不同,調用的參數解析器也不同,每個參數解析器都有不同的代碼實作。就我們的本次調用而言,參數是一個被@RequestBody注解修飾的LoginDTO對象。來看下這個參數能被哪個參數解析器處理,進入getArgumentResolver方法,發現目前有31個參數解析器可以使用

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖25 最後發現:RequestResponseBodyMethodProcessor這個參數解析器能夠支援目前的參數,點選他的supportsParameter方法,發現裡面判斷的是:參數上是否有@RequestBody注解。而我們的參數,LoginDTO對象,恰好是被@RequestBody注解修飾的。也就是說如果參數上有@RequestBody注解,就會交由RequestResponseBodyMethodProcessor這個參數解析器處理

java複制代碼@Override
public boolean supportsParameter(MethodParameter parameter) {
   return parameter.hasParameterAnnotation(RequestBody.class);
}
           

至于參數解析器的解析就由你自己去閱讀源碼了,RequestResponseBodyMethodProcessor解析參數調用了消息轉換器進行請求參數與參數對象的轉換。

當擷取到能夠處理的參數對象後,getMethodArgumentValues方法傳回了儲存這些對象的數組,接着再反射調用處理器,完成我們業務方法的調用。

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖26 當invokeForRequest方法對請求進行了參數解析并執行業務方法後,方法可能會有傳回值,拿到傳回值後,接着往下執行另外一個重要滴方法處理傳回值

this.returnValueHandlers.handleReturnValue()

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖27 這個handleReturnValue方法的處理模式實際上也類似于前面的參數解析器。頂層接口有兩個方法:supportXXX,handleXXX。supportXXX用于判斷是否支援傳入的參數,handleXXX用于處理傳入的參數。

實際上在Spring内部能看到很多類似的這種模式,把這種模式歸納一下:

xxxComposite、support(判斷)、handle(處理)、緩存 值得學習的模式:

各種處理器可以根據這種模式規範。首先定義接口,接口中有兩個方法:support、handle。一個是判斷目前處理器是否能夠處理,另一個是進行處理。對于系統中的多個處理器,則定義一個xxxComposite類,這個類也實作接口。support:周遊内部處理器,看哪個能對目前場景進行處理。更近一步的,可以将目前場景和處理器存入緩存,友善下一次調用。比如handle隻需要傳入場景,然後從緩存中擷取處理器,再調用處理器的方法執行即可。參數解析器部分完全實作了這種模式,可參考。下面是示例代碼:

java複制代碼// 代碼
// 首先定義一個Handler接口,用于規範處理器的兩個方法:能否支援目前對象(support)、處理(handle)
public interface Handler {
    boolean support(Object o);
    void handle(Object o);
}

// -------------------------------------------------
// 組合模式統一管理處理器
public class HandlerComposite implements Handler {
    // 處理器清單
    List<Handler> handlers = new ArrayList<>();
    
    // 緩存能夠處理的對象和對應的處理器
    private final Map<Object, Handler> handlerCache = new ConcurrentHashMap<>(256);
    
    // 添加處理器
    public void addHandlers(List<Handler> handlers) {
        this.handlers.addAll(handlers);
    }
    
    @Override
    public boolean support(Object o) {
        // 如果緩存中有,就無需再去周遊處理器檢視是否support。
        Handler cacheHandler = this.handlerCache.get(o);
        if (cacheHandler != null) {
            return true;
        }
        // 如果緩存中沒有,就周遊清單,當發現有能夠支援的處理器就進行緩存,友善後續使用。
        for (Handler handler : this.handlers) {
            if (handler.support(o)) {
                this.handlerCache.put(o, handler);
                return true;
            }
            ;
        }
        return false;
    }

    @Override
    public void handle(Object o) {
        // 處理前先從緩存擷取處理器,如果沒有就抛異常(隐含了一個前提:handle方法在support方法之後,并且support通過才能調用handle方法)
        Handler handler = getHandler(o);
        if (handler == null) {
            throw new IllegalArgumentException("handler does not exist");
        }
        handler.handle(o);
    }

    public Handler getHandler(Object o) {
        return this.handlerCache.get(o);
    }
}
// -------------------------------------------------
//處理器類①
public class MyHandlerOne implements Handler{
    @Override
    public boolean support(Object o) {
        if (xxx){
            return true;
        }
        return false;
    }

    @Override
    public void handle(Object o) {

    }
}
//處理器類②
public class MyHandlerTwo implements Handler{
    @Override
    public boolean support(Object o) {
        if (zzz){
            return true;
        }
        return false;
    }

    @Override
    public void handle(Object o) {

    }
}
// -------------------------------------------------
// 代碼調用示例
public class Example {
    public static void main(String[] args) {
        
        MyHandlerOne myHandlerOne = new MyHandlerOne();
        MyHandlerTwo myHandlerTwo = new MyHandlerTwo();
        HandlerComposite handlerComposite = new HandlerComposite();
        handlerComposite.addHandlers(Arrays.asList(myHandlerOne, myHandlerTwo));

        Object o = new Object();
        // 可以看到通過組合模式,簡化了對所有處理器的調用
        if (handlerComposite.support(o)){
            handlerComposite.handle(o);
        }
    }
}
           

講解完了這種模式,我們再來看下,本次請求中調用的是哪個傳回值處理器:RequestResponseBodyMethodProcessor ,可以看到與參數解析器調用的同一個類,實際上檢視它的繼承關系就知道,RequestResponseBodyMethodProcessor是同時具有參數解析和傳回值處理的功能。處理傳回值的時候,依舊會涉及内容協商、消息轉換的内容,此處就不再展開,如果後面新開一篇部落格介紹相關内容,我會把連結貼在這裡:// TODO

RequestResponseBodyMethodProcessor 它的supportsReturnType方法是這樣進行判斷的,判斷處理器類上是否有@ResponseBody注解或者傳回類型上是否有@ResponseBody注解

java複制代碼@Override
public boolean supportsReturnType(MethodParameter returnType) {
   return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
         returnType.hasMethodAnnotation(ResponseBody.class));
}
           

再看我們的處理器類,被@RestController注解修飾,

一個普通HTTP請求在SpringMVC内部是如何進行的?從源碼的角度分析

圖28 而@RestController是一個組合注解,裡面正好有@ResponseBody注解

java複制代碼@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

   @AliasFor(annotation = Controller.class)
   String value() default "";

}
           

當傳回值被處理後,就會一路傳回建構ModelAndView,直到傳回到DispatcherServlet類。再執行攔截器的postHandle後置處理,以及afterCompletion等方法。至此一個請求在SpringMVC中的流轉過程介紹,就接近尾聲。當然,實際肯定不會這麼簡單,SpringMVC還為我們做了許多額外的操作,就由感興趣的讀者去自行探究了~

最後再進行一個擴充,現在有這樣一個問題:正常的web網頁請求場景,處理器上有@ResponseBody注解,序列化預設使用jackson,消息轉換器是MappingJackson2HttpMessageConverter。在攔截器的postHandle後置進行中對HttpServletResponse操作有效嗎?

應該是無效的,因為傳回值處理器在調用MappingJackson2HttpMessageConverter這個消息轉換器時,内部會調用一個outputMessage.getBody().flush()方法将傳回資料刷出去。具體而言是将response裡面的commited狀态修改了(源碼調用較深,建議自己實地操作一次),此時response裡面的資訊也就已經确定,後面不能再改變。

對于一次請求在SpringMVC的流轉過程,本文就寫到這裡。文章内容較多,我寫這篇部落格的初衷是想對自己過往的知識做個總結,文中不免有許多跳躍之處,使文章不夠順暢,邏輯不夠清晰。當然,發出這篇文章時,并不表示這篇文章已經定型,應該會在後續的時間裡,遣詞造句做出一些優化,知識點查漏補缺,讓這篇文章更加通俗易懂且可靠。

繼續閱讀