SpringBoot启动流程分析之准备应用上下文refreshContext()

news2024/11/25 2:58:27

文章目录

        • 源码入口
        • 1、准备刷新
          • 1.1、子类prepareRefresh()方法
          • 1.2 父类prepareRefresh()方法
        • 2、通知子类刷新内部bean工厂
        • 3、准备bean工厂
        • 4、允许上下文子类对bean工厂进行后置处理

源码入口

org.springframework.boot.SpringApplication#run(java.lang.String…)

public ConfigurableApplicationContext run(String... args) {
            .... 
	try {
            //本篇内容从本行开始记录  
            refreshContext(context);
           //本篇内容记录到这,后续更新
            ....
        }
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
}

流程分析

private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

最终调用父类AbstractApplicationContext的refresh()方法。

protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

@Override
public final void refresh() throws BeansException, IllegalStateException {		
    try {
	     super.refresh();
    }
    catch (RuntimeException ex) {
	    stopAndReleaseWebServer();
	    throw ex;
    }
}

可以看到refresh中的步骤都是单个单个的方法

如果看过Spring源码的同学现在应该很熟悉了…
org.springframework.context.support.AbstractApplicationContext#refresh

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
	    // 准备刷新
	    prepareRefresh();
 
	    // 通知子类刷新内部bean工厂
	    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
	    // 准备bean工厂以便在此上下文中使用
	    prepareBeanFactory(beanFactory);
 
	    try {
	    	// 允许上下文子类中对bean工厂进行后处理
	    	postProcessBeanFactory(beanFactory);
 
	    	// 在bean创建之前调用BeanFactoryPostProcessors后置处理方法
	    	invokeBeanFactoryPostProcessors(beanFactory);
 
	    	// 注册BeanPostProcessor
	    	registerBeanPostProcessors(beanFactory);
 
	    	// 注册DelegatingMessageSource
	    	initMessageSource();
 
	    	// 注册multicaster
	    	initApplicationEventMulticaster();
 
	    	// 创建内置的Servlet容器
	    	onRefresh();
 
	    	// 注册Listener
	    	registerListeners();
 
	    	// 完成BeanFactory初始化,初始化剩余单例bean
	    	finishBeanFactoryInitialization(beanFactory);
 
	    	// 发布对应事件
	    	finishRefresh();
	    }
 
	    catch (BeansException ex) {
	    	if (logger.isWarnEnabled()) {
	    		logger.warn("Exception encountered during context initialization - " +
	    					"cancelling refresh attempt: " + ex);
	    	}
 
	    	// Destroy already created singletons to avoid dangling resources.
	    	destroyBeans();
 
	    	// Reset 'active' flag.
	    	cancelRefresh(ex);
 
	    	// Propagate exception to caller.
	    	throw ex;
	    }
 
	    finally {
	    	// Reset common introspection caches in Spring's core, since we
	    	// might not ever need metadata for singleton beans anymore...
	    	resetCommonCaches();
	    }
    }
}

开始之前先把AnnotationConfigServletWebServerApplicationContext类图放这:
在这里插入图片描述

1、准备刷新

先调用子类重写的方法,再调用父类方法。还记得前面讲过在创建AnnotationConfigServletWebServerApplicationContext的时候构造方法中实例化了一个ClassPathBeanDefinitionScanner。

@Override
protected void prepareRefresh() {
    this.scanner.clearCache();
    super.prepareRefresh();
}
1.1、子类prepareRefresh()方法

在其父类ClassPathScanningCandidateComponentProvider中有一个MetadataReaderFactory(接口)工厂对象,判断该对象是否是CachingMetadataReaderFactory这个特定类或者是它的子类的一个实例,返回Boolean值,是就是true,则清除缓存。该类中有个Map,用来缓存每个Spring资源句柄(即每个“.class”文件)的MetadataReader实例。

@Nullable
private Map<Resource, MetadataReader> metadataReaderCache;

/**
 * Clear the local MetadataReader cache, if any, removing all cached class metadata.
 */
public void clearCache() {
    if (this.metadataReaderCache instanceof LocalResourceCache) {
        synchronized (this.metadataReaderCache) {
            this.metadataReaderCache.clear();
        }
    }
    else if (this.metadataReaderCache != null) {
        // Shared resource cache -> reset to local cache.
        setCacheLimit(DEFAULT_CACHE_LIMIT);
    }
}

缓存Map如果是LocalResourceCache(可以看到该类继承了LinkedHashMap),执行的就是LinkedHashMap的clear()方法了

else如果缓存不为空,就是重新new一个LocalResourceCache

 * Specify the maximum number of entries for the MetadataReader cache.
 * <p>Default is 256 for a local cache, whereas a shared cache is
 * typically unbounded. This method enforces a local resource cache,
 * even if the {@link ResourceLoader} supports a shared resource cache.
 */
public void setCacheLimit(int cacheLimit) {
    if (cacheLimit <= 0) {
        this.metadataReaderCache = null;
    }
    else if (this.metadataReaderCache instanceof LocalResourceCache) {
        ((LocalResourceCache) this.metadataReaderCache).setCacheLimit(cacheLimit);
    }
    else {
        this.metadataReaderCache = new LocalResourceCache(cacheLimit);
    }
}

/**
 * Clear the local MetadataReader cache, if any, removing all cached class metadata.
 */
public void clearCache() {
    if (this.metadataReaderCache instanceof LocalResourceCache) {
        synchronized (this.metadataReaderCache) {
            this.metadataReaderCache.clear();
        }
    }
    else if (this.metadataReaderCache != null) {
        // Shared resource cache -> reset to local cache.
        setCacheLimit(DEFAULT_CACHE_LIMIT);
    }
}


@SuppressWarnings("serial")
private static class LocalResourceCache extends LinkedHashMap<Resource, MetadataReader> {

    private volatile int cacheLimit;

    public LocalResourceCache(int cacheLimit) {
        super(cacheLimit, 0.75f, true);
        this.cacheLimit = cacheLimit;
    }

    public void setCacheLimit(int cacheLimit) {
        this.cacheLimit = cacheLimit;
    }

    public int getCacheLimit() {
        return this.cacheLimit;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Resource, MetadataReader> eldest) {
        return size() > this.cacheLimit;
    }
}
1.2 父类prepareRefresh()方法
protected void prepareRefresh() {
        //系统启动时间
	this.startupDate = System.currentTimeMillis();
        //是否关闭标识,false
	this.closed.set(false);
        //是否活跃标识,true
	this.active.set(true);
 
	if (logger.isDebugEnabled()) {
		if (logger.isTraceEnabled()) {
			logger.trace("Refreshing " + this);
		}
		else {
			logger.debug("Refreshing " + getDisplayName());
		}
	}
 
	// 调用子类GenericWebApplicationContext重写后的方法替换servlet相关属性源
	initPropertySources();
 
	
	// 这里是验证由ConfigurablePropertyResolver#setRequiredProperties()方法指定的属性,解析为非空值,如果没有设置的话这个方法就不会执行什么操作。
	getEnvironment().validateRequiredProperties();
 
	// Allow for the collection of early ApplicationEvents,
	// to be published once the multicaster is available...
	this.earlyApplicationEvents = new LinkedHashSet<>();
}

在这里插入图片描述在这里插入图片描述

看下最后这个方法,initServlet属性源,方法注释是这么说的将基于Servlet的StubPropertySource替换为使用给定servletContext和servletConfig对象填充的实例。此方法可以被调用任意次数,但将用相应的实际属性源替换为StubPropertySource一次且仅一次。

看下if判断里的条件,servletContext不为空,source中存在指定name的的属性源,且该属性源要是StubPropertySource的类型或者是其子类。也就是当第一次调用以后,该属性源就被替换成了ServletContextPropertySource和ServletConfigPropertySource,所以之后的调用最后一个判断条件就不会成立了。

public static void initServletPropertySources(MutablePropertySources sources,
			@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
 
	Assert.notNull(sources, "'propertySources' must not be null");
        //servletContextInitParams
	String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
	if (servletContext != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
		sources.replace(name, new ServletContextPropertySource(name, servletContext));
	}
        //servletConfigInitParams
	name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
	if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
		sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
	}
}

看validateRequiredProperties()方法前,先看下Environment的类图。这里是验证由ConfigurablePropertyResolver#setRequiredProperties()方法指定的属性,解析为非空值,如果没有设置的话这个方法就不会执行什么操作,所以这里就略过了。

方法最后new了一个LinkedHashSet,收集早期事件,如果multicaster 有用就会广播事件。prepareRefresh()就执行完了。

2、通知子类刷新内部bean工厂

可以看到refreshBeanFactory()方法上的注释说的,什么都不做:我们拥有一个内部beanfactory,并依靠调用方通过我们的公共方法(或beanfactory)注册bean。

前面介绍GenericApplicationContext说了与每次刷新创建新的内部beanfactory实例的其他applicationContext实现不同,此上下文的内部beanfactory从一开始就可用,以便能够在其上注册bean定义。只能调用一次refresh()。

所以在该方法内只设置了SerializationId,该id是在准备应用上下文时调用ContextIdApplicationContextInitializer时设置的id,在setSerializationId方法中,使用id做key,new了一个弱引用对象为value,添加到了serializableFactories中,DefaultListableBeanFactory为被弱引用对象;如果需要,可以通过id得到引用对象,在通过get()方法得到DefaultListableBeanFactory对象。

@Override
protected final void refreshBeanFactory() throws IllegalStateException {
    if (!this.refreshed.compareAndSet(false, true)) {
        throw new IllegalStateException(
                "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
    }
    this.beanFactory.setSerializationId(getId());
}

/** Map from serialized id to factory instance. */
private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories = new ConcurrentHashMap<>(8);
public void setSerializationId(@Nullable String serializationId) {
    if (serializationId != null) {
        serializableFactories.put(serializationId, new WeakReference<>(this));
    }
    else if (this.serializationId != null) {
        serializableFactories.remove(this.serializationId);
    }
    this.serializationId = serializationId;
}
3、准备bean工厂
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	//通知内部bean工厂使用上下文的类加载器
	beanFactory.setBeanClassLoader(getClassLoader());
        //为bean定义值中的表达式指定解析策略,即解析el表达式,默认"#{"开头,"}"结尾
	beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        //在这里new了一个资源编辑注册器ResourceEditorRegistrar,该类实现了PropertyEditorRegistrar接口
        //作用是使用以下资源编辑器去填充给的的注册表:ResourceEditor、InputStreamEditor、InputSourceEditor、FileEditor、Urleditor、UriEditor、ClassEditor、ClassArrayEditor。
        //如果给的注册表是PropertyEditorRegistrySupport类型,编辑器交由该类管理
	beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
 
	// 向BeanPostProcessor的List中添加一个ApplicationContextAwareProcessor,参数是上下文
        //该类作用是将上下文传递给实现environmentaware、embeddedValueResolveraware、resourceLoaderware、applicationEventPublisheraware、messageSourceAware或applicationContextaware接口的bean。
	beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        //自动装配忽略给定的类型,添加到忽略的集合中
	beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
	beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
	beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
	beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
	beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
	beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
 
	// BeanFactory interface not registered as resolvable type in a plain factory.
	// MessageSource registered (and found for autowiring) as a bean.
        //根据第二个参数注册一个特殊的依赖类型
	beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
	beanFactory.registerResolvableDependency(ResourceLoader.class, this);
	beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
	beanFactory.registerResolvableDependency(ApplicationContext.class, this);
 
	// 该BeanPostProcessor检测那些实现了ApplicationListener接口的bean,在它们创建时初始化之后,将它们添加到应用上下文的事件多播器上
        //并在这些ApplicationListener bean销毁之前,将它们从应用上下文的事件多播器上移除。
	beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
 
	// Detect a LoadTimeWeaver and prepare for weaving, if found.
	if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
		beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
		// Set a temporary ClassLoader for type matching.
		beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
	}
 
	// Register default environment beans.
        //注册默认environment bean,如果beanfactory不存在environment 
	if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
		beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
	}
        //systemProperties 同上
	if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
		beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
	}
        //systemEnvironment 同上
	if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
		beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
	}
}
4、允许上下文子类对bean工厂进行后置处理

basePackages和annotatedClasses都为空。跳过
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    super.postProcessBeanFactory(beanFactory);
    if (this.basePackages != null && this.basePackages.length > 0) {
        this.scanner.scan(this.basePackages);
    }
    if (!this.annotatedClasses.isEmpty()) {
        this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1591497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux中磁盘的分区,格式化,挂载和文件系统的修复

一.分区工具 1.分区工具介绍 fdisk 2t及以下分区 推荐 (分完区不保存不生效&#xff0c;有反悔的可能) gdisk 全支持 推荐 parted 全支持 不推荐 ( 即时生效&#xff0c;分完立即生效) 2.fdisk 分区,查看磁盘 格式:fdisk -l [磁盘设备] fdisk -l 查看…

【研发日记】CANoe自动化测试的配置方式(三)——SystemVariables数组方式

文章目录 前言 一、例程功能 二、仿真ECU 三、SystemVariables数组&#xff1a; 四、测试模块 五、测试运行效果 六、分析和应用 总结 前言 近期在做的一个自动化测试项目&#xff0c;尝试了一种以前没用过的测试配置方式&#xff0c;感觉效果还不错。然后又回顾了一下以…

Trl: llama2-7b-hf使用QLora 4bit量化后ds zero3加上flash atten v2单机多卡训练(笔记)

目录 一、环境 1.1、环境安装 1.2、安装flash atten 二、代码 2.1、bash脚本 2.2、utils.py 注释与优化 2.3、train.py 注释与优化 2.4、模型/参数相关 2.4.1、量化后的模型 a) 量化后模型结构 b) 量化后模型layers 2.4.2、参数 a) training args b) peft args c) model arg…

安装一个在线VS Code 随时随地在线编辑代码 code server搭建教程

code-server是一款在线的 VS Code&#xff0c;只需将其部署到服务端&#xff0c;就可以在浏览器上使用 VS Code&#xff0c;本文将介绍 code-server 安装和使用方法。 首先我们需要准备一台Linux服务器&#xff0c;这里我推荐伍六七云&#xff1a;https://www.vps567.com/ 香港…

卫星图像10个开源数据集资源汇总

文章目录 1、UC Merced Land-Use 2、Indian Pines 3、KSC 4、Washington DC 5、BigEarthNet 6、水体卫星图像的图像 7、城市航拍图像分割数据集 8、游泳池和汽车卫星图像检测 9、人工月球景观数据集 10、马萨诸塞州道路数据集 1、UC Merced Land-Use 数据集下载地址&am…

android11 如何修改状态栏的背景

修改status_bar.xml &#xff1a; <LinearLayout android:id"id/status_bar_contents"android:background"#1ABC9C"android:layout_width"match_parent"android:layout_height"match_parent"android:paddingStart"dimen/statu…

数字IC/FPGA——亚稳态及跨时钟域

什么是亚稳态亚稳态会造成什么平均故障间隔时间如何解决亚稳态同步时钟和异步时钟单bit电平信号如何跨时钟域单bit脉冲信号如何跨时钟域多bit信号如何跨时钟域 目录 一、亚稳态1.基本概念2.危害3.平均故障时间4.解决亚稳态的方法 二、跨时钟域1.同步电路和异步电路&#xff08;…

c语言例题,求数组中最大值,99乘法口诀表

例题1&#xff1a;求出数组中最大的值 根据题意&#xff0c;我们知道的是需要从一个数组中找到一个最大的元素并且输出。那首先我们先建立一个数组&#xff0c;然后将一些不有序的整型元素放到数组中&#xff0c;然后再建立一个变量来存放数组中的第一个元素&#xff0c;通过一…

使用avx2 指令集加速向量算法运算

使用cpu-z 查看cpu指令集 2 向量加&#xff0c;乘法&#xff0c;除法 我们使用向量加&#xff0c;为什么函数是0 到 8 的计算&#xff0c;因为avx2 寄存器为256位&#xff0c;同时设置启动增强指令集 #include <immintrin.h> // 引入包含AVX2指令集的头文件void vecto…

AI识别技术详解 --在windows环境中部署基于YOLO v8模型的目标检测

首先 YOLO是一个端到端的目标检测算法&#xff0c;一次前向传播计算&#xff0c;实现图像的多目标检测任务&#xff0c;我么可以在ultralytics官网上查看YOLO的各个版本&#xff08;v1-v8&#xff09;以及源码 使用YOLO v8提供的python接口&#xff0c;训练一个佩戴安全帽的目标…

vue 百度地图 使用 vue-baidu-map 进行当前位置定位和范围展示

vue 百度地图 使用 vue-baidu-map 进行当前位置定位和范围展示&#xff08;考勤打卡&#xff09; 一、创建百度地图账号&#xff0c;获取秘钥二、 引入插件1、安装vue-baidu-map2、在main.js中引入 三、 简单使用 最近写项目的时候&#xff0c;做到了考勤打卡的模块内容&#x…

Qt控件---容器类

文章目录 QGroupBox&#xff08;有标题的分组框&#xff09;QTabWidget&#xff08;带有标签页控件&#xff09; QGroupBox&#xff08;有标题的分组框&#xff09; 属性说明title分组框的标题alignment分组框内部内容的对齐方式flat是否为 扁平 模式checkable是否可选&#x…

僵尸进程和孤儿进程

目录 引言僵尸进程僵尸进程的状态僵尸进程周边知识 孤儿进程孤儿进程的状态 进程中的其他状态①.R---表示进程运行状态。②.S---表示进程的休眠状态。(进程什么都没做)③T 和 t 进程的运行、阻塞和挂起运行阻塞挂起状态&#xff1a; 引言 今天我们来将僵尸进程和孤儿进程以及其…

两数之和-第12届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第51讲。 两数之和&#xf…

深入解析API技术:原理、实现与应用

在现代软件开发中&#xff0c;API&#xff08;应用程序接口&#xff09;扮演着至关重要的角色。API 允许不同的软件应用程序和系统之间进行通信和数据交换&#xff0c;从而构建出更加高效、灵活和可扩展的软件解决方案。本文将深入解析API技术的原理、实现方法&#xff0c;并附…

FANUC机器人通过ROBOGUIDE实现与实际的机器人进行程序导入导出的具体方法示例

FANUC机器人通过ROBOGUIDE实现与实际的机器人进行程序导入导出的具体方法示例 如下图所示,在电脑的开始菜单中找到”Robot Neiborhood”,点击进入, 如下图所示,设置要连接的机器人名称和主机IP地址(要确保自己的电脑和机器人IP地址在同一网段内),点击Add添加, 添加在线…

TCP 三次握手与四次挥手面试题(计算机网络)

TCP 基本认识 TCP 头格式有哪些&#xff1f; 序列号&#xff1a;在建立连接时由计算机生成的随机数作为其初始值&#xff0c;通过 SYN 包传给接收端主机&#xff0c;每发送一次数据&#xff0c;就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。 确认应答号&a…

成都百洲文化传媒有限公司电商领域的新锐力量

在电商服务领域&#xff0c;成都百洲文化传媒有限公司凭借其专业的服务理念和创新的策略&#xff0c;正逐渐成为行业内的翘楚。这家公司不仅拥有资深的电商团队&#xff0c;还以其精准的市场定位和高效的服务模式&#xff0c;赢得了众多客户的信赖和好评。 一、专业团队&#…

基于 FPGA 的 DE1-SoC 功率估算器

Introduction 功耗是当今许多技术都要考虑的重要因素。例如&#xff0c;手机生产商总是谈论他们在电源管理方面的改进&#xff0c;以及如何延长电池的使用寿命。功能与功耗之间的平衡是许多人都在研究的有趣课题。然而&#xff0c;当我们做实验时&#xff0c;我们很少会考虑我…

centos7上docker搭建vulhub靶场

1 vulhub靶场概述 VulHub是一个在线靶场平台&#xff0c;提供了丰富的漏洞环境供安全爱好者学习和实践。 该平台主要面向网络安全初学者和进阶者&#xff0c;通过模拟真实的漏洞环境&#xff0c;帮助用户深入了解漏洞的成因、利用方式以及防范措施。 此外&#xff0c;VulHub还…