网站首页 > 技术文章 正文
文章来源:https://dwz.cn/uX7PtgTy
作者:cxuan
ClassPathXmlApplicationContext的注册方式
源码分析基于Spring4.3
从ClassPathXmlApplicationContext入口,最终都会调用到
上述注释的解释如是说:在容器的启动过程中,初始化过程中所有的bean都是单例存在的
自动刷新
ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");
就等同于手动刷新
ApplicationContext context = new ClassPathXmlApplicationContext(); context.register("xxx.xml"); context.refresh();
上述一共有三条链路,下面来一一分析
加载父子容器
首先是加载并初始化父容器的方法
1、第一个出场的是ClassPathXmlApplicationContext,它是一个独立的应用程序上下文,从类路径获取上下文定义文件,能够将普通路径解析为包含包路径的类路径资源名称。它可以支持Ant-Style(路径匹配原则),它是一站式应用程序的上下文,考虑使用GenericApplicationContext类结合XmlBeanDefinitionReader来设置更灵活的上下文配置。
Ant-Style 路径匹配原则,例如 "mypackages/application-context.xml" 可以用"mypackages/*-context.xml" 来替换。
??注意: 如果有多个上下文配置,那么之后的bean定义将覆盖之前加载的文件。这可以用来通过额外的XML文件故意覆盖某些bean定义
2、随后不紧不慢走过来的不是一个完整的somebody,AbstractXmlApplicationContext, 它是为了方便ApplicationContext的实现而出现的(抽象类一个很重要的思想就是适配)。
AbstractXmlApplicationContext 的最主要作用就是通过创建一个XML阅读器解析ClassPathXmlApplicationContext 注册的配置文件。它有两个最主要的方法 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 和 loadBeanDefinitions(XmlBeanDefinitionReader reader)
3、下一个缓缓出场的是 AbstractRefreshableConfigApplicationContext ,它就像是中间人的角色,并不作多少工作,很像古代丞相的奏折要呈递给皇上,它的作用就相当于是拿奏折的角色。它用作XML应用程序上下文实现的基类,例如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext和XmlWebApplicationContext
4、当老板的一般都比较听小秘的,那么AbstractRefreshableApplicationContext就扮演了小秘的角色,它是ApplicationContext的基类,支持多次调用refresh()方法,每次都会创建一个新的内部bean factory实例。继承 AbstractRefreshableApplicationContext 需要唯一实现的方法就是loadBeanDefinitions,在每一次调用刷新方法的时候。一个具体的实现是加载bean定义信息的DefaultListableBeanFactory。
5、但是只有小秘给老板递交请辞不行,中间还要有技术leader 来纵览大局,向上与老板探讨公司发展计划,在下领导新人做项目打硬仗(这种男人真的很有魅力哈哈哈),但是技术leader也不能干完所有的工作,他还需要交给手下的程序员去帮他完成具体的工作,程序员接到一项工作,看看有没有可复用的项目和开源类库,发现有可用的,直接把"引用"链接过去就可以了。这就是容器的初始化工作,但是这一步的流程还没有结束,你还得时刻记住你是给boss干活的。
你需要一些程序员帮你做具体的编码工作,也需要明确你是公司的员工,需要听从老板的,所以你需要明确老板是谁
但是这个时候老板出差了,不在了(因为传过来的parent 是 null),所以你需要自己做一些decision。至此,第一条线路就分析完成了。
配置路径解析
第二条线路,ApplicationContext中的 setConfigLocations(configLocations)
关键点:路径解析方法 : AbstractRefreshableConfigApplicationContext 中的 resolvePath(locations[i]).trim(); 来看看是如何进行路径解析的
// 解析给定的路径,必要时用相应的环境属性值替换占位符。应用于路径配置。 protected String resolvePath(String path) { return getEnvironment().resolveRequiredPlaceholders(path); }
涉及两个方法,AbstractRefreshableConfigApplicationContext 中的getEnvironment() 和 validateRequiredProperties(),先来看第一个
getEnvironment()
// 以配置的形式返回此应用程序上下文的Environment,来进一步自定义 // 如果没有指定,则通过初始化默认的环境。 @Override public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { // 使用默认的环境配置 this.environment = createEnvironment(); } return this.environment; }
下面来看一下createEnvironment()如何初始化默认的环境:
// 创建并返回一个 StandardEnvironment,子类重写这个方法为了提供 // 一个自定义的 ConfigurableEnvironment 实现。 protected ConfigurableEnvironment createEnvironment() { // StandardEnvironment 继承AbstractEnvironment,而AbstractEnvironment // 实现了ConfigurableEnvironment return new StandardEnvironment(); }
其实很简单,也只是new 了一个StandardEnvironment() 的构造器而已。StandardEnvironment是什么?非web应用程序的Environment 的标准实现。他实现了AbstractEnvironment 抽象类,下面是具体的继承树:
StandardEnvironment是AbstractEnvironment的具体实现,而AbstractEnvironment又是继承了ConfigurableEnvironment接口,提供了某些方法的具体实现,ConnfigurableEnvironment 继承了Environment,而Environment 和 ConfigurablePropertyResolver 同时继承了PropertyResolver
下面来看一下StandardEnvironment() 的源码:
现在读者就会产生疑问,不是说new出来一个标准的StandardEnvironment 实现吗,但是StandardEnvironment并没有默认的构造方法啊?这是什么回事呢?
其实StandardEnvironment 的构造方法是 AbstractEnvironment:
public AbstractEnvironment() { // 实现自定义属性资源的方法,也就是StandardEnvironment中customizePropertySources() customizePropertySources(this.propertySources); if (logger.isDebugEnabled()) { logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources); } }
上述的customizePropertySources 由StandardEnvironment 来实现,具体如下
@Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
由于容器在刚起步的时候 propertySources 是null,所以添加完系统环境(systemEnvironment)和系统属性(systemProperties) 之后,会变成下图所示
如何获取系统属性和如何获取系统环境没有往下跟,有兴趣的读者可以继续沿用。
大致截一个图,里面大概的属性是这样
systemProperties
systemEnvironment
另外一个是 resolveRequiredPlaceholders,它是由 PropertyResolver 超顶级接口定义的方法
// 在给定的text 参数中解析${} 占位符,将其替换为getProperty 解析的相应属性值。 // 没有默认值的无法解析的占位符将导致抛出IllegalArgumentException。 String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
由 AbstractPropertyResolver 子类来实现,且看AbstractPropertyResolver 的继承树
具体实现的方法如下:
解析完成占位符之后,需要做真正的解析,调用AbstractPropertyResolver中的doResolvePlaceholders 方法。
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { @Override public String resolvePlaceholder(String placeholderName) { return getPropertyAsRawString(placeholderName); } }); }
PlaceholderResolver是 PropertyPlaceholderHelper类的内部类,这是一种匿名内部类的写法,它真正调用的就是PropertyPlaceholderHelper`中的 replacePlaceholders 方法,具体如下:
直白一点,上述过程就是用来判断有没有 ${} 这个占位符,如果有的话就进入下面的判断逻辑,把${}中的值替换为 PlaceholderResolver 返回的值,如果没有的话,就直接返回。
容器刷新
在经过上述的准备工作完成后,接下来就是整个IOC,DI和AOP的核心步骤了,也是Spring框架的灵魂。由于源码太多,设计范围太广,本篇只分析刷新预处理应该做的事:我们都知道,无论你加载的是哪一种上下文环境,最终都会调用 AbstractApplicationContext 的refresh()方法,此方法是一切加载、解析、注册、销毁的核心方法,采用了工厂的设计思想。
刷新容器之刷新预处理
此步骤的主要作用在于:准备刷新的上下文,设置启动的时间和active的标志作为扮演属性资源初始化的角色。
这里面有两处代码需要说明:initPropertySources这个方法是需要子类进行实现的,默认是不会做任何事情的;getEnvironment() 这个方法由于上述的源码分析过程中,已经默认创建了 createEnvironment,所以这段代码是直接返回的
@Override public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { this.environment = createEnvironment(); } return this.environment; }
下面只剩下了validateRequiredProperties()的分析,不着急,看源码不能着急,要怀着这个世界很美好的心情去看。
首先在 ConfigurablePropertyResolver 接口中定义了 validateRequiredProperties 方法
// 验证每一个被setRequiredProperties 设置的属性存在并且解析非空值,会抛出 // MissingRequiredPropertiesException 异常如果任何一个需要的属性没有被解析。 void validateRequiredProperties() throws MissingRequiredPropertiesException;
在抽象子类AbstractPropertyResolver 中被重写
因为在我们的源码分析中,没有看到任何操作是在对 requiredProperties 进行添加操作,也就是如下:
@Override public void setRequiredProperties(String... requiredProperties) { if (requiredProperties != null) { for (String key : requiredProperties) { this.requiredProperties.add(key); } } }
所以,此时的 requiredProperties 这个set集合是null, 也就不存在没有解析的元素了。
本篇到此就结束了,下一篇文章会进行源码分析的下一个步骤: 创建IOC容器以及Bean的解析。
猜你喜欢
- 2024-10-15 BATJ面试必会之 Spring 篇(30题) 面试spring的面试题
- 2024-10-15 Spring知识点提炼 spring题
- 2024-10-15 springmvc的核心是啥,请求的流程怎么处理,控制反转怎么实现
- 2024-10-15 spring源码分析——spring大纲 spring源码分析和总结简书
- 2024-10-15 Spring思维导图,让Spring不再难懂(ioc篇)
- 2024-10-15 Spring框架介绍及使用 spring框架的使用步骤
- 2024-10-15 架构师必知必会:Java内置的控制反转机制-Service Provider
- 2024-10-15 轻松理解 Spirng IoC/控制方向反转
- 2024-10-15 什么是控制反转(ioc),通过解释总结告诉你
- 2024-10-15 搞透IOC,Spring IOC看这篇就够了
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)