<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
框架原始碼是我們 Coding 晉級中的必修課,SSM 應該算是小夥伴們日常接觸最多的框架了,這其中 SpringMVC 初始化流程相對來說要簡單一些,因此今天鬆哥就先來和大家分析一下 SpringMVC 初始化流程。
本文算是 SpringMVC 用法的一個進階,如果小夥伴們對 SpringMVC 的基礎用法還不熟悉,可以在公眾號後臺回覆 ssm,有鬆哥錄製的免費視訊教學。
即使你沒看過 SpringMVC 的原始碼,估計也聽說過:DispatcherServlet 是 SpringMVC 的大腦,它負責整個 SpringMVC 的排程工作,是 SpringMVC 中最最核心的類,SpringMVC 整個頂層架構設計都體現在這裡,所以搞明白 DispatcherServlet 的原始碼,基本上 SpringMVC 的工作原理也就瞭然於胸了。
然而 DispatcherServlet 繼承自 FrameworkServlet,FrameworkServlet 又繼承自 HttpServletBean,如下圖:
因此我們的分析就從 HttpServletBean 開始。
HttpServletBean
繼承自 HttpServlet
,它負責將 init-param
中的引數注入到當前 Servlet
範例的屬性中,同時也為子類提供了增加 requiredProperties
的能力,需要注意的是 HttpServletBean
並不依賴於 Spring
容器。
大家知道,HttpServlet 的初始化是從 init 方法開始的,所以我們就先從 HttpServletBean 的 init 方法開始看起:
@Override public final void init() throws ServletException { // Set bean properties from init parameters. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. initServletBean(); }
在這個方法裡,首先獲取到 Servlet 的所有設定並轉為 PropertyValues,然後通過 BeanWrapper 修改目標 Servlet 的相關屬性。BeanWrapper 是 Spring 中提供一個工具,使用它可以修改一個物件的屬性,像下面這樣:
public class Main { public static void main(String[] args) { User user = new User(); BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(user); beanWrapper.setPropertyValue("username", "itboyhub"); PropertyValue pv = new PropertyValue("address", "www.itboyhub.com"); beanWrapper.setPropertyValue(pv); System.out.println("user = " + user); } }
最終輸出:
user = User{username='itboyhub', address='www.itboyhub.com'}
所以前面的 bw 實際上就代表當前 DispatcherServlet 物件。
通過 BeanWrapper 修改目標 Servlet 的相關屬性時,有一個 initBeanWrapper 方法是空方法,開發者如有需要可以在子類中實現該方法,並且完成一些初始化操作。
屬性設定完成後,最終呼叫 initServletBean 方法進行 Servlet 初始化,然而該方法也是一個空方法,在子類中實現。
這就是 HttpServletBean 所做的事情,比較簡單,載入 Servlet 相關屬性並設定給當前 Servlet 物件,然後呼叫 initServletBean 方法繼續完成 Servlet 的初始化操作。
從前面的介紹可知,FrameworkServlet 初始化的入口方法就是 initServletBean,因此我們就從 FrameworkServlet#initServletBean 方法開始看起:
@Override protected final void initServletBean() throws ServletException { //省略... try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { //省略... } }
這個方法原本挺長的,但是拋開紀錄檔列印異常丟擲,剩下的核心程式碼其實就兩行:
那麼這裡最為重要的其實就是 initWebApplicationContext 方法了,我們一起來看下:
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = findWebApplicationContext(); } if (wac == null) { wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { synchronized (this.onRefreshMonitor) { onRefresh(wac); } } if (this.publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
這裡的邏輯也比較清晰:
org.springframework.web.context.WebApplicationContext.ROOT
,所以根據這個 key 就可以呼叫 ServletContext#getAttribute 方法獲取到 rootContext 了。上面的這些步驟中,通過 createWebApplicationContext 方法建立 WebApplicationContext 物件需要和大家細說下,因為一般情況下就是通過這種方式建立的 WebApplicationContext。我們來看一下相關的方法:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default 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())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); }
這裡一共涉及到兩個方法:
createWebApplicationContext
首先獲取到建立型別,並檢查建立型別,沒問題的話呼叫 instantiateClass 方法完成建立工作,然後給建立好的 wac 物件設定各種屬性,設定的 configLocation 就是我們在 web.xml 檔案中設定的 SpringMVC 組態檔路徑,預設的檔案路徑是 /WEB-INF/[servletName]-servlet.xml
。
configureAndRefreshWebApplicationContext
configureAndRefreshWebApplicationContext 方法主要也是設定&重新整理 WebApplicationContext,在這個方法裡會呼叫 addApplicationListener 為 wac 新增一個監聽器,監聽的是 ContextRefreshedEvent 事件,當收到該事件後,會呼叫 FrameworkServlet 的 onApplicationEvent 方法,並在該方法中呼叫 onRefresh 方法完成重新整理,重新整理之後,會將 refreshEventReceived 變數標記為 true。
public void onApplicationEvent(ContextRefreshedEvent event) { this.refreshEventReceived = true; synchronized (this.onRefreshMonitor) { onRefresh(event.getApplicationContext()); } }
這就是 FrameworkServlet#initServletBean 方法的大致工作邏輯。這裡涉及到了 onRefresh 方法,但是這是一個空方法,在子類 DispatcherServlet 中實現了,所以接下來我們就來看 DispatcherServlet。
這裡我們就不廢話了,直接來看 onRefresh 方法,如下:
@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
在 onRefresh 方法中呼叫了 initStrategies 進行初始化操作。initStrategies 的內容其實很簡單,就是九個元件的初始化。九個的初始化流程比較類似,這裡我們以常見的檢視解析器的初始化方法 initViewResolvers 為例,來一起看看初始化流程:
private void initViewResolvers(ApplicationContext context) { this.viewResolvers = null; if (this.detectAllViewResolvers) { // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList<>(matchingBeans.values()); // We keep ViewResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.viewResolvers); } } else { try { ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default ViewResolver later. } } // Ensure we have at least one ViewResolver, by registering // a default ViewResolver if no other resolvers are found. if (this.viewResolvers == null) { this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); if (logger.isTraceEnabled()) { logger.trace("No ViewResolvers declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
一開始的 viewResolvers 變數是一個集合,解析出來的檢視解析器物件都將放入這個集合中。
首先判斷 detectAllViewResolvers 變數是否為 true,如果為 true,則直接去查詢 Spring 容器中的所有檢視解析器,將查詢結果賦值給 viewResolvers,然後進行排序。預設情況下 detectAllViewResolvers 變數的值為 true,如果有需要,可以在 web.xml 中進行設定,像下面這樣:
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-servlet.xml</param-value> </init-param> <init-param> <param-name>detectAllViewResolvers</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
如果 detectAllViewResolvers 的值為 false,那麼接下來就會去 Spring 容器中查詢一個名為 viewResolver 的檢視解析器,此時查詢到的就是一個單獨的檢視解析器。
一般來說,我們並不需要在 web.xml 中去設定 detectAllViewResolvers 的值,檢視解析器有多少個就載入多少個。
舉個簡單例子,我們在 SpringMVC 的組態檔中可能像下面這樣設定檢視解析器:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
預設情況下,這個 bean 的 id 有沒有都行,如果有,取什麼值都可以,反正最終都是通過型別而不是 id 去查詢的檢視解析器。但是如果你在 web.xml 中將 detectAllViewResolvers 修改為 false,那麼這個 bean 的 id 取值就比較重要了,就一定要是 viewResolver。
如果在 Spring 容器中通過這兩種方式(通過型別查詢或通過 id 查詢)都沒有找到 ViewResolver 範例,那麼會呼叫 getDefaultStrategies 方法去獲取一個預設的 ViewResolver 範例。預設範例的獲取方式如下:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { if (defaultStrategies == null) { try { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } } String key = strategyInterface.getName(); String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(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( "Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err); } } return strategies; } else { return Collections.emptyList(); } }
這段程式碼其實也比較簡單,就是通過反射去獲取預設的檢視解析器。
首先給 defaultStrategies 賦值,defaultStrategies 的值實際上就是從 DispatcherServlet.properties 檔案中載入到的,我們來看下這個檔案內容:
可以看到,這裡一共定義了 8 個預設的鍵值對,有的值是一個,有的值是多個。前面 initStrategies 方法中一共要初始化九個元件,這裡預設只定義了 8 個,少了一個 MultipartResolver,這也好理解,並非所有的專案都有檔案上傳,而且即使有檔案上傳,用哪一個具體的 MultipartResolver 也不好確定,還是要開發者自己決定。
defaultStrategies 其實載入到的就是這 8 個鍵值對,其中檢視解析器對應的是 org.springframework.web.servlet.view.InternalResourceViewResolver,通過反射建立該類的範例,當 Spring 容器中不存在任何檢視解析器的時候,預設的檢視解析器即此。
這就是 initViewResolvers 的工作流程,另外 8 個也和它差不多,唯一不同的是 initMultipartResolver,如下:
private void initMultipartResolver(ApplicationContext context) { try { this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); } catch (NoSuchBeanDefinitionException ex) { this.multipartResolver = null; } }
可以看到,它只是根據 bean 的名字去查詢 bean 範例,沒有去查詢預設的 MultipartResolver。
說到這裡,鬆哥和大家多說一句 SpringMVC 設定中的小細節,
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"> </bean>
上面這個關於檢視解析器和檔案上傳解析器的設定,不知道小夥伴們有沒有注意過,檢視解析器的 id 可有可無,而檔案上傳解析器的 id 必須是 multipartResolver,回顧我們上面的原始碼分析,你就知道為啥了!
好啦,這就是鬆哥和小夥伴們分享的 SpringMVC 的初始化流程,主要涉及到了 HttpServletBean、FrameworkServlet 以及 DispatcherServlet 三個範例,HttpServletBean 主要是載入 Servlet 設定的各種屬性並設定到 Servlet 上;FrameworkServlet 則主要是初始化了 WebApplicationContext;DispatcherServlet 則主要是初始化了自身的九個元件。
到此這篇關於深入瞭解SpringMVC初始化流程的文章就介紹到這了,更多相關SpringMVC初始化流程內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45