Spring5源码(52)-Web应用上下文环境创建

简介: Spring5源码(52)-Web应用上下文环境创建


1. Web应用上下文环境创建简析

通过上一节的分析,找到了SpringMVC源码分析的入口,接下来看Web应用上下文环境创建过程。打开ContextLoader类的initWebApplicationContext方法:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    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!");
    }
    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 {
        // 将上下文存储在本地实例变量中,以确保它在ServletContext关闭时可用。
        // Store context in local instance variable, to guarantee that it is available on ServletContext shutdown.
        if (this.context == null) {
            // 1.创建web应用上线文环境
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            // 如果当前上下文环境未激活,那么其只能提供例如设置父上下文、设置上下文id等功能
            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 ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                // 2.配置并刷新当前上下文环境
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将当前上下文环境存储到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE变量中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
        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");
        }
        return this.context;
    }
    catch (RuntimeException | Error ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}

该方法一共涉及两个比较重要的点:

  • 创建web应用上线文环境
  • 配置并刷新当前上下文环境
2. 创建web应用上线文环境

/**
 * 为当前类加载器实例化根WebApplicationContext,可以是默认上线文加载类或者自定义上线文加载类
 */
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 1.确定实例化WebApplicationContext所需的类
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 2.实例化得到的WebApplicationContext类
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

逻辑很简单,得到一个类,将其实例化。我们经常说的web应用上下文环境,是不是比我们想象的还要简单。。。

那么要得到或者明确哪个类呢? 继续看代码:

/**
 * 返回WebApplicationContext(web应用上线文环境)实现类
 * 如果没有自定义默认返回XmlWebApplicationContext类
 *
 * 两种方式:
 * 1。非自定义:通过ContextLoader类的静态代码块加载ContextLoader.properties配置文件并解析,该配置文件中的默认类即XmlWebApplicationContext
 * 2。自定义: 通过在web.xml文件中,配置context-param节点,并配置param-name为contextClass的自己点,如
 *      <context-param>
 *          <param-name>contextClass</param-name>
 *          <param-value>org.springframework.web.context.support.MyWebApplicationContext</param-value>
 *      </context-param>
 *
 * Return the WebApplicationContext implementation class to use, either the
 * default XmlWebApplicationContext or a custom context class if specified.
 * @param servletContext current servlet context
 * @return the WebApplicationContext implementation class to use
 * @see #CONTEXT_CLASS_PARAM
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    // 1.自定义
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    // 2.默认
    else {
        // 根据静态代码块的加载这里 contextClassName = XmlWebApplicationContext
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

自定义方式注释里已经写的很清晰了,我们来看默认方式,这里涉及到了一个静态变量defaultStrategies,并在下面的静态代码块中对其进行了初始化操作:

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
/**
 * 静态代码加载默认策略,即默认的web应用上下文
 * DEFAULT_STRATEGIES_PATH --> ContextLoader.properties
 *
 * org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
 */
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进行了解析,那么ContextLoader.properties中存储的内容是什么呢?

# 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

很简单,通过上面的操作,我们就可以确定contextClassName是XmlWebApplicationContext,跟我们之前分析的ApplicationContext差不多,只是在其基础上又提供了对web的支持。接下来通过BeanUtils.instantiateClass(contextClass)将其实例化即可。

3.配置并刷新当前上下文环境

/**
 * 配置并刷新当前web应用上下文
 */
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    /**
     * 1.配置应用程序上下文id
     * 如果当前应用程序上下文id仍然设置为其原始默认值,则尝试为其设置自定义上下文id,如果有的话。
     * 在web.xml中配置
     * <context-param>
     *      <param-name>contextId</param-name>
     *      <param-value>jack-2019-01-02</param-value>
     *  </context-param>
     */
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        // 无自定义id则为其生成默认id
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
    wac.setServletContext(sc);
    /**
     * 2.设置配置文件路径,如
     * <context-param>
     *      <param-name>contextConfigLocation</param-name>
     *      <param-value>classpath:spring-context.xml</param-value>
     *  </context-param>
     */
    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
    // 3.创建ConfigurableEnvironment并配置初始化参数
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
    // 4.自定义配置上下文环境
    customizeContext(sc, wac);
    // 5.刷新上下文环境
    wac.refresh();
}

前三个步骤比较简单,在前面的博客中多少有些介绍,我们来看自定义配置上下文环境和刷新上下文环境

3.1 自定义配置上下文环境

protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
    /**
     * 加载并实例化web.xml配置文件中的 globalInitializerClasses 和 contextInitializerClasses 配置
     *
     * globalInitializerClasses 代表所有的web application都会应用
     * contextInitializerClasses 代表只有当前的web application会使用
     * 例如,在web.xml配置文件中:
     *  <context-param>
     *      <param-name>contextInitializerClasses</param-name>
     *      <param-value>com.lyc.cn.init.MyContextInitializerClasses</param-value>
     *  </context-param>
     *
     *  容器将会调用自定义的initialize方法,其实就在这段代码的下方。。。
     */
    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
            determineContextInitializerClasses(sc);
    for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
        Class<?> initializerContextClass =
                GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
        if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
            throw new ApplicationContextException(String.format(
                    "Could not apply context initializer [%s] since its generic parameter [%s] " +
                    "is not assignable from the type of application context used by this " +
                    "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
                    wac.getClass().getName()));
        }
        this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    }
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
        initializer.initialize(wac);
    }
}

该实现很简单,我们只要在web.xml中自定义contextInitializerClasses和globalInitializerClasses并提供实现类即可:

如:

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>com.lyc.cn.init.MyContextInitializerClasses</param-value>
</context-param>

public class MyContextInitializerClasses implements ApplicationContextInitializer<XmlWebApplicationContext> {
    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    @Override
    public void initialize(XmlWebApplicationContext applicationContext) {
        System.out.println("MyContextInitializerClasses initialize ...");
        System.out.println("MyContextInitializerClasses " + applicationContext.toString());
    }
}
3.2 刷新上下文环境

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1、准备刷新上下文环境
        prepareRefresh();
        // 2、读取xml并初始化BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 3、填充BeanFactory功能
        prepareBeanFactory(beanFactory);
        try {
            // 4、子类覆盖方法额外处理(空方法)
            postProcessBeanFactory(beanFactory);
            // 5、调用BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            // 6、注册BeanPostProcessors
            registerBeanPostProcessors(beanFactory);
            // 7、初始化Message资源
            initMessageSource();
            // 8、初始事件广播器
            initApplicationEventMulticaster();
            // 9、留给子类初始化其他Bean(空的模板方法)
            onRefresh();
            // 10、注册事件监听器
            registerListeners();
            // 11、初始化其他的单例Bean(非延迟加载的)
            finishBeanFactoryInitialization(beanFactory);
            // 12、完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知
            finishRefresh();
        }
        catch (BeansException ex) {
            // 13、销毁已经创建的Bean
            destroyBeans();
            // 14、重置容器激活标签
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}

这段代码在前面的博客中已经详细的分析过了,感兴趣的同学查看前面的博客吧! 到这里Web应用上下文环境创建过程就结束了。



目录
相关文章
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
23天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
38 3
|
27天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
2天前
|
前端开发 JavaScript UED
在数字化时代,Web 应用性能优化尤为重要。本文探讨了CSS与HTML在提升Web性能中的关键作用及未来趋势
在数字化时代,Web 应用性能优化尤为重要。本文探讨了CSS与HTML在提升Web性能中的关键作用及未来趋势,包括样式表优化、DOM操作减少、图像优化等技术,并分析了电商网站的具体案例,强调了技术演进对Web性能的深远影响。
10 5
|
11天前
|
缓存 安全 网络安全
HTTP/2与HTTPS在Web加速中的应用
HTTP/2与HTTPS在Web加速中的应用
|
14天前
|
SQL 安全 前端开发
PHP与现代Web开发:构建高效的网络应用
【10月更文挑战第37天】在数字化时代,PHP作为一门强大的服务器端脚本语言,持续影响着Web开发的面貌。本文将深入探讨PHP在现代Web开发中的角色,包括其核心优势、面临的挑战以及如何利用PHP构建高效、安全的网络应用。通过具体代码示例和最佳实践的分享,旨在为开发者提供实用指南,帮助他们在不断变化的技术环境中保持竞争力。
|
17天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
41 9
|
24天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
24 3
|
24天前
|
前端开发 JavaScript API
前端框架新探索:Svelte在构建高性能Web应用中的优势
【10月更文挑战第26天】近年来,前端技术飞速发展,Svelte凭借独特的编译时优化和简洁的API设计,成为构建高性能Web应用的优选。本文介绍Svelte的特点和优势,包括编译而非虚拟DOM、组件化开发、状态管理及响应式更新机制,并通过示例代码展示其使用方法。
36 2
|
24天前
|
测试技术 持续交付 PHP
PHP在Web开发中的应用与最佳实践###
【10月更文挑战第25天】 本文将深入探讨PHP在现代Web开发中的应用及其优势,并分享一些最佳实践来帮助开发者更有效地使用PHP。无论是初学者还是有经验的开发者,都能从中受益。 ###
52 1
下一篇
无影云桌面