“莫道儒冠误此生,从来诗书不负人。”坚持就会有收获!
承接上篇记录,上篇主要是在说多读源码注解,而接下来都过程,更多是debug一步步观察对象变化,来进行阅读。一边阅读,一边某些变量拿捏不准的时候,进行debug。
下面这个图很重要,我觉得在读代码的时候帮助很大。
1.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() {
// Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment.
// 在上下文环境中初始化任何占位符属性源。
initPropertySources();
// Validate that all properties marked as required are resolvable:
// see ConfigurablePropertyResolver#setRequiredProperties
// 验证标记为必需的所有属性是否可解析:请参阅ConfigurablePropertyResolver #setRequiredProperties
getEnvironment().validateRequiredProperties();
// Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
// 重置本地application listeners为预刷新状态
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
// 允许收集早期的ApplicationEvents,
// 将在multicaster可用时发布...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
这个是该方法的源码,翻译了部分注释。
前面几行,都是设置一些该对象的标识变量,initPropertySources()
这个方法在当前类(AbstractApplicationContext)中是没有具体实现的,但是子类是有实现的,可以从下面的截图看到和最上方的图可以知道,并没有相关的类的实现这个方法,所以在当前过程中这个方法没有做任何处理,如果感觉不相信,就去每个实现类中打断点,看看会不会走到断点中。(愚蠢的我做了,确实没走进任何一个实现类中。)
接下来是
getEnvironment().validateRequiredProperties()
方法,getEnvironment()
其实就是ConfigurableEnvironment
对象,实际是new了一个StandardEnvironment
(ConfigurableEnvironment的子孙类)返回来,这里最好debug看一下,代码很简单,但是变量的值可能拿捏不准。validateRequiredProperties()
方法是一个接口,实现类有两个AbstractEnvironment
和AbstractPropertyResolver
,StandardEnvironment
也是AbstractEnvironment的子类,所以,选择查看AbstractEnvironment的方法,可以看到源码是调用了propertyResolver
的方法,而propertyResolver
又是一个ConfigurablePropertyResolver
的对象,ConfigurablePropertyResolver
的子类就是AbstractPropertyResolver
(好家伙!又绕回来了,早知道就直接看AbstractPropertyResolver中的方法了)。
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
this.propertyResolver.validateRequiredProperties();
}
直接查看AbstractPropertyResolver中的方法,这个用截图展示,在这里我打了一个断点,可以看出来,这requiredProperties的size为0,getEnvironment().validateRequiredProperties();
整个方法在注释中解释为验证标记为必需的所有属性是否可解析。说明一切正常,那就继续往下看。
接下来是查看earlyApplicationListeners
是否为null,为null就new一个,并将当前对象的applicationListeners全部放到容器中。
最后是初始化
earlyApplicationEvents
,prepareRefresh()
就结束了。
2.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();(通知子类刷新内部的bean factory)
主要关注obtainFreshBeanFactory()
方法的内容。当前所在类AbstractApplicationContext
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
查看refreshBeanFactory()
方法,它是一个接口方法,可以看到它的实现类分别为AbstractRefreshableApplicationContext
和GenericApplicationContext
,根据最上面的继承图我们可以知道,我们需要看的是AbstractRefreshableApplicationContext
中的实现方法。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 为了序列话指定id,如果需要的话,让这个BeanFactory从id反序列化到BeanFactory对象
beanFactory.setSerializationId(getId());
/**
* 设置两个属性:
* 1. 是否允许覆盖同名称的不同定义的对象
* 2. 是否允许bean之间存在循环依赖
*/
customizeBeanFactory(beanFactory);
// 初始化DocumentReader,并进行XML文件读取和解析
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
首先是判断当前的beanFactory
是否为null,不为null的话,就先释放掉资源,对于我们这种进行初始化的操作而言,肯定是null,debug验证也是null,所以直接看createBeanFactory ()
,
得到了一个DefaultListableBeanFactory
对象,然后是设置序列化编号,customizeBeanFactory(beanFactory)
从名字上来讲,是定制beanFactory,再看看代码,两行注释是我添加的,分别是allowBeanDefinitionOverriding
和allowCircularReferences
两个Boolean
变量的含义。
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
//是否允许覆盖同名称的不同定义的对象
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//是否允许bean之间存在循环依赖
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
这是一个定制beanFactory的方法,但是,这里实际的值都是为null,那么究竟何时才会设置这些值咧?
当然是子类中进行,举个栗子
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
......
@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
super.setAllowBeanDefinitionOverriding(false);
super.setAllowCircularReferences(false);
super.customizeBeanFactory(beanFactory);
}
}
当然,也不是一定要设置这两个值,看spring自己的测试用例里面的代码,它就啥都没设置,这个得看自己的需求,不过一般的需求,是不会深入到要求我们自己设计一个ClassPathXmlApplicationContext
这样的深度。
@Test
public void abstractRefreshableContextWithMisconfiguredBean() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext() {
@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
super.customizeBeanFactory(beanFactory);
registerMisconfiguredBeanDefinition(beanFactory);
}
};
assertFactoryCountThroughoutLifecycle(ctx);
}
这样customizeBeanFactory(beanFactory);
也阅读完了,那么接下来是loadBeanDefinitions(beanFactory);
初始化DocumentReader,并进行XML文件读取和解析,它是一个abstract
的方法,实际是交给子类去实现的,它有四个实现,根据我们最上面的图可知道,我们应该看AbstractXmlApplicationContext
中的实现。
代码如下:
private boolean validating = true;
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 为给定的BeanFactory创建一个新的XmlBeanDefinitionReader。
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//使用此上下文的资源加载环境配置bean定义阅读器。
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// 允许子类提供阅读器的自定义初始化,
// 然后继续实际加载bean定义。
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
该方法是为了通过XmlBeanDefinitionReader加载bean定义。代码很简单,但是需要我们debug看一下具体的过程。我是基本上一行一行的debug,引用的方法内部也看了一边,重点关注loadBeanDefinitions(beanDefinitionReader);
方法
可以进去看看
loadBeanDefinitions
方法,这里基本上是spring如何解析xml资源并且注册bean的过程,基本上完成了bean的解析,注册和IOC容器的实例化。这个代码值得一看,在整个过程中,spring用了很多方法的重载,这里面的内容实在太多了,我自己看了很久,里面内容很多,具体可以参照这篇文章的内容(https://www.imooc.com/article/13900),里面介绍了解析时使用的一些反射用法,提及了如何解析相关的xml,这里就就不多说了。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
基本上到这里ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
方法就结束,接下来该去看prepareBeanFactory(beanFactory);
看完整块obtainFreshBeanFactory
方法,其实是需要花很多时间的,不是这里说的内容这么少,省略掉了bean的解析注册流程没有写,我自己看完都觉得很累了,读源码真的不是一件轻松的事情,能坚持读完的人是真的厉害的人。看完这个部分,我个人觉得spring前期的准备流程是很长的逻辑线,但是我们看代码,首先就能看到整体的脉络,然后再深入一条条脉络,写的方法,重用性很高,并且有很多代码技巧,eg,定义一个方法为loadBeanDefinitions
,方法里面做完一些准备工作,再有doLoadBeanDefinitions
去做真正的加载,而且很重要一点,所以的方法和变量命名都非常好,我们基本上可以从名字知道这个方法干什么,对底层封装的非常好。
会在下一篇文章继续记录第三步骤prepareBeanFactory(beanFactory);
的解读。