亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringMVC初始化流程實例分析

發布時間:2022-07-08 09:21:38 來源:億速云 閱讀:143 作者:iii 欄目:開發技術

本文小編為大家詳細介紹“SpringMVC初始化流程實例分析”,內容詳細,步驟清晰,細節處理妥當,希望這篇“SpringMVC初始化流程實例分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

1.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 的初始化操作。

2.FrameworkServlet

從前面的介紹可知,FrameworkServlet 初始化的入口方法就是 initServletBean,因此我們就從 FrameworkServlet#initServletBean 方法開始看起:

@Override
protected final void initServletBean() throws ServletException {
 //省略...
 try {
  this.webApplicationContext = initWebApplicationContext();
  initFrameworkServlet();
 }
 catch (ServletException | RuntimeException ex) {
  //省略...
 }
}

這個方法原本挺長的,但是拋開日志打印異常拋出,剩下的核心代碼其實就兩行:

  • initWebApplicationContext 方法用來初始化 WebApplicationContext。

  • initFrameworkServlet 方法用來初始化 FrameworkServlet,但是這個方法是一個空方法,沒有具體的實現。本來子類可以重寫該方法做一些初始化操作,但是實際上子類并沒有重寫該方法,所以這個方法我們就暫且忽略之,不去分析了。

那么這里最為重要的其實就是 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;
}

這里的邏輯也比較清晰:

  1. 首先獲取 rootContext。在默認情況下,Spring 會將容器設置為 ServletContext 的一個屬性,屬性的 key 為 org.springframework.web.context.WebApplicationContext.ROOT,所以根據這個 key 就可以調用 ServletContext#getAttribute 方法獲取到 rootContext 了。

  2. 獲取 WebApplicationContext 實例,也就是給 wac 變量賦值的過程,這里存在三種可能性:1.如果已經通過構造方法給 webApplicationContext 賦值了,則直接將其賦給 wac 變量,同時,如果需要設置 parent 就設置,需要刷新就刷新。這種方式適用于 Servlet3.0 以后的環境,因為從 Servlet3.0 開始,才支持直接調用 ServletContext.addServlet 方法去注冊 Servlet,手動注冊的時候就可以使用自己提前準備好的 WebApplicationContext 了,這塊松哥在我錄制的 Spring Boot 視頻中也講過,感興趣的小伙伴可以在公眾號后臺回復 vhr 查看視頻詳情;2.如果第一步沒能成功給 wac 賦值,那么調用 findWebApplicationContext 方法嘗試去 ServletContext 中查找 WebApplicationContext 對象,找到了就賦值給 wac;3.如果第二步沒能成功給 wac 賦值,那么調用 createWebApplicationContext 方法創建一個 WebApplicationContext 對象并賦值給 wac,一般來說都是通過這種方式創建的 WebApplicationContext。這三套組合拳下來,wac 肯定是有值了。

  3. 當 ContextRefreshedEvent 事件沒有觸發時,調用 onRefresh 方法完成容器刷新(由于第一種和第三種獲取 WebApplicationContext 的方式最終都會調用 configureAndRefreshWebApplicationContext 方法,然后發布事件,再將 refreshEventReceived 變量標記為 true,所以實際上只有第二種方式獲取 wac 實例的時候,這里才會刷新,具體可以看下文分析)。

  4. 最后將 wac 保存到到 ServletContext 中。保存的時候會根據 publishContext 變量的值來決定是否保存,publishContext 可以在 web.xml 中配置 Servlet 時通過 init-param 進行配置,保存的目的是為了方便獲取。

上面的這些步驟中,通過 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。

3.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 文件中加載到的,我們來看下這個文件內容:

SpringMVC初始化流程實例分析

可以看到,這里一共定義了 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初始化流程實例分析”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

马尔康县| 德保县| 荔浦县| 桃源县| 四子王旗| 龙胜| 兴义市| 普兰县| 三亚市| 鹰潭市| 丽水市| 巨鹿县| 呈贡县| 盐亭县| 桃江县| 泰来县| 亚东县| 大连市| 南郑县| 达拉特旗| 旬邑县| 澄江县| 杭州市| 高要市| 陆河县| 白城市| 保山市| 丹阳市| 古浪县| 怀柔区| 全椒县| 资讯| 象山县| 乌恰县| 车险| 博白县| 吉木乃县| 彭阳县| 沈丘县| 芜湖市| 宁国市|