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应用上下文环境创建过程就结束了。



目录
相关文章
|
7月前
|
前端开发 算法 API
构建高性能图像处理Web应用:Next.js与TailwindCSS实践
本文分享了构建在线图像黑白转换工具的技术实践,涵盖技术栈选择、架构设计与性能优化。项目采用Next.js提供优秀的SSR性能和SEO支持,TailwindCSS加速UI开发,WebAssembly实现高性能图像处理算法。通过渐进式处理、WebWorker隔离及内存管理等策略,解决大图像处理性能瓶颈,并确保跨浏览器兼容性和移动设备优化。实际应用案例展示了其即时处理、高质量输出和客户端隐私保护等特点。未来计划引入WebGPU加速、AI增强等功能,进一步提升用户体验。此技术栈为Web图像处理应用提供了高效可行的解决方案。
|
3月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
184 0
探索Spring Boot的@Conditional注解的上下文配置
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
504 70
|
6月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。该方案设计目标是实现通用性,适用于各种网络架构和业务场景。
180 1
|
7月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。
240 7
|
9月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
228 0
|
Web App开发 JavaScript 前端开发
《单页Web应用:JavaScript从前端到后端》——1.3 精心编写的单页应用的用户效益
所有这些好处意味着,你可能会想把下个应用做成单页应用。每次点击后都会重新渲染整张页面的笨拙网站,容易日益疏远富有经验的用户。精心编写的单页应用具有互动和快速响应的界面,还伴有访问网络的功能,这将帮助我们把客户留在属于他们的地方:使用我们的产品。
1251 0

热门文章

最新文章