“当时年少春衫薄,骑马倚斜桥,满楼红袖招”
常常有人沉溺于回忆之中,但是我个人觉得还是要向前看,做好眼前的事才是对自己最大的负责,“来世不可待,往事不可追。”
一、我阅读源码前参照书籍
相信阅读源码的人,应该都查过相关的书籍,我在阅读源码的时候,我查看了一下相关的书籍,业界好像常常有人推荐《spring源码深度解析》这本书,我也看了一半,开始阅读源码,这本书从BeanFactory入手,先从解析配置文件开始,讲XmlBeanFactory
这个类,我依葫芦画瓢,发现这个类已经不推荐使用了。
@Deprecated:若某类或某方法加上该注解之后,表示此方法或类不再建议使用,调用时也会出现删除线,但并不代表不能用,只是说,不推荐使用,因为还有更好的方法可以调用。
还有一本书《Spring技术内幕》,这个本书也应该看spring源码的朋友,可能会看的一本书。
二、阅读源码前自己的理解与想法
一开始到这个地方,不知道该怎么继续看下去了,但是细细想来,按照我们自己的理解,首先,我的理解是(个人理解,错误的地方请大家指正),任何项目启动的时候,第一件事是干什么咧,我认为是读取配置,讲项目需要的相关的类加载到内存中,依此类推,spring最开始学习的时候,大家都是先从xml配置beans开始,那么启动的时候,spring是不是也应该先把xml中的配置读取到,然后按照配置,将相关的bean放到容器中,依照设定好的流程,完善上下文的其他部分,也就是ApplicationContext。
我先把自己的理解猜想写下来,再阅读源码的过程,一点点修正我的想法。
三、开始证实
既然我认为上下文(ApplicationContext)是这个源码阅读的关键,那么我就从构建一个上下文(ApplicationContext)开始。
package com.laoye.test;
import com.laoye.spring.beans.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeansTest {
@Test
public void testStudent(){
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
Student student = context.getBean("student",Student.class);
System.out.println(student.getName());
}
}
代码如上,具体的如何构建spring阅读源码环境以及增加一个测试模块,我已经在记录二中介绍过了,我就不过多介绍了。
总共三行代码,其中第一行,读取xml配置,生成上下文,可以看看ApplicationContext和ClassPathXmlApplicationContext之间的关系:
首先走进第一行代码,就是进入ClassPathXmlApplicationContext的构造器中,构造器代码如下:
/**
* Create a new ClassPathXmlApplicationContext, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation resource location
* @throws BeansException if context creation failed
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
首先看注解中的介绍这个构造器要做什么事:创建新的ClassPathXmlApplicationContext,加载xml文件中的定义并自动刷新上下文。那么实际怎么做的,向下走,代码如下:
/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
其中的configLocations是我们写入的参数classpath*:application.xml
,refresh为true
,parent为null
,当然在这里最好也读一下注释内容,前半部分跟上面看的内容差不多,多了一个内容loading all bean definitions and creating all singletons.Alternatively, call refresh manually after further configuring the context.
(加载所有定义的bean,或者在进一步配置上下文之后手动调用refresh)。
代码中super(parent);
这句代码,Java中子类的构造器中必须调用父类的构造器,如果父类没有无参的构造器,则必须显式调用父类的构造器,就是使用supper
,并且是写到第一行,相当于添加父类的引用,这种显式调用一直到类AbstractApplicationContext
,可以由上面提供的类的继承图可以看到ClassPathXmlApplicationContext
向上跨过四层到AbstractApplicationContext
,AbstractApplicationContext中使用的代码如下:
/**
* Create a new AbstractApplicationContext with the given parent context.
* @param parent the parent context
*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
按照习惯,先读注释,再看代码,下面是两行代码分别调用的方法:
this()指向AbstractApplicationContext的构造方法
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
这个方法构造器内生成了一个资源解析器,可以走进去看一下,就是new了一个资源解析器对象PathMatchingResourcePatternResolver
(它实现了接口ResourcePatternResolver
)。不过记住这个对象,在后面的源码中会用到,不然到时看代码的时候一头雾水。
setParent(parent)方法的代码:
/**
* Set the parent of this application context.
* <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
* this (child) application context environment if the parent is non-{@code null} and
* its environment is an instance of {@link ConfigurableEnvironment}.
* @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
*/
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
应该可以知道,参数传进来是null,所以代码就很好懂。
显式的supper()方法到这里就是尽头,接下来回到ClassPathXmlApplicationContext构造器,看第二行代码 setConfigLocations(configLocations);
走到方法里面去看
/**
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
我们传的参数locations为classpath*:application.xml
,代码简单,configLocations
是一个String数组,值得注意的是,setConfigLocations
这个方法,在类AbstractRefreshableConfigApplicationContext
中,有上面的继承图我们可以知道,ClassPathXmlApplicationContext
向上两层就是该类。
ClassPathXmlApplicationContext构造器中剩下的就是整个方法的重中之重,refresh()
方法(boolean变量refresh传的true,所以refresh是必须调用的),
走到refresh()方法里:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
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();
}
}
}
这里我们可以看到,spring的整体风格的一个鲜明的例子,我们写代码也应该学习这种方式,整个方法步骤分明,每一个步骤都有说明,组合各个基础方法,并且异常处理非常的好,我个人认为这一个模版方法,值得深入了解。
由于这个是接口方法的实现,所以整个方法的注释在上层接口方法上,这个方法到底在干一个什么事,可以从注解中了解到:
Load or refresh the persistent representation of the configuration,which might an XML file, properties file, or relational database schema.As this is a startup method, it should destroy already created singletons if it fails, to avoid dangling resources. In other words, after invocation of that method, either all or no singletons at all should be instantiated.
(加载或刷新配置的持久表示形式,这可能是XML文件、属性文件或关系数据库。由于这是一种启动方法,因此如果失败,它应该销毁已创建的单例,以避免资源空置。换句话说,在调用该方法之后,要么全部实例化,要么根本不实例化单例。)
现在再来看这个方法,先读每一行代码上的注解,可以看到步骤:
1.Prepare this context for refreshing.(准备上下文的刷新)
2.Tell the subclass to refresh the internal bean factory.(通知子类刷新内部的bean factory)
3.Prepare the bean factory for use in this context.(准备上下文使用的bean factory)
4.Allows post-processing of the bean factory in context subclasses.(在上下文子类中,允许bean factory的后处理)
5.Invoke factory processors registered as beans in the context.(在上下文中调用注册为bean的工厂处理器)
6.Register bean processors that intercept bean creation.(注册拦截bean创建的bean处理器。)
7.Initialize message source for this context.(为此上下文初始化消息源。)
8.Initialize event multicaster for this context.(初始化上下文中事件广播。)
9.Initialize other special beans in specific context subclasses.(初始化特定上下文子类中的其他特殊bean。)
10.Check for listener beans and register them.(检查监听bean并注册。)
11.Instantiate all remaining (non-lazy-init) singletons.(实例化(非延迟加载)剩下的单例)
12.Last step: publish corresponding event.(最后一步:发布对应的事件)
翻译仅供参考
说明一下:lazy-init这个配置只针对scope为singleton的bean,延迟加载 ,设置为lazy的bean将不会在ApplicationContext启动时提前被实例化,而是在第一次向容器通过getBean索取bean时实例化的。这种懒加载的方式,也是降低启动时间的一种手段。
我会一点点慢慢记录,争取每一篇文章不会太长也不会太短,太长看到一半就不想看了,太短说不了什么内容。会在下一篇开始详细介绍每一个步骤。