主頁 > 後端開發 > 精盡Spring MVC原始碼分析 - WebApplicationContext 容器的初始化

精盡Spring MVC原始碼分析 - WebApplicationContext 容器的初始化

2020-12-15 06:55:57 後端開發

該系列檔案是本人在學習 Spring MVC 的原始碼程序中總結下來的,可能對讀者不太友好,請結合我的原始碼注釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.2.4.RELEASE

該系列其他檔案請查看:《精盡 Spring MVC 原始碼分析 - 文章導讀》

隨著 Spring BootSpring Cloud 在許多中大型企業中被普及,可能你已經忘記當年經典的 Servlet + Spring MVC 的組合,是否還記得那個 web.xml 組態檔,在開始本文之前,請先拋開 Spring Boot 到一旁,回到從前,一起來看看 Servlet 是怎么和 Spring MVC 集成,怎么來初始化 Spring 容器的,在開始閱讀本文之前,最好有一定的 Servlet 和 Spring IOC 容器方面的知識,比較容易理解

概述

在開始看具體的原始碼實作之前,我們先一起來看看現在“陌生”的 web.xml 檔案,可以查看我的另一篇 MyBatis 使用手冊 檔案中集成 Spring小節涉及到的 web.xml 的檔案,部分內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>Archetype Created Web Application</display-name>

    <!-- 【1】 Spring 配置 -->
    <!-- 在容器(Tomcat、Jetty)啟動時會被 ContextLoaderListener 監聽到,
         從而呼叫其 contextInitialized() 方法,初始化 Root WebApplicationContext 容器 -->
    <!-- 宣告 Spring Web 容器監聽器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- Spring 和 MyBatis 的組態檔 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml</param-value>
    </context-param>

    <!-- 【2】 Spring MVC 配置 -->
    <!-- 1.SpringMVC 配置 前置控制器(SpringMVC 的入口)
         DispatcherServlet 是一個 Servlet,所以可以配置多個 DispatcherServlet -->
    <servlet>
        <!-- 在 DispatcherServlet 的初始化程序中,框架會在 web 應用 的 WEB-INF 檔案夾下,
             尋找名為 [servlet-name]-servlet.xml 的組態檔,生成檔案中定義的 Bean. -->
        <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-mvc.xml</param-value>
        </init-param>
        <!-- 程式運行時從 web.xml 開始,加載順序為:context-param -> Listener -> Filter -> Structs -> Servlet
             設定 web.xml 檔案啟動時加載的順序(1 代表容器啟動時首先初始化該 Servlet,讓這個 Servlet 隨 Servlet 容器一起啟動)
             load-on-startup 是指這個 Servlet 是在當前 web 應用被加載的時候就被創建,而不是第一次被請求的時候被創建  -->
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <!-- 這個 Servlet 的名字是 SpringMVC,可以有多個 DispatcherServlet,是通過名字來區分的
             每一個 DispatcherServlet 有自己的 WebApplicationContext 背景關系物件,同時保存在 ServletContext 中和 Request 物件中
             ApplicationContext(Spring 容器)是 Spring 的核心
             Context 我們通常解釋為背景關系環境,Spring 把 Bean 放在這個容器中,在需要的時候,可以 getBean 方法取出-->
        <servlet-name>SpringMVC</servlet-name>
        <!-- Servlet 攔截匹配規則,可選配置:*.do、*.action、*.html、/、/xxx/* ,不允許:/* -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

【1】 處,配置了 org.springframework.web.context.ContextLoaderListener 物件,它實作了 Servlet 的 javax.servlet.ServletContextListener 介面,能夠監聽 ServletContext 物件的生命周期,也就是監聽 Web 應用的生命周期,當 Servlet 容器啟動或者銷毀時,會觸發相應的 ServletContextEvent 事件,ContextLoaderListener 監聽到啟動事件,則會初始化一個Root Spring WebApplicationContext 容器,監聽到銷毀事件,則會銷毀該容器

【2】 處,配置了 org.springframework.web.servlet.DispatcherServlet 物件,它繼承了 javax.servlet.http.HttpServlet 抽象類,也就是一個 Servlet,Spring MVC 的核心類,處理請求,會初始化一個屬于它的 Spring WebApplicationContext 容器,并且這個容器是以 【1】 處的 Root 容器作為父容器

  • 為什么有了 【2】 創建了容器,還需要 【1】 創建 Root 容器呢?因為可以配置多個 【2】 呀,當然,實際場景下,不太會配置多個 【2】 ??
  • 再總結一次,【1】【2】 分別會創建其對應的 Spring WebApplicationContext 容器,并且它們是父子容器的關系

Root WebApplicationContext 容器

概述web.xml中,我們已經看到,Root WebApplicationContext 容器的初始化,通過 ContextLoaderListener 來實作,在 Servlet 容器啟動時,例如 Tomcat、Jetty 啟動后,則會被 ContextLoaderListener 監聽到,從而呼叫 contextInitialized(ServletContextEvent event) 方法,初始化 Root WebApplicationContext 容器

而 ContextLoaderListener 的類圖如下:

ContextLoader

ContextLoaderListener

org.springframework.web.context.ContextLoaderListener 類,實作 javax.servlet.ServletContextListener 介面,繼承 ContextLoader 類,實作 Servlet 容器啟動和關閉時,分別初始化和銷毀 WebApplicationContext 容器,代碼如下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	public ContextLoaderListener() {
	}

    /**
     * As of Spring 3.1, supports injecting the root web application context
     */
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
        // <1> 初始化 Root WebApplicationContext
		initWebApplicationContext(event.getServletContext());
	}


	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
        // <2> 銷毀 Root WebApplicationContext
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}
  1. 監聽到 Servlet 容器啟動事件,則呼叫父類 ContextLoader 的 initWebApplicationContext(ServletContext servletContext) 方法,初始化 WebApplicationContext 容器
  2. 監聽到 Servlet 銷毀啟動事件,則呼叫父類 ContextLoader 的 closeWebApplicationContext(ServletContext servletContext) 方法,銷毀 WebApplicationContext 容器

ContextLoader

org.springframework.web.context.ContextLoader 類,真正實作初始化和銷毀 WebApplicationContext 容器的邏輯的類

靜態代碼塊
public class ContextLoader {

	/**
	 * Name of the class path resource (relative to the ContextLoader class)
	 * that defines ContextLoader's default strategy names.
	 */
	private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

    /**
     * 默認的配置 Properties 物件
     */
	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, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}
}

ContextLoader.properties 中,讀取默認的配置 Properties 物件,實際上,正如 Load default strategy implementations from properties file. This is currently strictly internal and not meant to be customized by application developers. 所注釋,這是一個應用開發者無需關心的配置,而是 Spring 框架自身所定義的

打開來該檔案瞅瞅,代碼如下:

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

這意味著什么呢?如果我們沒有在 <context-param /> 標簽中指定 WebApplicationContext,則默認使用 XmlWebApplicationContext 類,我們在使用 Spring 的程序中一般情況下不會主動指定

構造方法
public class ContextLoader {
    
    /**
	 * Name of servlet context parameter (i.e., {@value}) that can specify the
	 * config location for the root context, falling back to the implementation's default otherwise.
	 * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
	 */
	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
    
	/** Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext. */
	private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = new ConcurrentHashMap<>(1);

	/** The 'current' WebApplicationContext, if the ContextLoader class is deployed in the web app ClassLoader itself. */
	@Nullable
	private static volatile WebApplicationContext currentContext;


	/** The root WebApplicationContext instance that this loader manages. */
	@Nullable
	private WebApplicationContext context;

	/**
	 * Create a new {@code ContextLoader} that will create a web application context
	 * based on the "contextClass" and "contextConfigLocation" servlet context-params.
	 * See class-level documentation for details on default values for each.
	 */
	public ContextLoader() {
	}

	/**
	 * Create a new {@code ContextLoader} with the given application context.
     * This constructor is useful in Servlet 3.0+ environments where instance-based
	 * registration of listeners is possible through the {@link ServletContext#addListener} API.
	 */
	public ContextLoader(WebApplicationContext context) {
		this.context = context;
	}
    
    // ... 省略其他相關配置屬性
}
  • 概述web.xml 檔案中可以看到定義的 contextConfigLocation 引數為 spring-mybatis.xml 組態檔路徑
  • currentContextPerThread:用于保存當前 ClassLoader 類加載器與 WebApplicationContext 物件的映射關系
  • currentContext:如果當前執行緒的類加載器就是 ContextLoader 類所在的類加載器,則該屬性用于保存 WebApplicationContext 物件
  • context:WebApplicationContext 實體物件

關于類加載器涉及到 JVM 的“雙親委派機制”,在《精盡MyBatis原始碼分析 - 基礎支持層》 有簡單的講述到,可以參考一下

initWebApplicationContext

initWebApplicationContext(ServletContext servletContext) 方法,初始化 WebApplicationContext 物件,代碼如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // <1> 若已經存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 對應的 WebApplicationContext 物件,則拋出 IllegalStateException 例外,
    // 例如,在 web.xml 中存在多個 ContextLoader
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    // <2> 列印日志
    servletContext.log("Initializing Spring root WebApplicationContext");
    Log logger = LogFactory.getLog(ContextLoader.class);
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    // 記錄開始時間
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
            // <3> 初始化 context ,即創建 context 物件
            this.context = createWebApplicationContext(servletContext);
        }
        // <4> 如果是 ConfigurableWebApplicationContext 的子類,如果未重繪,則進行配置和重繪
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) { // <4.1> 未重繪( 激活 )
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) { // <4.2> 無父容器,則進行加載和設定,
                    // The context instance was injected without an explicit parent ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                // <4.3> 配置 context 物件,并進行重繪
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // <5> 記錄在 servletContext 中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        // <6> 記錄到 currentContext 或 currentContextPerThread 中
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
        }

        // <7> 回傳 context
        return this.context;
    }
    catch (RuntimeException | Error ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}
  1. 若 ServletContext(Servlet 的背景關系)已存在 Root WebApplicationContext 物件,則拋出例外,因為不能再初始化該物件

  2. 列印日志,在啟動 SSM 專案的時候,是不是都會看到這個日志“Initializing Spring root WebApplicationContext”

  3. 如果context為空,則呼叫createWebApplicationContext(ServletContext sc)方法,初始化一個 Root WebApplicationContext 物件,方法如下:

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        // <1> 獲得 context 的類(默認情況是從 ContextLoader.properties 組態檔讀取的,為 XmlWebApplicationContext)
        Class<?> contextClass = determineContextClass(sc);
        // <2> 判斷 context 的類,是否符合 ConfigurableWebApplicationContext 的型別
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        // <3> 創建 context 的類的物件
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    
  4. 如果是 ConfigurableWebApplicationContext 的子類,并且未重繪,則進行配置和重繪

    1. 如果未重繪(激活),默認情況下,是符合這個條件的,所以會往下執行
    2. 如果無父容器,則進行加載和設定,默認情況下,loadParentContext(ServletContext servletContext) 方法回傳一個空物件,也就是沒有父容器了
    3. 呼叫configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)方法,配置context物件,并進行重繪
  5. context物件保存在 ServletContext 中

  6. context物件設定到currentContext或者currentContextPerThread物件中,差異就是類加載器是否相同,具體用途目前不清楚??

  7. 回傳已經初始化的context物件

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) 方法,配置 ConfigurableWebApplicationContext 物件,并進行重繪,方法如下:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    // <1> 如果 wac 使用了默認編號,則重新設定 id 屬性
    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
        // 情況一,使用 contextId 屬性
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else { // 情況二,自動生成
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    // <2>設定 context 的 ServletContext 屬性
    wac.setServletContext(sc);
    // <3> 設定 context 的組態檔地址
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    // 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(sc, null);
    }

    // <4> 對 context 進行定制化處理
	customizeContext(sc, wac);
	// <5> 重繪 context ,執行初始化
	wac.refresh();
}
  1. 如果 wac 使用了默認編號,則重新設定 id 屬性,默認情況下,我們不會對 wac 設定編號,所以會執行進去,而實際上,id 的生成規則,也分成使用 contextId<context-param /> 標簽中由用戶配置,和自動生成兩種情況,?? 默認情況下,會走第二種情況

  2. 設定 wac 的 ServletContext 屬性

  3. 【關鍵】設定 context 的組態檔地址,例如我們在概述中的 web.xml 中所看到的

    <!-- Spring 和 MyBatis 的組態檔 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml</param-value>
    </context-param>
    
  4. wac 進行定制化處理,暫時忽略

  5. 【關鍵】觸發 wac 的重繪事件,執行初始化,此處,就會進行一些的 Spring 容器的初始化作業,涉及到 Spring IOC 相關內容

closeWebApplicationContext

closeWebApplicationContext(ServletContext servletContext) 方法,關閉 WebApplicationContext 容器物件,方法如下:

public void closeWebApplicationContext(ServletContext servletContext) {
    servletContext.log("Closing Spring root WebApplicationContext");
    try {
        // 關閉 context
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ((ConfigurableWebApplicationContext) this.context).close();
        }
    }
    finally {
        // 移除 currentContext 或 currentContextPerThread
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = null;
        }
        else if (ccl != null) {
            currentContextPerThread.remove(ccl);
        }
        // 從 ServletContext 中移除
        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
}

在 Servlet 容器銷毀時被呼叫,用于關閉 WebApplicationContext 物件,以及清理相關資源物件

Servlet WebApplicationContext 容器

概述web.xml中,我們已經看到,除了會初始化一個 Root WebApplicationContext 容器外,還會往 Servlet 容器的 ServletContext 背景關系中注入一個 DispatcherServlet 物件,初始化該物件的程序也會初始化一個 Servlet WebApplicationContext 容器

DispatcherServlet 的類圖如下:

DispatcherServlet

可以看到 DispatcherServlet 是一個 Servlet 物件,在注入至 Servlet 容器會呼叫其 init 方法,完成一些初始化作業

  • HttpServletBean ,負責將 ServletConfig 設定到當前 Servlet 物件中,它的 Java doc:

    /**
     * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
     * its config parameters ({@code init-param} entries within the
     * {@code servlet} tag in {@code web.xml}) as bean properties.
     */
    
  • FrameworkServlet ,負責初始化 Spring Servlet WebApplicationContext 容器,同時該類覆寫了 doGet、doPost 等方法,并將所有型別的請求委托給 doService 方法去處理,doService 是一個抽象方法,需要子類實作,它的 Java doc:

    /**
     * Base servlet for Spring's web framework. Provides integration with
     * a Spring application context, in a JavaBean-based overall solution.
     */
    
  • DispatcherServlet ,負責初始化 Spring MVC 的各個組件,以及處理客戶端的請求,協調各個組件作業,它的 Java doc:

    /**
     * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
     * or HTTP-based remote service exporters. Dispatches to registered handlers for processing
     * a web request, providing convenient mapping and exception handling facilities.
     */
    

每一層的 Servlet 實作類,負責執行相應的邏輯,條理清晰,我們逐個來看

HttpServletBean

org.springframework.web.servlet.HttpServletBean 抽象類,實作 EnvironmentCapable、EnvironmentAware 介面,繼承 HttpServlet 抽象類,負責將 ServletConfig 集成到 Spring 中

構造方法
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

	@Nullable
	private ConfigurableEnvironment environment;

	/**
	 * 必須配置的屬性的集合,在 {@link ServletConfigPropertyValues} 中,會校驗是否有對應的屬性
	 * 默認為空
	 */
	private final Set<String> requiredProperties = new HashSet<>(4);

	protected final void addRequiredProperty(String property) {
		this.requiredProperties.add(property);
	}

    /**
     * 實作了 EnvironmentAware 介面,自動注入 Environment 物件
     */
	@Override
	public void setEnvironment(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required");
		this.environment = (ConfigurableEnvironment) environment;
	}

    /**
     * 實作了 EnvironmentAware 介面,回傳 Environment 物件
     */
	@Override
	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
            // 如果 Environment 為空,則創建 StandardServletEnvironment 物件
			this.environment = createEnvironment();
		}
		return this.environment;
	}

	/**
	 * Create and return a new {@link StandardServletEnvironment}.
	 */
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardServletEnvironment();
	}
}

關于 xxxAware介面,在 Spring 初始化該 Bean 的時候會呼叫其setXxx方法來注入一個物件,本文暫不分析

init方法

init()方法,重寫 GenericServlet 中的方法,負責將 ServletConfig 設定到當前 Servlet 物件中,方法如下:

@Override
public final void init() throws ServletException {
    // Set bean properties from init parameters.
    // <1> 決議 <init-param /> 標簽,封裝到 PropertyValues pvs 中
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // <2.1> 將當前的這個 Servlet 物件,轉化成一個 BeanWrapper 物件,從而能夠以 Spring 的方式來將 pvs 注入到該 BeanWrapper 物件中
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            // <2.2> 注冊自定義屬性編輯器,一旦碰到 Resource 型別的屬性,將會使用 ResourceEditor 進行決議
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            // <2.3> 空實作,留給子類覆寫,目前沒有子類實作
            initBeanWrapper(bw);
            // <2.4> 以 Spring 的方式來將 pvs 注入到該 BeanWrapper 物件中
            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.
    // 交由子類去實作,查看 FrameworkServlet#initServletBean() 方法
    initServletBean();
}
  1. 決議 Servlet 配置的 <init-param /> 標簽,封裝成 PropertyValues pvs 物件,其中,ServletConfigPropertyValues 是 HttpServletBean 的私有靜態類,繼承 MutablePropertyValues 類,ServletConfig 的 封裝實作類,該類的代碼如下:

    private static class ServletConfigPropertyValues extends MutablePropertyValues {
        public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException {
            // 獲得缺失的屬性的集合
            Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? new HashSet<>(requiredProperties) : null);
    
            // <1> 遍歷 ServletConfig 的初始化引數集合,添加到 ServletConfigPropertyValues 中,并從 missingProps 移除
            Enumeration<String> paramNames = config.getInitParameterNames();
            while (paramNames.hasMoreElements()) {
                String property = paramNames.nextElement();
                Object value = https://www.cnblogs.com/lifullmoon/p/config.getInitParameter(property);
                // 添加到 ServletConfigPropertyValues 中
                addPropertyValue(new PropertyValue(property, value));
                // 從 missingProps 中移除
                if (missingProps != null) {
                    missingProps.remove(property);
                }
            }
            // Fail if we are still missing properties.
            if (!CollectionUtils.isEmpty(missingProps)) {
                throw new ServletException("...");
            }
        }
    }
    

    在它的構造方法中可以看到,將<init-param />標簽定義的一些配置項決議成 PropertyValue 物件,例如在前面概述web.xml中的配置,如下:

    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    
  2. 如果存在<init-param />初始化引數

    1. 將當前的這個 Servlet 物件,轉化成一個 BeanWrapper 物件,從而能夠以 Spring 的方式來將 pvs 注入到該 BeanWrapper 物件中,簡單來說,BeanWrapper 是 Spring 提供的一個用來操作 Java Bean 屬性的工具,使用它可以直接修改一個物件的屬性
    2. 注冊自定義屬性編輯器,一旦碰到 Resource 型別的屬性,將會使用 ResourceEditor 進行決議
    3. 呼叫initBeanWrapper(BeanWrapper bw)方法,可初始化當前這個 Servlet 物件,空實作,留給子類覆寫,目前好像還沒有子類實作
    4. 遍歷 pvs 中的屬性值,注入到該 BeanWrapper 物件中,也就是設定到當前 Servlet 物件中,例如 FrameworkServlet 中的 contextConfigLocation 屬性則會設定為上面的 classpath:spring-mvc.xml 值了
  3. 【關鍵】呼叫initServletBean()方法,空實作,交由子類去實作,完成自定義初始化邏輯,查看 FrameworkServlet#initServletBean() 方法

FrameworkServlet

org.springframework.web.servlet.FrameworkServlet 抽象類,實作 ApplicationContextAware 介面,繼承 HttpServletBean 抽象類,負責初始化 Spring Servlet WebApplicationContext 容器

構造方法
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    // ... 省略部分屬性
    
    /** Default context class for FrameworkServlet. */
	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

	/** WebApplicationContext implementation class to create. */
	private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

	/** Explicit context config location. 組態檔的地址 */
	@Nullable
	private String contextConfigLocation;

	/** Should we publish the context as a ServletContext attribute?. */
	private boolean publishContext = true;

	/** Should we publish a ServletRequestHandledEvent at the end of each request?. */
	private boolean publishEvents = true;

	/** WebApplicationContext for this servlet. */
	@Nullable
	private WebApplicationContext webApplicationContext;

	/** 標記是否是通過 {@link #setApplicationContext} 注入的 WebApplicationContext */
	private boolean webApplicationContextInjected = false;

	/** 標記已經是否接收到 ContextRefreshedEvent 事件,即 {@link #onApplicationEvent(ContextRefreshedEvent)} */
	private volatile boolean refreshEventReceived = false;

	/** Monitor for synchronized onRefresh execution. */
	private final Object onRefreshMonitor = new Object();

	public FrameworkServlet() {
	}

	public FrameworkServlet(WebApplicationContext webApplicationContext) {
		this.webApplicationContext = webApplicationContext;
	}
    
    @Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
			this.webApplicationContext = (WebApplicationContext) applicationContext;
			this.webApplicationContextInjected = true;
		}
	}
}
  • contextClass 屬性:創建的 WebApplicationContext 型別,默認為 XmlWebApplicationContext.class,在 Root WebApplicationContext 容器的創建程序中也是它

  • contextConfigLocation 屬性:組態檔的地址,例如:classpath:spring-mvc.xml

  • webApplicationContext 屬性:WebApplicationContext 物件,即本文的關鍵,Servlet WebApplicationContext 容器,有四種創建方式

    1. 通過上面的構造方法
    2. 實作了 ApplicationContextAware 介面,通過 Spring 注入,也就是 setApplicationContext(ApplicationContext applicationContext) 方法
    3. 通過 findWebApplicationContext() 方法,下文見
    4. 通過 createWebApplicationContext(WebApplicationContext parent) 方法,下文見
initServletBean

initServletBean() 方法,重寫父類的方法,在 HttpServletBean 的 init() 方法的最后一步會呼叫,進一步初始化當前 Servlet 物件,當前主要是初始化Servlet WebApplicationContext 容器,代碼如下:

@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {
        // <1> 初始化 WebApplicationContext 物件
        this.webApplicationContext = initWebApplicationContext();
        // <2> 空實作,留給子類覆寫,目前沒有子類實作
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (logger.isDebugEnabled()) {
        String value = https://www.cnblogs.com/lifullmoon/p/this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :
                "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                "': request parameters and headers will be " + value);
    }

    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}
  1. 呼叫 initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 物件
  2. 呼叫 initFrameworkServlet() 方法,可對當前 Servlet 物件進行自定義操作,空實作,留給子類覆寫,目前好像還沒有子類實作
initWebApplicationContext

initWebApplicationContext() 方法【核心】,初始化 Servlet WebApplicationContext 物件,方法如下:

protected WebApplicationContext initWebApplicationContext() {
    // <1> 獲得根 WebApplicationContext 物件
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    // <2> 獲得 WebApplicationContext wac 物件
    WebApplicationContext wac = null;

    // 第一種情況,如果構造方法已經傳入 webApplicationContext 屬性,則直接使用
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        // 如果是 ConfigurableWebApplicationContext 型別,并且未激活,則進行初始化
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) { // 未激活
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                // 配置和初始化 wac
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // 第二種情況,從 ServletContext 獲取對應的 WebApplicationContext 物件
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    // 第三種,創建一個 WebApplicationContext 物件
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    // <3> 如果未觸發重繪事件,則主動觸發重繪事件
    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    // <4> 將 context 設定到 ServletContext 中
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}
  1. 呼叫 WebApplicationContextUtils#getWebApplicationContext((ServletContext sc) 方法,從 ServletContext 中獲得 Root WebApplicationContext 物件,可以回到ContextLoader#initWebApplicationContext方法中的第 5 步,你會覺得很熟悉

  2. 獲得 WebApplicationContext wac 物件,有三種情況

    1. 如果構造方法已經傳入 webApplicationContext 屬性,則直接參考給 wac,也就是上面構造方法中提到的第 1、2 種創建方式

      如果 wac 是 ConfigurableWebApplicationContext 型別,并且未重繪(未激活),則呼叫 configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,進行配置和重繪,下文見

      如果父容器為空,則設定為上面第 1 步獲取到的 Root WebApplicationContext 物件

    2. 呼叫 findWebApplicationContext()方法,從 ServletContext 獲取對應的 WebApplicationContext 物件,也就是上面構造方法中提到的第 3 種創建方式

      @Nullable
      protected WebApplicationContext findWebApplicationContext() {
          String attrName = getContextAttribute();
          // 需要配置了 contextAttribute 屬性下,才會去查找,一般我們不會去配置
          if (attrName == null) {
              return null;
          }
          // 從 ServletContext 中,獲得屬性名對應的 WebApplicationContext 物件
          WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
          // 如果不存在,則拋出 IllegalStateException 例外
          if (wac == null) {
              throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
          }
          return wac;
      }
      

      一般不會這樣做

    3. 呼叫createWebApplicationContext(@Nullable WebApplicationContext parent)方法,創建一個 WebApplicationContext 物件

      protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
          // <a> 獲得 context 的類,XmlWebApplicationContext.class
          Class<?> contextClass = getContextClass();
          // 如果非 ConfigurableWebApplicationContext 型別,拋出 ApplicationContextException 例外
          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");
          }
          // <b> 創建 context 類的物件
          ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
      
          // <c> 設定 environment、parent、configLocation 屬性
          wac.setEnvironment(getEnvironment());
          wac.setParent(parent);
          String configLocation = getContextConfigLocation();
          if (configLocation != null) {
              wac.setConfigLocation(configLocation);
          }
          // <d> 配置和初始化 wac
          configureAndRefreshWebApplicationContext(wac);
      
          return wac;
      }
      

      <a> 獲得 context 的 Class 物件,默認為 XmlWebApplicationContext.class,如果非 ConfigurableWebApplicationContext 型別,則拋出例外

      <b> 創建 context 的實體物件

      <c> 設定 environmentparentconfigLocation 屬性,其中,configLocation 是個重要屬性

      <d> 呼叫 configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,進行配置和重繪,下文見

  3. 如果未觸發重繪事件,則呼叫 onRefresh(ApplicationContext context) 方法,主動觸發重繪事件,該方法為空實作,交由子類 DispatcherServlet 去實作

  4. context 設定到 ServletContext 中

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 物件,方法如下:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // <1> 如果 wac 使用了默認編號,則重新設定 id 屬性
    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
        // 情況一,使用 contextId 屬性
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        // 情況二,自動生成
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }

    // <2> 設定 wac 的 servletContext、servletConfig、namespace 屬性
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // <3> 添加監聽器 SourceFilteringListener 到 wac 中
    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
    // <4>
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    // <5> 執行處理完 WebApplicationContext 后的邏輯,目前是個空方法,暫無任何實作
    postProcessWebApplicationContext(wac);
    // <6> 執行自定義初始化 context
    applyInitializers(wac);
    // <7> 重繪 wac ,從而初始化 wac
    wac.refresh();
}

實際上,處理邏輯和ContextLoader#configureAndRefreshWebApplicationContext方法差不多

  1. 如果 wac 使用了默認編號,則重新設定 id 屬性
  2. 設定 wac 的 servletContext、servletConfig、namespace 屬性
  3. 添加監聽器 SourceFilteringListener 到 wac
  4. 配置 Environment 物件,暫時忽略
  5. 執行處理完 WebApplicationContext 后的邏輯,空方法,暫無任何實作
  6. wac 進行定制化處理,暫時忽略
  7. 【關鍵】觸發 wac 的重繪事件,執行初始化,此處,就會進行一些的 Spring 容器的初始化作業,涉及到 Spring IOC 相關內容
onRefresh

onRefresh(ApplicationContext context) 方法,當 Servlet WebApplicationContext 重繪完成后,觸發 Spring MVC 組件的初始化,方法如下:

/**
 * Template method which can be overridden to add servlet-specific refresh work.
 * Called after successful context refresh.
 * <p>This implementation is empty.
 * @param context the current WebApplicationContext
 * @see #refresh()
 */
protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}

這是一個空方法,具體的實作,在子類 DispatcherServlet 中,代碼如下:

// DispatcherServlet.java
@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    // 初始化 MultipartResolver
    initMultipartResolver(context);
    // 初始化 LocaleResolver
    initLocaleResolver(context);
    // 初始化 ThemeResolver
    initThemeResolver(context);
    // 初始化 HandlerMappings
    initHandlerMappings(context);
    // 初始化 HandlerAdapters
    initHandlerAdapters(context);
    // 初始化 HandlerExceptionResolvers 
    initHandlerExceptionResolvers(context);
    // 初始化 RequestToViewNameTranslator
    initRequestToViewNameTranslator(context);
    // 初始化 ViewResolvers
    initViewResolvers(context);
    // 初始化 FlashMapManager
    initFlashMapManager(context);
}

初始化九個組件,這里只是先提一下,在后續的檔案中會進行分析

onRefresh方法的觸發有兩種方式:

  • 方式一:如果refreshEventReceivedfalse,也就是未接收到重繪事件(防止重復初始化相關組件),則在 initWebApplicationContext 方法中直接呼叫
  • 方式二:通過在 configureAndRefreshWebApplicationContext 方法中,觸發 wac 的重繪事件

為什么上面的方式二可以觸發這個方法的呼叫呢?

先看到 configureAndRefreshWebApplicationContext 方法的第 3 步,添加了一個 SourceFilteringListener 監聽器,如下:

// <3> 添加監聽器 SourceFilteringListener 到 wac 中
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

監聽到相關事件后,會委派給 ContextRefreshListener 進行處理,它是 FrameworkServlet 的私有內部類,來看看它又是怎么處理的,代碼如下:

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

直接將該事件委派給了 FrameworkServlet 的 onApplicationEvent 方法,如下:

public void onApplicationEvent(ContextRefreshedEvent event) {
    // 標記 refreshEventReceived 為 true
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        // 處理事件中的 ApplicationContext 物件,空實作,子類 DispatcherServlet 會實作
        onRefresh(event.getApplicationContext());
    }
}

先設定 refreshEventReceivedtrue,表示已接收到重繪時間,然后再呼叫 onRefresh 方法,回到上面的方式一方式二,是不是連通起來了,所以說該方法是一定會被觸發的

總結

本分對 Spring MVC 兩種容器的創建程序進行分析,分別為 Root WebApplicationContextServlet WebApplicationContext 容器,它們是父子關系,創建程序并不是很復雜,前置是在 Tomcat 或者 Jetty 等 Servlet 容器啟動后,由 ContextLoaderListener 監聽到相應事件而創建的,后者是在 DispatcherServlet 初始化的程序中創建的,因為它是一個 HttpServlet 物件,會呼叫其 init 方法,完成初始化相關作業

DispatcherServlet 是 Spring MVC 的核心類,相當于一個調度者,請求的處理程序都是通過它調度各個組件來完成的,在后續的文章中進行分析

參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/234594.html

標籤:Java

上一篇:[GO]go redis實作滑動視窗限流-redis版

下一篇:Spring Boot 的2020最后一擊:2.4.1、2.3.7、2.2.12 發布

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more