- 1、上面那句代码有个文件叫
applicationContext.xml
, 这是个资源文件,由于我们的bean
都在里边进行配置定义,那 Spring 总得对这个文件进行读取并解析
吧!所以 Spring 中有个模块叫Resource
模块,顾名思义,就是资源
嘛!用于对所有资源xml、txt、property
等文件资源的抽象。关于对Resource
的更多知识,可以参考下边两篇文章:
谈一谈我对 Spring Resource 的理解:https://juejin.im/post/5ab0ce60518825611a405106
Spring 资源文件剖析和策略模式应用(李刚):http://www.ibm.com/developerworks/cn/java/j-lo-spring-resource/index.html
下面先贴一张小编生成的类图(图片有点大,不知道会不会不清晰,如果不清晰可以按照上面说的idea生成方法去生成即可)
:
可以看到Resource
是整个体系的根接口,点进源码可以看到它定义了许多的策略方法
,因为它是用了策略模式
这种设计模式,运用的好处就是策略接口/类
定义了同一的策略,不同的子类有不同的具体策略实现,客户端调用时传入一个具体的实现对象比如UrlResource或者FileSystemResource
给策略接口/类Resource
即可!
所有策略
如下:
- 2、上面讲了 Spring 框架对各种资源的抽象采用了
策略模式
,那么问题来了,现在表示资源的东西有了,那么是怎么把该资源加载进来呢?于是就有了下面的ResourceLoader
组件,该组件负责对 Spring 资源的加载,资源指的是xml
、properties
等文件资源,返回一个对应类型的Resource
对象。。UML 图如下:
从上面的 UML 图可以看出,ResourceLoader
组件其实跟Resource
组件差不多,都是一个根接口,对应有不同的子类实现,比如加载来自文件系统的资源,则可以使用FileSystemResourceLoader
, 加载来自ServletContext
上下文的资源,则可以使用ServletContextResourceLoader
。还有最重要的一点,从上图看出,ApplicationContext
,AbstractApplication
是实现了ResourceLoader
的,这说明什么呢?说明我们的应用上下文ApplicationContext
拥有加载资源的能力,这也说明了为什么可以通过传入一个String resource path
给ClassPathXmlApplicationContext("applicationContext.xml")
就能获得 xml 文件资源的原因了!清晰了吗?nice!
- 3、上面两点讲到了,好!既然我们拥有了加载器
ResourceLoader
,也拥有了对资源的描述Resource
, 但是我们在 xml 文件中声明的<bean/>
标签在 Spring 又是怎么表示的呢?注意这里只是说对bean
的定义,而不是说如何将<bean/>
转换为bean
对象。我想应该不难理解吧!就像你想表示一个学生Student
,那么你在程序中肯定要声明一个类Student
吧!至于学生数据是从excel
导入,或者程序运行时new
出来,或者从xml
中加载进来这些都不重要,重要的是你要有一个将现实中的实体表示为程序中的对象的东西,所以<bean/>
也需要在 Spring 中做一个定义!于是就引入一个叫BeanDefinition
的组件,UML 图如下:
下面讲解下 UML 图:
首先配置文件中的<bean/>
标签跟我们的BeanDefinition
是一一对应的,<bean>
元素标签拥有class
、scope
、lazy-init
等配置属性,BeanDefinition
则提供了相应的beanClass
、scope
、lazyInit
属性。
其中
RootBeanDefinition
是最常用的实现类,它对应一般性的<bean>
元素标签,GenericBeanDefinition
是自2.5
以后新加入的bean
文件配置属性定义类,是一站式服务类。在配置文件中可以定义父<bean>
和子<bean>
,父<bean>
用RootBeanDefinition
表示,而子<bean>
用ChildBeanDefiniton
表示,而没有父<bean>
的<bean>
就使用RootBeanDefinition
表示。AbstractBeanDefinition
对两者共同的类信息进行抽象。Spring
通过BeanDefinition
将配置文件中的<bean>
配置信息转换为容器的内部表示,并将这些BeanDefiniton
注册到BeanDefinitonRegistry
中。Spring
容器的BeanDefinitionRegistry
就像是Spring
配置信息的内存数据库,主要是以map
的形式保存,后续操作直接从BeanDefinitionRegistry
中读取配置信息。一般情况下,BeanDefinition
只在容器启动时加载并解析,除非容器刷新或重启,这些信息不会发生变化,当然如果用户有特殊的需求,也可以通过编程的方式在运行期调整BeanDefinition
的定义。
- 4、有了加载器
ResourceLoader
,也拥有了对资源的描述Resource
,也有了对bean
的定义,我们不禁要问,我们的Resource
资源是怎么转成我们的BeanDefinition
的呢? 因此就引入了BeanDefinitionReader
组件, Reader 嘛!就是一种读取机制,UML 图如下:
从上面可以看出,Spring 对 reader 进行了抽象,具体的功能交给其子类去实现,不同的实现对应不同的类,如PropertiedBeanDefinitionReader
,XmlBeanDefinitionReader
对应从 Property 和 xml 的 Resource 解析成BeanDefinition
。
其实这种读取数据转换成内部对象的,不仅仅是 Spring 专有的,比如:Dom4j 解析器
SAXReader reader = new SAXReader(); Document doc = reader.read(url.getFile());
//url 是一个 URLResource 对象 严格来说,都是 Reader 体系吧,就是将统一资源数据对象读取转换成相应内部对象。
- 5、好了!基本上所有组件都快齐全了!对了,还有一个组件,你有了
BeanDefinition
后,你还必须将它们注册到工厂中去,所以当你使用getBean()
方法时工厂才知道返回什么给你。还有一个问题,既然要保存注册这些bean
, 那肯定要有个数据结构充当容器吧!没错,就是一个Map
, 下面贴出BeanDefinitionRegistry
的一个实现,叫SimpleBeanDefinitionRegistry
的源码图:
BeanDefinitionRegistry
的 UML 图如下:
从图中可以看出,BeanDefinitionRegistry
有三个默认实现,分别是SimpleBeanDefinitionRegistry
,DefaultListableBeanFactory
,GenericApplicationContext
, 其中SimpleBeanDefinitionRegistry
,DefaultListableBeanFactory
都持有一个 Map,也就是说这两个实现类把保存了 bean。而GenericApplicationContext
则持有一个DefaultListableBeanFactory
对象引用用于获取里边对应的 Map。在DefaultListableBeanFactory
中
在GenericApplicationContext
中
- 6、前面说的 5 个点基本上可以看出
ApplicationContext
上下文基本直接或间接贯穿所有的部分,因此我们一般称之为容器
,除此之外,ApplicationContext
还拥有除了bean容器
这种角色外,还包括了获取整个程序运行的环境参数等信息(比如 JDK 版本,jre 等),其实这部分 Spring 也做了对应的封装,称之为Enviroment
, 下面就跟着小编的 eclipse, 一起 debug 下容器的初始化工程吧!
四、实践是检验真理的唯一标准
学生类Student.java
如下:
package com.wokao666;
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public Student() {
super();
}
@Override
public String toString() {
return “Student [id=” + id + “, ]”;
}
}
在application.xml
中进行配置,两个bean
:
好了,接下来给最开头那段代码打个断点 (Breakpoint
):
第一步:急切地加载ContextClosedEvent类,以避免在WebLogic 8.1中的应用程序关闭时出现奇怪的类加载器问题。
这一步无需太过在意!
第二步:既然是new ClassPathXmlApplicationContext()
那么就调用构造器嘛!
第三步:
第四步:
好,我们跟着第三步中的super(parent)
,再结合上面第三节的第 6 小点 UML 图一步一步跟踪,然后我们来到AbstractApplicationContext
的这个方法:
那么里边的resourcePatternResolver
的类型是什么呢?属于第三节说的 6 大步骤的哪个部分呢?通过跟踪可以看到它的类型是ResourcePatternResolver
类型的,而ResourcePatternResolver
又是继承了ResourceLoader
接口,因此属于加载资源模块,如果还不清晰,咱们再看看ResourcePatternResolver
的源码即可,如下图:
对吧!不仅继承ResourceLoader
接口,而且只定义一个getResources()
方法用于返回Resource[]
资源集合。再者,这个接口还使用了策略模式
,其具体的实现都在实现类当中,好吧!来看看 UML 图就知道了!
PathMatchingResourcePatternResolver
这个实现类呢!它就是用来解释不同路径资源的,比如你传入的资源路径有可能是一个常规的url
, 又或者有可能是以classpath*
前缀,都交给它处理。
ServletContextResourcePatternResolver
这个实现类顾名思义就是用来加载Servlet
上下文的,通常用在 web 中。
第五步:
接着第四步的方法,我们在未进入第四步的方法时,此时会对AbstractApplicationContext
进行实例化,此时this
对象的某些属性被初始化了(如日志对象)
,如下图:
接着进入getResourcePatternResolver()
方法:
第四步说了,PathMatchingResourcePatternResolver
用来处理不同的资源路径的,怎么处理,我们先进去看看!
如果找到,此时控制台会打印找到用于OSGi包URL解析的Equinox FileLocator
日志。没打印很明显找不到!
运行完成返回setParent()
方法。
第六步:
如果父代是非null
,,则该父代与当前this
应用上下文环境合并。显然这一步并没有做什么事!parent
显然是null
的,那么就不合并嘛!还是使用当前this
的环境。
做个总结:前六步基本上做了两件事:
-
1、初始化相关上下文环境,也就是初始化
ClassPathXmlApplicationContext
实例 -
2、获得一个
resourcePatternResolver
对象,方便第七步的资源解析成Resource
对象
第七步:
第七步又回到刚开始第三步的代码,因为我们前面 6 步已经完成对super(parent)
的追踪。让我们看看setConfigLocation()
方法是怎么一回事~
/**
-
Set the config locations for this application context.//未应用上下文设置资源路径
-
If not set, the implementation may use a default as appropriate.//如果未设置,则实现可以根据需要使用默认值。
*/
public void setConfigLocations(String… locations) {
if (locations != null) {//非空
Assert.noNullElements(locations, “Config locations must not be null”);//断言保证locations的每个元素都不为null
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();//去空格,很好奇resolvePath做了什么事情?
}
}
else {
this.configLocations = null;
}
}
进入resolvePath()
方法看看:
/**
-
解析给定的资源路径,必要时用相应的环境属性值替换占位符,应用于资源路径配置。
-
Resolve the given path, replacing placeholders with corresponding
-
environment property values if necessary. Applied to config locations.
-
@param path the original file path
-
@return the resolved file path
-
@see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
进入getEnvironment()
看看:
/**
-
{@inheritDoc}
-
If {@code null}, a new environment will be initialized via
-
{@link #createEnvironment()}.
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
进入createEnvironment()
, 方法,我们看到在这里创建了一个新的StandardEnviroment
对象,它是Environment
的实现类,表示容器运行的环境,比如 JDK 环境,Servlet 环境,Spring 环境等等,每个环境都有自己的配置数据,如System.getProperties()
、System.getenv()
等可以拿到 JDK 环境数据;ServletContext.getInitParameter()
可以拿到 Servlet 环境配置数据等等, 也就是说 Spring 抽象了一个Environment
来表示环境配置。
生成的StandardEnviroment
对象并没有包含什么内容,只是一个标准的环境,所有的属性都是默认值。
总结:对传入的path
进行路径解析
第八步:这一步是重头戏
先做个小结:到现在为止,我们拥有了以下实例:
现在代码运行到如下图的refresh()
方法:
看一下这个方法的内容是什么?
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 刷新前准备工作,包括设置启动时间,是否激活标识位,初始化属性源(property source)配置
prepareRefresh();
// 创建beanFactory(过程是根据xml为每个bean生成BeanDefinition并注册到生成的beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//准备创建好的beanFactory(给beanFactory设置ClassLoader,设置SpEL表达式解析器,设置类型转化器【能将xml String类型转成相应对象】,
//增加内置ApplicationContextAwareProcessor对象,忽略各种Aware对象,注册各种内置的对账对象【BeanFactory,ApplicationContext】等,
//注册AOP相关的一些东西,注册环境相关的一些bean
prepareBeanFactory(beanFactory);
try {
// 模板方法,为容器某些子类扩展功能所用(工厂后处理器)这里可以参考BeanFactoryPostProcessor接口的postProcessBeanFactory方法
postProcessBeanFactory(beanFactory);
// 调用所有BeanFactoryPostProcessor注册为Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 注册所有实现了BeanPostProcessor接口的Bean
registerBeanPostProcessors(beanFactory);
// 初始化MessageSource,和国际化相关
initMessageSource();
// 初始化容器事件传播器
initApplicationEventMulticaster();
// 调用容器子类某些特殊Bean的初始化,模板方法
onRefresh();
// 为事件传播器注册监听器
registerListeners();
// 初始化所有剩余的bean(普通bean)
finishBeanFactoryInitialization(beanFactory);
// 初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已创建的bean
destroyBeans();
// 重置active
标志
cancelRefresh(ex);
throw ex;
}
finally {
//重置一些缓存
resetCommonCaches();
}
}
}
在这里我想说一下,这个refresh()
方法其实是一个模板方法, 很多方法都让不同的实现类去实现,但该类本身也实现了其中一些方法,并且这些已经实现的方法是不允许子类重写的,比如:prepareRefresh()
方法。更多模板方法设计模式,可看我之前的文章 谈一谈我对‘模板方法’设计模式的理解(Template)。
先进入prepareRefresh()
方法:
/**
-
Prepare this context for refreshing, setting its startup date and
-
active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();//设置容器启动时间
this.closed.set(false);//容器关闭标志,是否关闭?
this.active.set(true);//容器激活标志,是否激活?
if (logger.isInfoEnabled()) {//运行到这里,控制台就会打印当前容器的信息
logger.info("Refreshing " + this);
}
// 空方法,由子类覆盖实现,初始化容器上下文中的property文件
initPropertySources();
//验证标记为必需的所有属性均可解析,请参阅ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
//允许收集早期的ApplicationEvents,一旦多播器可用,即可发布…
this.earlyApplicationEvents = new LinkedHashSet();
}
控制台输出:
三月 22, 2018 4:21:13 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@96532d6: startup date [Thu Mar 22 16:21:09 CST 2018]; root of context hierarchy
第九步:
进入obtainFreshBeanFactory()
方法:
/**
-
告诉子类刷新内部bean工厂(子类是指AbstractApplicationContext的子类,我们使用的是ClassPathXmlApplicationContext)
-
Tell the subclass to refresh the internal bean factory.
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();//刷新Bean工厂,如果已经存在Bean工厂,那就关闭并销毁,再创建一个新的bean工厂
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
he internal bean factory.
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();//刷新Bean工厂,如果已经存在Bean工厂,那就关闭并销毁,再创建一个新的bean工厂
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-uEzwyTc3-1715134850968)]
[外链图片转存中…(img-Js2pvVrM-1715134850969)]
[外链图片转存中…(img-26BYRr5i-1715134850969)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!