在Spring中,ContextLoaderListener只是輔助功能,用于創(chuàng)建WebApplicationContext類型的實(shí)例,而真正的邏輯實(shí)現(xiàn)其實(shí)是在DispatcherServlet中進(jìn)行的,DispatcherServlet是實(shí)現(xiàn)Servlet接口的實(shí)現(xiàn)類。Servlet是一個(gè)JAVA編寫的程序,此程序是基于HTTP協(xié)議的,在服務(wù)端運(yùn)行的(如Tomcat),是按照Servlet規(guī)范編寫的一個(gè)JAVA類。主要是處理客戶端的請求并將其結(jié)果發(fā)送到客戶端。Servlet的生命周期是由Servlet的容器來控制的,它可以分為三個(gè)階段:初始化、運(yùn)行和銷毀。
1、初始化階段
- Servlet容器加載Servlet類,把Servlet類的.class文件中的數(shù)據(jù)讀入到內(nèi)存中。
- Servlet容器創(chuàng)建一個(gè)ServletConfig對象,ServletConfig對象包含了Servlet的初始化配置信息。
- Servlet容器創(chuàng)建一個(gè)Servlet對象。
- Servlet容器調(diào)用Servlet對象的init方法進(jìn)行初始化。
2、運(yùn)行階段
當(dāng)Servlet容器收到一個(gè)請求時(shí),Servlet容器會針對這個(gè)請求創(chuàng)建ServletRequest和ServletResponse對象,然后調(diào)用service方法。并把這兩個(gè)參數(shù)傳遞給service方法,service方法通過ServletRequest對象獲取請求的信息,并處理該請求。再通過ServletResponse對象生成這個(gè)請求的響應(yīng)結(jié)果。然后銷毀ServletRequest和ServletResponse對象,不管這個(gè)請求時(shí)GET還是POST提交的,最終這個(gè)請求都會由service來處理。
3、銷毀階段
當(dāng)web應(yīng)用被終止時(shí),Servlet容器會先調(diào)用Servlet對象的destroy方法,然后再銷毀Servlet對象,同時(shí)也會銷毀與Servlet對象相關(guān)聯(lián)的ServletConfig對象。我們可以在destroy方法的實(shí)現(xiàn)中,釋放Servlet所占用的資源,如關(guān)閉數(shù)據(jù)庫連接,關(guān)閉文件輸入輸出流等。
Servlet的框架是由兩個(gè)JAVA包組成:javax.servlet和javax.servlet.http。在javax.servlet包中定義了所有的servlet類都必須實(shí)現(xiàn)或擴(kuò)展的通用接口和類,在javax.servlet.http包中定義了采用HTTP通信協(xié)議的HttpServlet類。
servlet被設(shè)計(jì)成請求驅(qū)動,servlet的請求可能包含多個(gè)數(shù)據(jù)項(xiàng),當(dāng)web容器接受到某個(gè)servlet請求時(shí),servlet把請求封裝成一個(gè)HttpServletRequest對象,然后把對象傳給servlet的對應(yīng)的服務(wù)方法。
HTTP的請求方式包括delete、get、options、post、put和trace,在HttpServlet類中分別提供了相應(yīng)的服務(wù)方法,它們是doDelete、doGet、doOptions、doPost、doPut和doTrace。
DispatcherServlet的初始化
在servlet初始化階段會調(diào)用其init方法,所以我們首先要查看DispatcherServlet中是否重寫了init方法。DispatcherServlet類相關(guān)的結(jié)構(gòu)圖如下:
我們在HttpServletBean中找到了該方法:
/**
* 將配置參數(shù)映射到這個(gè)servlet的bean屬性,并調(diào)用子類的初始化方法。
* @throws ServletException 如果bean屬性無效(或缺少必需的屬性),或者子類的初始化失敗。
*/
@Override
public final void init() throws ServletException {
// 從初始化參數(shù)設(shè)置bean屬性。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 將當(dāng)前的這個(gè)Servlet類轉(zhuǎn)換為一個(gè)BeanWrapper,從而能夠以Spring的方式來對init-param的值進(jìn)行注入。
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// 注冊自定義屬性編輯器,一旦遇到Resource類型的屬性將會使用ResourceEditor進(jìn)行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 空實(shí)現(xiàn),留給子類覆蓋
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("無法設(shè)置 servlet '" + getServletName() + "' 的 bean 屬性", ex);
}
throw ex;
}
}
// 允許子類執(zhí)行它們喜歡的任何初始化操作。
initServletBean();
}
函數(shù)DispatcherServlet的初始化過程主要是通過將當(dāng)前的Servlet類型實(shí)例轉(zhuǎn)換為BeanWapper類型實(shí)例,以便使用Spring中提供的注入功能進(jìn)行對應(yīng)屬性的注入。這些屬性如contextAttribute、contextClass、nameSpace、contextConfigLocation等,都可以在web.xml文件中以初始化參數(shù)的方式配置在Servlet的聲明中。DispatcherServlet繼承自FrameworkServlet,F(xiàn)rameworkServlet類上包含對應(yīng)的同名屬性,Spring會保證這些參數(shù)被注入到對應(yīng)的值中。屬性注入主要是包含以下幾個(gè)步驟。
1、封裝及驗(yàn)證初始化參數(shù)
ServletConfigPropertyValues除了封裝屬性外還有對屬性驗(yàn)證的功能。
/**
* 創(chuàng)建新的ServletConfigPropertyValues。
* @param config 我們將使用它從ServletConfig中獲取屬性值
* @param requiredProperties 我們需要的屬性集合,對于這些我們不能使用默認(rèn)值
* @throws ServletException 如果缺少任何必需的屬性
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// 如果我們?nèi)匀蝗鄙賹傩?,則失敗。
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"從ServletConfig初始化Servlet '" + config.getServletName() +
"'失敗;缺少以下必需的屬性:" +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
這個(gè)函數(shù)創(chuàng)建一個(gè)ServletConfigPropertyValues對象,從給定的ServletConfig中獲取屬性值,并將其添加到PropertyValues中。如果requiredProperties中存在缺失的屬性,則拋出ServletException異常。
2、將當(dāng)前Servlet實(shí)例轉(zhuǎn)化成BeanWapper實(shí)例
PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要是用于將指定實(shí)例轉(zhuǎn)化為Spring中可以處理的BeanWapper類型的實(shí)例。
3、注冊相對于Resource的屬性編輯器
這里使用屬性編輯器的目的是在對當(dāng)前實(shí)例(DispatcherServlet)屬性注入過程中一旦遇到Resource類型的屬性就會使用ResourceEditor去解析。
4、屬性注入
BeanWapper為Spring中的方法,支持Spring的自動注入。其實(shí)我們最常用的屬性注入無非是contextAttribute、contextClass、nameSpace、contextConfigLocation等。
5、ServletBean的初始化
在ContextLoaderListener加載的時(shí)候已經(jīng)創(chuàng)建了WebApplicationContext實(shí)例,而在這個(gè)函數(shù)中最重要的就是對這個(gè)實(shí)例進(jìn)行進(jìn)一步的補(bǔ)充初始化。
繼續(xù)查看initServletBean(),父類覆蓋了HttpServletBean中的initServletBean函數(shù),源碼如下:
/**
* 重寫HttpServletBean類的方法,在設(shè)置完所有bean屬性后調(diào)用。創(chuàng)建該Servlet的Web應(yīng)用上下文。
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("初始化Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("上下文初始化失敗", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"可能導(dǎo)致潛在敏感數(shù)據(jù)的不安全記錄顯示" :
"已掩蓋以防止對潛在敏感數(shù)據(jù)的不安全記錄";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': 請求參數(shù)和標(biāo)頭將被 " + value);
}
if (logger.isInfoEnabled()) {
logger.info("完成初始化需要 " + (System.currentTimeMillis() - startTime) + " ms");
}
}
這個(gè)函數(shù)會調(diào)用initWebApplicationContext用于創(chuàng)建并初始化WebApplicationContext實(shí)例,initFrameworkServlet()函數(shù)不做任何實(shí)現(xiàn),可以在子類中進(jìn)行擴(kuò)展。
WebApplicationContext的初始化
initWebApplicationContext函數(shù)的主要工作就是創(chuàng)建并刷新WebApplicationContext實(shí)例并對Servlet功能所使用的變量進(jìn)行初始化。initWebApplicationContext函數(shù)的源碼如下:
/**
* 初始化并發(fā)布該servlet的WebApplicationContext。
* <p>實(shí)際創(chuàng)建上下文的工作委托給{@link #createWebApplicationContext}方法。
* 子類可以重寫該方法。
* @return WebApplicationContext實(shí)例
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 在構(gòu)造函數(shù)中注入了上下文實(shí)例 -> 使用它
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
// 上下文尚未刷新 -> 提供服務(wù),如設(shè)置父上下文、設(shè)置applicationContextId等
if (cwac.getParent() == null) {
// 注入父上下文時(shí)未指定明確的父上下文 -> 將根application上下文(如果有的話)設(shè)置為父上下文
cwac.setParent(rootContext);
}
// 配置并刷新WebApplicationContext實(shí)例
configureAndRefreshWebApplicationContext(cwac);
}
}
if (wac == null) {
// 在構(gòu)造函數(shù)中沒有注入上下文實(shí)例 -> 檢查servlet context中是否存在一個(gè)注冊的上下文。
// 如果存在,則假定父上下文(如果有的話)已經(jīng)設(shè)置,并且用戶已經(jīng)進(jìn)行了任何初始化,例如設(shè)置上下文id
wac = findWebApplicationContext();
}
if (wac == null) {
// 為這個(gè)servlet沒有定義上下文實(shí)例 -> 創(chuàng)建一個(gè)本地上下文
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 該上下文不是支持刷新的ConfigurableApplicationContext或者在構(gòu)造函數(shù)中注入的上下文已經(jīng)刷新 ->
// 在這里手動觸發(fā)onRefresh方法。
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// 將上下文發(fā)布為servlet context attribute。
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
這個(gè)函數(shù)用于初始化和發(fā)布WebApplicationContext。它首先從servlet上下文中獲取根應(yīng)用程序上下文,然后根據(jù)需要創(chuàng)建和配置應(yīng)用程序上下文實(shí)例。如果已經(jīng)存在一個(gè)應(yīng)用程序上下文實(shí)例,則直接使用它。如果沒有,則根據(jù)需要創(chuàng)建一個(gè)本地應(yīng)用程序上下文。最后,將應(yīng)用程序上下文發(fā)布為servlet上下文屬性,并返回該上下文實(shí)例。
對于initWebApplicationContext函數(shù)中的初始化工作主要包含幾個(gè)部分。
1、通過構(gòu)造函數(shù)的注入對WebApplicationContext進(jìn)行初始化
當(dāng)進(jìn)入initWebApplicationContext函數(shù)后通過判斷this.webApplicationContext !=null后,便可以確定this.webApplicationContext是否是通過構(gòu)造函數(shù)來初始化的。
2、通過contextAttribute進(jìn)行初始化
通過在web.xml文件中配置的servlet參數(shù)contextAttribute來查找ServletContext中對應(yīng)的屬性,默認(rèn)為WebApplicationContext.class .getName()+".ROOT"。也就是在ContextLoaderListener加載時(shí)會創(chuàng)建WebApplicationContext實(shí)例,并將實(shí)例以WebApplicationContext.class.getName()+".ROOT"為key放入ServletContext中,當(dāng)然我們也可以重寫初始化邏輯使用自己創(chuàng)建的WebApplicationContext,并在servlet的配置中通過初始化參數(shù)contextAttribute指定key。
/**
* 從配置了名稱的`ServletContext`屬性中獲取一個(gè)`WebApplicationContext`。
* 在該 servlet 初始化(或調(diào)用)之前,`WebApplicationContext`必須已經(jīng)加載并存儲在 `ServletContext` 中。
* <p>子類可以覆蓋此方法以提供不同的`WebApplicationContext`檢索策略。
* @return 該 servlet 的`WebApplicationContext`,如果未找到則返回`null`
* @see #getContextAttribute()
*/
@Nullable
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac = WebApplicationContextUtils
.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("未找到WebApplicationContext:初始化器未注冊?");
}
return wac;
}
3、重寫創(chuàng)建WebApplicationContext實(shí)例
如果以上兩種方式都沒有獲取到WebApplicationContext實(shí)例,只能重寫創(chuàng)建新的實(shí)例了。
/**
* 創(chuàng)建用于該servlet的WebApplicationContext,可以是默認(rèn)的
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* 或者如果設(shè)置了的話,可以是一個(gè)自定義的上下文類(通過
* {@link #setContextClass 設(shè)置})。
* 代理到#createWebApplicationContext(ApplicationContext)方法。
* @param parent 要使用的父WebApplicationContext,如果無,則傳入{@code null}
* @return 用于該servlet的WebApplicationContext
* @see org.springframework.web.context.support.XmlWebApplicationContext
* @see #createWebApplicationContext(ApplicationContext)
*/
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
/**
* 創(chuàng)建本servlet的WebApplicationContext,可以是一個(gè)默認(rèn)的XmlWebApplicationContext或者是一個(gè)自定義的上下文類(通過setContextClass方法設(shè)置)。
* <p>此實(shí)現(xiàn)期望自定義上下文實(shí)現(xiàn)ConfigurableWebApplicationContext接口。可以在子類中重寫。
* <p>請不要忘記將此servlet實(shí)例作為創(chuàng)建的上下文的應(yīng)用監(jiān)聽器注冊(以便觸發(fā)它的onRefresh回調(diào)),并在返回上下文實(shí)例之前調(diào)用ConfigurableApplicationContext的refresh方法。
* @param parent 要使用的父級ApplicationContext,如果無父級則為null
* @return 本servlet的WebApplicationContext
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 讀取servlet的初始化參數(shù)contextClass,如果沒有配置默認(rèn)為XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"在名稱為'" + getServletName() +
"'的servlet中發(fā)生致命的初始化錯誤:自定義WebApplicationContext類[" + contextClass.getName() +
"]不是ConfigurableWebApplicationContext類型的");
}
// 通過反射方式實(shí)例化contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// parent為在ContextLoaderListener中創(chuàng)建的實(shí)例
// 在ContextLoaderListener加載的時(shí)候初始化的WebApplicationContext類型的實(shí)例
wac.setParent(parent);
// 獲取contextConfigLocation屬性,配置在servlet初始化參數(shù)中
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//初始化Spring環(huán)境,包括加載配置文件等
configureAndRefreshWebApplicationContext(wac);
return wac;
}
4、configureAndRefreshWebApplicationContext
無論是通過構(gòu)造函數(shù)注入還是單獨(dú)創(chuàng)建,都會調(diào)用configureAndRefreshWebApplicationContext方法來對已經(jīng)創(chuàng)建的WebApplicationContext實(shí)例進(jìn)行配置及刷新,源碼如下:
/**
* 配置并刷新Web應(yīng)用上下文
*
* @param wac 可配置的Web應(yīng)用上下文
*/
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 應(yīng)用上下文id仍然設(shè)置為其默認(rèn)值
// -> 基于可用信息分配一個(gè)更有用的id
if (this.contextId != null) {
wac.setId(this.contextId);
} else {
// 生成默認(rèn)id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 當(dāng)上下文刷新時(shí),wac環(huán)境的#initPropertySources方法將被調(diào)用;這里提前調(diào)用以確保servlet屬性源在以下下方的post-processing或初始化之前可用
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment cwe) {
cwe.initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
個(gè)函數(shù)用于配置和刷新Web應(yīng)用上下文。首先根據(jù)上下文的id是否與默認(rèn)值相同來為其設(shè)置一個(gè)更有用的id。然后設(shè)置上下文的servletContext、servletConfig和命名空間,并添加一個(gè)監(jiān)聽器。接下來,通過調(diào)用wac環(huán)境的initPropertySources方法來初始化屬性源。最后,調(diào)用postProcessWebApplicationContext和applyInitializers方法對上下文進(jìn)行后處理和初始化,并刷新上下文。
5、刷新WebApplicationContext
onRefresh是FrameworkServlet類中提供的模板方法,在其子類DispatcherServlet中進(jìn)行了重寫,主要用于刷新Spring在Web功能實(shí)現(xiàn)中所必須使用的全局變量。DispatcherServlet中onRefresh函數(shù)的源碼如下:
/**
* 這個(gè)方法調(diào)用了 {@link #initStrategies} 方法。
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* 初始化這個(gè) servlet 使用的策略對象。
* <p>對于需要初始化更多策略對象的情況,可以被子類重寫。
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
初始化MultipartResolver
在Spring中,MultipartResolver主要是處理文件上傳。默認(rèn)情況下,Spring是沒有Multipart處理的,因?yàn)楹芏嚅_發(fā)者想要自己處理它們。如果想使用Spring的Multipart,則需要在Web應(yīng)用的上下文中添加Multipart解析器。這樣,每個(gè)請求就會被檢查是否包含Multipart。然而如果請求中包含Multipart,那么上下文中定義的MultipartResolver就會解析它,這樣請求中的Multipart屬性就會想其他屬性一樣被處理。常用配置如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maximumFileSize">
<value>100000</value>
</property>
</bean>
那么MultipartResolver就是在initMultipartResolver中被加入到DispatcherServlet中的。
/**
* 初始化用于此類的 MultipartResolver。
* <p>如果在 BeanFactory 中未定義給定名稱的 bean,則不提供多部分處理。
*/
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("檢測到 " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("檢測到 " + this.multipartResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// 默認(rèn)情況下沒有 multipart 解析器。
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("未聲明 '" + MULTIPART_RESOLVER_BEAN_NAME + "' multipart 解析器");
}
}
}
因?yàn)橹暗牟襟E已經(jīng)完成了Spring中配置文件的解析,所以在這里只要在配置文件注冊過都可以通過ApplicationContext提供的getBean方法來直接獲取對應(yīng)的bean,進(jìn)而初始化MultipartResolver中的multipartResolver變量。
初始化LocaleResolver
在Spring的國際化配置中一共有三種使用方式。
- 基于URL參數(shù)的配置。通過URL參數(shù)來控制國際化,而提供這個(gè)功能的就是AcceptHeaderLocaleResolver,默認(rèn)的參數(shù)名為local,注意大小寫。
- 基于session的配置。它通過檢驗(yàn)用戶會話中預(yù)置的屬性來解析區(qū)域。最常用的是根據(jù)用戶本次會話過程中的語言設(shè)定決定語言中來。
- 基于cookie的國際配置。CookieLocaleResolver用于通過瀏覽器的cookie設(shè)置Locale對象。這種策略在應(yīng)用程序中不支持會話或者狀態(tài)必須保存在客戶端有用。
這三種方式都可以解決國際化問題,但是對于LocaleResolver的使用基礎(chǔ)是在DispatcherServlet中的初始化。
/**
* 初始化該類使用的LocaleResolver。
* <p>如果BeanFactory中沒有給定名稱的bean,將默認(rèn)使用AcceptHeaderLocaleResolver。
*/
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("檢測到 " + this.localeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("檢測到 " + this.localeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// 需要使用默認(rèn)的LocaleResolver
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("沒有LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': 使用默認(rèn) [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
提取配置文件中設(shè)置的LocaleResolver來初始化DispatcherServlet中的localeResolver屬性。
初始化ThemeResolver
initThemeResolver未來會被遺棄,這里不做詳細(xì)介紹,只是簡單的展示源碼。
/**
* 初始化由該類使用的ThemeResolver。
* 如果BeanFactory中沒有給定名稱的bean定義此命名空間,默認(rèn)為FixedThemeResolver。
*/
@Deprecated
private void initThemeResolver(ApplicationContext context) {
try {
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("檢測到 " + this.themeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("檢測到 " + this.themeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// 需要使用默認(rèn)策略
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("沒有ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +
"': 使用默認(rèn) [" + this.themeResolver.getClass().getSimpleName() + "]");
}
}
}
初始化HandlerMappings
當(dāng)客戶端發(fā)出Request時(shí)DispatcherServlet會將Request提交給,然后HandlerMapping根據(jù)WebApplicationContext的配置來回傳給DispatcherServlet相應(yīng)的Controller。
在基于SpringMVC的Web應(yīng)用中,可以為DispatcherServlet提供多個(gè)HandlerMapping供其應(yīng)用。DispatcherServlet在選用HandlerMapping的過程中,將根據(jù)我們所指定的一些列HandlerMapping的優(yōu)先級進(jìn)行排序,然后優(yōu)先使用優(yōu)先級在前的HandlerMapping。如果當(dāng)前的HandlerMapping能夠返回可用的Handler,DispatcherServlet則使用當(dāng)前返回的Handler來進(jìn)行Web請求的處理,而不再繼續(xù)詢問其他的HandlerMapping。否則,DispatcherServlet將繼續(xù)按照各個(gè)HandlerMapping的優(yōu)先級進(jìn)行詢問,直到獲取一個(gè)可用的Handler為止。初始化配置的源碼如下:
/**
* 初始化用于此類的HandlerMappings。
* <p>如果BeanFactory中未定義此命名空間的HandlerMapping bean,默認(rèn)為BeanNameUrlHandlerMapping。
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 在ApplicationContext中查找所有HandlerMappings,包括祖先上下文。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// 我們保持HandlerMappings的排序。
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// 忽略,之后我們將添加一個(gè)默認(rèn)HandlerMapping。
}
}
// 如果找不到其他HandlerMapping,則通過注冊默認(rèn)HandlerMapping來確保至少有一個(gè)HandlerMapping。
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("未聲明servlet '" + getServletName() +
"': 使用DispatcherServlet.properties中的默認(rèn)策略");
}
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
這個(gè)Java函數(shù)用于初始化HandlerMappings,根據(jù)配置從ApplicationContext中獲取所有的HandlerMapping實(shí)例,并按照指定的順序排序。如果沒有定義HandlerMapping,則使用默認(rèn)的BeanNameUrlHandlerMapping。最后,根據(jù)找到的HandlerMapping實(shí)例設(shè)置一些屬性值。如果只期望SpringMVC加載指定的HandlerMapping時(shí),可以修改web.xml中的DispatcherServlet的初始參數(shù),將detectAllHandlerMappings設(shè)置為false,此時(shí)SpringMVC將會查找名為“handlerMapping”的bean,并作為當(dāng)前系統(tǒng)中唯一的HandlerMapping。
初始化HandlerAdapters
該步驟適用于初始化適配器,源碼如下:
/**
* 初始化該類使用的HandlerAdapters。
* <p>如果該命名空間的BeanFactory中沒有定義HandlerAdapter Bean,則默認(rèn)使用SimpleControllerHandlerAdapter。
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// 在ApplicationContext中,包括祖先上下文,查找所有的HandlerAdapters。
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// 我們按排序順序保存HandlerAdapters。
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
} else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
} catch (NoSuchBeanDefinitionException ex) {
// 忽略,稍后添加默認(rèn)HandlerAdapter。
}
}
// 確保我們至少有一些HandlerAdapters,如果找不到其他HandlerAdapters,則注冊默認(rèn)HandlerAdapters。
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("對于servlet '" + getServletName() + "'沒有聲明HandlerAdapters:使用DispatcherServlet.properties中的默認(rèn)策略");
}
}
}
同樣在初始化的過程中涉及了一個(gè)變量detectAllHandlerAdapters,和detectAllHandlerMappings作用很相似,只不過作用對象是HandlerAdapter??梢酝ㄟ^修改web.xml中的DispatcherServlet的初始參數(shù),將detectAllHandlerAdapters設(shè)置為false,使得SpringMVC查找bean名稱為“handlerAdapter”的HandlerAdapter實(shí)例。
作為總控制器的派遣servlet通過處理器映射得到處理器后,會輪詢處理器適配器模板,查找能夠處理當(dāng)前HTTP請求的處理器適配器的實(shí)現(xiàn),處理器適配器模塊根據(jù)處理器映射返回的處理器類型,例如簡單的適配器類型、注解控制器類型或者遠(yuǎn)程調(diào)用處理器類型,來選擇一個(gè)適當(dāng)?shù)奶幚砥鬟m配器的實(shí)現(xiàn),從而適配當(dāng)前的HTTP請求。
- HTTP請求處理器適配器(HttpRequestHandlerAdapter),僅支持對HTTP請求處理器的適配,它簡單地將HTTP請求對象和相應(yīng)對象傳遞給HTTP請求處理器的實(shí)現(xiàn),它并不需要返回值,它主要是基于HTTP的遠(yuǎn)程調(diào)用的實(shí)現(xiàn)上。
- 簡單控制處理器適配器(SimpleControllerHandlerAdapter),這個(gè)實(shí)現(xiàn)類將HTTP請求適配到一個(gè)控制器的實(shí)現(xiàn)進(jìn)行處理。這里控制器的實(shí)現(xiàn)是一個(gè)簡單的控制器接口的實(shí)現(xiàn)。簡單控制處理器適配器被設(shè)計(jì)成一個(gè)框架類的實(shí)現(xiàn),不需要被改寫,客戶化的業(yè)務(wù)邏輯通常是在控制器接口的實(shí)現(xiàn)類中實(shí)現(xiàn)的。
- 注解方式處理器適配器(AnnotationMethodHandlerAdapter),這個(gè)類的實(shí)現(xiàn)是基于注解的實(shí)現(xiàn),它需要結(jié)合注解的方式映射和注解方法處理器協(xié)同工作。它通過解析聲明在注解控制器的請求映射信息來解析相應(yīng)的處理器方法來處理當(dāng)前的HTTP請求。在處理的過程中,它通過反射來發(fā)現(xiàn)探測處理器方法的參數(shù),調(diào)用處理器方法,并且映射返回值到模型和控制器對象,最后返回模型和控制器對象給作為主控制器的派遣器servlet。
初始化HandlerExceptionResolvers
基于HandlerExceptionResolver接口的異常處理,使用這種方法只需要實(shí)現(xiàn)resolveException方法,該方法返回一個(gè)ModelAndView對象,在方法內(nèi)部對異常的類型進(jìn)行判斷,然后嘗試生成對應(yīng)的ModelAndView對象,如果該方法返回了null,則Spring會繼續(xù)尋找其他實(shí)現(xiàn)了HandlerExceptionResolver接口的bean。換句話說,Spring會搜索所有注冊在其環(huán)境中實(shí)現(xiàn)了HandlerExceptionResolver接口bean,逐個(gè)執(zhí)行,直到返回一個(gè)ModelAndView對象。
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* 檢查是否應(yīng)該應(yīng)用此解析器(即,如果提供的處理器與配置的
* {@linkplain #setMappedHandlers 處理器} 或 {@linkplain #setMappedHandlerClasses 處理器類} 中的任何一個(gè)匹配)
* 然后委托給 {@link #doResolveException} 模板方法。
*/
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// 在 warn 日志啟用時(shí)打印調(diào)試消息。
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
}
// 在 logException 方法中顯式配置的 warn 日志器。
logException(ex, request);
}
return result;
}
else {
return null;
}
}
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
// ErrorResponse exceptions that expose HTTP response details
if (ex instanceof ErrorResponse errorResponse) {
ModelAndView mav = null;
if (ex instanceof HttpRequestMethodNotSupportedException theEx) {
mav = handleHttpRequestMethodNotSupported(theEx, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException theEx) {
mav = handleHttpMediaTypeNotSupported(theEx, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException theEx) {
mav = handleHttpMediaTypeNotAcceptable(theEx, request, response, handler);
}
else if (ex instanceof MissingPathVariableException theEx) {
mav = handleMissingPathVariable(theEx, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException theEx) {
mav = handleMissingServletRequestParameter(theEx, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException theEx) {
mav = handleMissingServletRequestPartException(theEx, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException theEx) {
mav = handleServletRequestBindingException(theEx, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException theEx) {
mav = handleMethodArgumentNotValidException(theEx, request, response, handler);
}
else if (ex instanceof HandlerMethodValidationException theEx) {
mav = handleHandlerMethodValidationException(theEx, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException theEx) {
mav = handleNoHandlerFoundException(theEx, request, response, handler);
}
else if (ex instanceof NoResourceFoundException theEx) {
mav = handleNoResourceFoundException(theEx, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException theEx) {
mav = handleAsyncRequestTimeoutException(theEx, request, response, handler);
}
return (mav != null ? mav :
handleErrorResponse(errorResponse, request, response, handler));
}
// Other, lower level exceptions
if (ex instanceof ConversionNotSupportedException theEx) {
return handleConversionNotSupported(theEx, request, response, handler);
}
else if (ex instanceof TypeMismatchException theEx) {
return handleTypeMismatch(theEx, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException theEx) {
return handleHttpMessageNotReadable(theEx, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException theEx) {
return handleHttpMessageNotWritable(theEx, request, response, handler);
}
else if (ex instanceof MethodValidationException theEx) {
return handleMethodValidationException(theEx, request, response, handler);
}
else if (ex instanceof BindException theEx) {
return handleBindException(theEx, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
/**
* 處理 {@link ErrorResponse} 異常。
* <p>默認(rèn)實(shí)現(xiàn)將狀態(tài)和頭信息設(shè)置為從 {@code ErrorResponse} 獲取到的值。
* 如果可用,{@link ProblemDetail#getDetail()} 將用作
* {@link HttpServletResponse#sendError(int, String)} 的消息。
* @param errorResponse 需要處理的異常
* @param request 當(dāng)前的 HTTP 請求
* @param response 當(dāng)前的 HTTP 響應(yīng)
* @param handler 執(zhí)行的處理器
* @return 一個(gè)空的 {@code ModelAndView},表示異常已處理
* @throws IOException 可能從 {@link HttpServletResponse#sendError} 拋出
* @since 6.0
*/
protected ModelAndView handleErrorResponse(ErrorResponse errorResponse,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
if (!response.isCommitted()) {
HttpHeaders headers = errorResponse.getHeaders();
headers.forEach((name, values) -> values.forEach(value -> response.addHeader(name, value)));
int status = errorResponse.getStatusCode().value();
String message = errorResponse.getBody().getDetail();
if (message != null) {
response.sendError(status, message);
}
else {
response.sendError(status);
}
}
else {
logger.warn("忽略異常,響應(yīng)已提交。: " + errorResponse);
}
return new ModelAndView();
}
}
初始化RequestToViewNameTranslator
當(dāng)Controller處理器方法沒有返回一個(gè)View對象或邏輯視圖名稱,并且在該方法中沒有直接往Response的輸出流里面寫數(shù)據(jù)的時(shí)候,Spring就會采用約定好的方式提供一個(gè)邏輯視圖名稱。這個(gè)邏輯視圖名稱是通過Spring定義的RequestToViewNameTranslator接口的getViewName方法來實(shí)現(xiàn)的。首先看一下初始化RequestToViewNameTranslator的源碼如下:
/**
* 初始化該servlet實(shí)例使用的RequestToViewNameTranslator。
* <p>如果沒有配置實(shí)現(xiàn),則默認(rèn)使用DefaultRequestToViewNameTranslator。
*/
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator = context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
if (logger.isTraceEnabled()) {
logger.trace("檢測到 " + this.viewNameTranslator.getClass().getSimpleName());
}
else if (logger.isDebugEnabled()) {
logger.debug("檢測到 " + this.viewNameTranslator);
}
}
catch (NoSuchBeanDefinitionException ex) {
// 需要使用默認(rèn)實(shí)現(xiàn)
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
if (logger.isTraceEnabled()) {
logger.trace("未檢測到RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
"': 使用默認(rèn) [" + this.viewNameTranslator.getClass().getSimpleName() + "]");
}
}
}
Spring已經(jīng)給我們提供了一個(gè)它自己的實(shí)現(xiàn),就是DefaultRequestToViewNameTranslator,源碼如下:
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
private static final String SLASH = "/";
private String prefix = "";
private String suffix = "";
private String separator = SLASH;
private boolean stripLeadingSlash = true;
private boolean stripTrailingSlash = true;
private boolean stripExtension = true;
/**
* Translates the request URI of the incoming {@link HttpServletRequest}
* into the view name based on the configured parameters.
* @throws IllegalArgumentException if neither a parsed RequestPath, nor a
* String lookupPath have been resolved and cached as a request attribute.
* @see ServletRequestPathUtils#getCachedPath(ServletRequest)
* @see #transformPath
*/
@Override
public String getViewName(HttpServletRequest request) {
String path = ServletRequestPathUtils.getCachedPathValue(request);
return (this.prefix + transformPath(path) + this.suffix);
}
/**
* Transform the request URI (in the context of the webapp) stripping
* slashes and extensions, and replacing the separator as required.
* @param lookupPath the lookup path for the current request,
* as determined by the UrlPathHelper
* @return the transformed path, with slashes and extensions stripped
* if desired
*/
@Nullable
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && path.startsWith(SLASH)) {
path = path.substring(1);
}
if (this.stripTrailingSlash && path.endsWith(SLASH)) {
path = path.substring(0, path.length() - 1);
}
if (this.stripExtension) {
path = StringUtils.stripFilenameExtension(path);
}
if (!SLASH.equals(this.separator)) {
path = StringUtils.replace(path, SLASH, this.separator);
}
return path;
}
}
初始化ViewResolvers
在SpringMVC中,當(dāng)Controller將請求處理結(jié)果放入到ModelAndView中以后,DispatcherServlet會根據(jù)ModelAndView選擇合適的視圖進(jìn)行渲染。ViewResolver接口定義了resolveViewName方法,根據(jù)viewName創(chuàng)建合適類型的View實(shí)現(xiàn)。初始化ViewResolvers的源碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-768566.html
/**
* 初始化用于此類的 ViewResolvers。
* <p如果在此命名空間的 BeanFactory 中未定義 ViewResolver 象,我們將默認(rèn)使用 InternalResourceViewResolver。
*/
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
// 在 ApplicationContext 中查找所有命名空間的 ViewResolvers,包括祖先層級。
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// 我們按升序排列 ViewResolvers。
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
} else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
// 忽略,后面我們會添加一個(gè)默認(rèn)的 ViewResolver。
}
}
// 如果沒有找到其他 ViewResolver,則通過從 DispatcherServlet.properties 中注冊一個(gè)默認(rèn)的 ViewResolver 來確保至少有一個(gè) ViewResolver。
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("未為 servlet '" + getServletName() +
" 聲明 ViewResolvers:使用默認(rèn)的 ViewResolver 策略");
}
}
}
初始化FlashMapManager
SpringMVC Flush提供了一個(gè)請求存儲屬性,可供其他請求使用。在使用重定向的時(shí)候非常必要,例如POST/GET/DELETE。初始化FlashMapManager的源碼如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-768566.html
/**
* 初始化由此servlet實(shí)例使用的FlashMapManager。
* <p>如果未配置實(shí)現(xiàn),則默認(rèn)為{@code org.springframework.web.servlet.support.DefaultFlashMapManager}。
*/
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.flashMapManager);
}
}
catch (NoSuchBeanDefinitionException ex) {
// 需要使用默認(rèn)實(shí)現(xiàn)
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
if (logger.isTraceEnabled()) {
logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +
"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");
}
}
}
到了這里,關(guān)于SpringMVC源碼解析——DispatcherServlet初始化的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!