溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringMVC中如何實(shí)現(xiàn)DispatcherServlet的初始化與請(qǐng)求轉(zhuǎn)發(fā)

發(fā)布時(shí)間:2021-12-16 10:19:58 來源:億速云 閱讀:143 作者:小新 欄目:編程語言

小編給大家分享一下SpringMVC中如何實(shí)現(xiàn)DispatcherServlet的初始化與請(qǐng)求轉(zhuǎn)發(fā),希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

在我們第一次學(xué)Servlet編程,學(xué)java web的時(shí)候,還沒有那么多框架。我們開發(fā)一個(gè)簡單的功能要做的事情很簡單,就是繼承HttpServlet,根據(jù)需要重寫一下doGet,doPost方法,跳轉(zhuǎn)到我們定義好的jsp頁面。Servlet類編寫完之后在web.xml里注冊(cè)這個(gè)Servlet類。

除此之外,沒有其他了。我們啟動(dòng)web服務(wù)器,在瀏覽器中輸入地址,就可以看到瀏覽器上輸出我們寫好的頁面。為了更好的理解上面這個(gè)過程,你需要學(xué)習(xí)關(guān)于Servlet生命周期的三個(gè)階段,就是所謂的“init-service-destroy”。

以上的知識(shí),我覺得對(duì)于你理解SpringMVC的設(shè)計(jì)思想,已經(jīng)足夠了。SpringMVC當(dāng)然可以稱得上是一個(gè)復(fù)雜的框架,但是同時(shí)它又遵循Servlet世界里最簡單的法則,那就是“init-service-destroy”。我們要分析SpringMVC的初始化流程,其實(shí)就是分析DispatcherServlet類的init()方法,讓我們帶著這種單純的觀點(diǎn),打開DispatcherServlet的源碼一窺究竟吧。

配置元素讀取

用Eclipse IDE打開DispatcherServlet類的源碼,ctrl+T看一下。

SpringMVC中如何實(shí)現(xiàn)DispatcherServlet的初始化與請(qǐng)求轉(zhuǎn)發(fā)

DispatcherServlet類的初始化入口方法init()定義在HttpServletBean這個(gè)父類中,HttpServletBean類作為一個(gè)直接繼承于HttpServlet類的類,覆寫了HttpServlet類的init()方法,實(shí)現(xiàn)了自己的初始化行為。

@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}
		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}
		// Let subclasses do whatever initialization they like.
		initServletBean();
		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

這里的initServletBean()方法在HttpServletBean類中是一個(gè)沒有任何實(shí)現(xiàn)的空方法,它的目的就是留待子類實(shí)現(xiàn)自己的初始化邏輯,也就是我們常說的模板方法設(shè)計(jì)模式。SpringMVC在此生動(dòng)的運(yùn)用了這個(gè)模式,init()方法就是模版方法模式中的模板方法,SpringMVC真正的初始化過程,由子類FrameworkServlet中覆寫的initServletBean()方法觸發(fā)。

再看一下init()方法內(nèi)被try,catch塊包裹的代碼,里面涉及到BeanWrapper,PropertyValues,ResourceEditor這些Spring內(nèi)部非常底層的類。要深究具體代碼實(shí)現(xiàn)上面的細(xì)節(jié),需要對(duì)Spring框架源碼具有相當(dāng)深入的了解。我們這里先避繁就簡,從代碼效果和設(shè)計(jì)思想上面來分析這段try,catch塊內(nèi)的代碼所做的事情:

  • 注冊(cè)一個(gè)字符串到資源文件的編輯器,讓Servlet下面的配置元素可以使用形如“classpath:”這種方式指定SpringMVC框架bean配置文件的來源。

  • 將web.xml中在DispatcherServlet這個(gè)Servlet下面的配置元素利用JavaBean的方式(即通過setter方法)讀取到DispatcherServlet中來。

這兩點(diǎn),我想通過下面一個(gè)例子來說明一下。

我在web.xml中注冊(cè)的DispatcherServlet配置如下:

<!-- springMVC配置開始 -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring/spring-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<!-- springMVC配置結(jié)束 -->

可以看到,我注冊(cè)了一個(gè)名為contextConfigLocation的元素,其值為“classpath:spring/spring-servlet.xml”,這也是大家常常用來指定SpringMVC配置文件路徑的方法。上面那段try,catch塊包裹的代碼發(fā)揮的作用,一個(gè)是將“classpath:spring/spring-servlet.xml”這段字符串轉(zhuǎn)換成classpath路徑下的一個(gè)資源文件,供框架初始化讀取配置元素。在我的工程中是在spring文件夾下面的配置文件spring-servlet.xml。

SpringMVC中如何實(shí)現(xiàn)DispatcherServlet的初始化與請(qǐng)求轉(zhuǎn)發(fā)

另外一個(gè)作用,就是將contextConfigLocation的值讀取出來,然后通過setContextConfigLocation()方法設(shè)置到DispatcherServlet中,這個(gè)setContextConfigLocation()方法是在FrameworkServlet類中定義的,也就是上面繼承類圖中DispatcherServlet的直接父類。

我們?cè)趕etContextConfigLocation()方法上面打上一個(gè)斷點(diǎn),啟動(dòng)web工程,可以看到下面的調(diào)試結(jié)果。

SpringMVC中如何實(shí)現(xiàn)DispatcherServlet的初始化與請(qǐng)求轉(zhuǎn)發(fā)

HttpServletBean類的作者是大名鼎鼎的Spring之父Rod Johnson。作為POJO編程哲學(xué)的大師,他在HttpServletBean這個(gè)類的設(shè)計(jì)中,運(yùn)用了依賴注入思想完成了配置元素的讀取。他抽離出HttpServletBean這個(gè)類的目的也在于此,就是“以依賴注入的方式來讀取Servlet類的配置信息”,而且這里很明顯是一種setter注入。

明白了HttpServletBean類的設(shè)計(jì)思想,我們也就知道可以如何從中獲益。具體來說,我們繼承HttpServletBean類(就像DispatcherServlet做的那樣),在類中定義一個(gè)屬性,為這個(gè)屬性加上setter方法后,我們就可以在元素中為其定義值。在類被初始化后,值就會(huì)被注入進(jìn)來,我們可以直接使用它,避免了樣板式的getInitParameter()方法的使用,而且還免費(fèi)享有Spring中資源編輯器的功能,可以在web.xml中,通過“classpath:”直接指定類路徑下的資源文件。

注意,雖然SpringMVC本身為了后面初始化上下文的方便,使用了字符串來聲明和設(shè)置contextConfigLocation參數(shù),但是將其聲明為Resource類型,同樣能夠成功獲取。鼓勵(lì)讀者們自己繼承HttpServletBean寫一個(gè)測試用的Servlet類,并設(shè)置一個(gè)參數(shù)來調(diào)試一下,這樣能夠幫助你更好的理解獲取配置參數(shù)的過程。

容器上下文的建立

上一篇文章中提到過,SpringMVC使用了Spring容器來容納自己的配置元素,擁有自己的bean容器上下文。在SpringMVC初始化的過程中,非常關(guān)鍵的一步就是要建立起這個(gè)容器上下文,而這個(gè)建立上下文的過程,發(fā)生在FrameworkServlet類中,由上面init()方法中的initServletBean()方法觸發(fā)。

@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();
		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

initFrameworkServlet()方法是一個(gè)沒有任何實(shí)現(xiàn)的空方法,除去一些樣板式的代碼,那么這個(gè)initServletBean()方法所做的事情已經(jīng)非常明白:

this.webApplicationContext = initWebApplicationContext();

這一句簡單直白的代碼,道破了FrameworkServlet這個(gè)類,在SpringMVC類體系中的設(shè)計(jì)目的,它是 用來抽離出建立 WebApplicationContext 上下文這個(gè)過程的。

initWebApplicationContext()方法,封裝了建立Spring容器上下文的整個(gè)過程,方法內(nèi)的邏輯如下:

  1. 獲取由ContextLoaderListener初始化并注冊(cè)在ServletContext中的根上下文,記為rootContext

  2. 如果webApplicationContext已經(jīng)不為空,表示這個(gè)Servlet類是通過編程式注冊(cè)到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),上下文也由編程式傳入。若這個(gè)傳入的上下文還沒被初始化,將rootContext上下文設(shè)置為它的父上下文,然后將其初始化,否則直接使用。

  3. 通過wac變量的引用是否為null,判斷第2步中是否已經(jīng)完成上下文的設(shè)置(即上下文是否已經(jīng)用編程式方式傳入),如果wac==null成立,說明該Servlet不是由編程式注冊(cè)到容器中的。此時(shí)以contextAttribute屬性的值為鍵,在ServletContext中查找上下文,查找得到,說明上下文已經(jīng)以別的方式初始化并注冊(cè)在contextAttribute下,直接使用。

  4. 檢查wac變量的引用是否為null,如果wac==null成立,說明2、3兩步中的上下文初始化策略都沒成功,此時(shí)調(diào)用createWebApplicationContext(rootContext),建立一個(gè)全新的以rootContext為父上下文的上下文,作為SpringMVC配置元素的容器上下文。大多數(shù)情況下我們所使用的上下文,就是這個(gè)新建的上下文。

  5. 以上三種初始化上下文的策略,都會(huì)回調(diào)onRefresh(ApplicationContext context)方法(回調(diào)的方式根據(jù)不同策略有不同),onRefresh方法在DispatcherServlet類中被覆寫,以上面得到的上下文為依托,完成SpringMVC中默認(rèn)實(shí)現(xiàn)類的初始化。

  6. 最后,將這個(gè)上下文發(fā)布到ServletContext中,也就是將上下文以一個(gè)和Servlet類在web.xml中注冊(cè)名字有關(guān)的值為鍵,設(shè)置為ServletContext的一個(gè)屬性。你可以通過改變publishContext的值來決定是否發(fā)布到ServletContext中,默認(rèn)為true。

以上面6點(diǎn)跟蹤FrameworkServlet類中的代碼,可以比較清晰的了解到整個(gè)容器上下文的建立過程,也就能夠領(lǐng)會(huì)到FrameworkServlet類的設(shè)計(jì)目的,它是用來建立一個(gè)和Servlet關(guān)聯(lián)的Spring容器上下文,并將其注冊(cè)到ServletContext中的。跳脫開SpringMVC體系,我們也能通過繼承FrameworkServlet類,得到與Spring容器整合的好處,F(xiàn)rameworkServlet和HttpServletBean一樣,是一個(gè)可以獨(dú)立使用的類。整個(gè)SpringMVC設(shè)計(jì)中,處處體現(xiàn)開閉原則,這里顯然也是其中一點(diǎn)。

初始化SpringMVC默認(rèn)實(shí)現(xiàn)類

初始化流程在FrameworkServlet類中流轉(zhuǎn),建立了上下文后,通過onRefresh(ApplicationContext context)方法的回調(diào),進(jìn)入到DispatcherServlet類中。

@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

DispatcherServlet類覆寫了父類FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各種編程元素的初始化。當(dāng)然這些編程元素,都是作為容器上下文中一個(gè)個(gè)bean而存在的。具體的初始化策略,在initStrategies()方法中封裝。

protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

我們以其中initHandlerMappings(context)方法為例,分析一下這些SpringMVC編程元素的初始化策略,其他的方法,都是以類似的策略初始化的。

private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

detectAllHandlerMappings變量默認(rèn)為true,所以在初始化HandlerMapping接口默認(rèn)實(shí)現(xiàn)類的時(shí)候,會(huì)把上下文中所有HandlerMapping類型的Bean都注冊(cè)在handlerMappings這個(gè)List變量中。如果你手工將其設(shè)置為false,那么將嘗試獲取名為handlerMapping的Bean,新建一個(gè)只有一個(gè)元素的List,將其賦給handlerMappings。如果經(jīng)過上面的過程,handlerMappings變量仍為空,那么說明你沒有在上下文中提供自己HandlerMapping類型的Bean定義。此時(shí),SpringMVC將采用默認(rèn)初始化策略來初始化handlerMappings。

點(diǎn)進(jìn)去getDefaultStrategies看一下。

@SuppressWarnings("unchecked")
	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<T>(classNames.length);
			for (String className : classNames) {
				try {
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Error loading DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]: problem with class file or dependent class", err);
				}
			}
			return strategies;
		}
		else {
			return new LinkedList<T>();
		}
	}

它是一個(gè)范型的方法,承擔(dān)所有SpringMVC編程元素的默認(rèn)初始化策略。方法的內(nèi)容比較直白,就是以傳遞類的名稱為鍵,從defaultStrategies這個(gè)Properties變量中獲取實(shí)現(xiàn)類,然后反射初始化。

需要說明一下的是defaultStrategies變量的初始化,它是在DispatcherServlet的靜態(tài)初始化代碼塊中加載的。

private static final Properties defaultStrategies;
	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
		}
	}
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

這個(gè)DispatcherServlet.properties里面,以鍵值對(duì)的方式,記錄了SpringMVC默認(rèn)實(shí)現(xiàn)類,它在spring-webmvc-3.1.3.RELEASE.jar這個(gè)jar包內(nèi),在org.springframework.web.servlet包里面。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

至此,我們分析完了initHandlerMappings(context)方法的執(zhí)行過程,其他的初始化過程與這個(gè)方法非常類似。所有初始化方法執(zhí)行完后,SpringMVC正式完成初始化,靜靜等待Web請(qǐng)求的到來。

看完了這篇文章,相信你對(duì)“SpringMVC中如何實(shí)現(xiàn)DispatcherServlet的初始化與請(qǐng)求轉(zhuǎn)發(fā)”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI