spring框架被广泛在各个项目使用,它主要的作用有 ioc,aop 等功能,为了更好的理解他的运作方式,笔者准备阅读spring ioc的源码。并把一些学习笔记和自己的一些理解和大家分享讨论。
话不多说,直接进入正题,我们先来简单回顾下如何使用spring的ioc功能,首先在application.xml中配置一个bean A。
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<bean id="a" class="com.zjd.A" />
</beans>
BeanFactory beanfactory = new ClassPathApplicationContext("application.xml");
A a = (A)beanfactory.getBean("a");
System.out.print(a);
以上的一段代码发生了什么,spring是如何加载一个对象到beanFactory的。自己先思考一下如何实现的,先做一个猜想
- spring读取了xml配置文件,读取bean标签的一些属性,并封装到一个bean当中(后面知道是BeanDefinition接口,其中的实现类为RootBeanDefinition)
- getBean(“a”)代码会触发Bean的实例化过程,并把实例化后的对象放到一个Map当中
以上猜想是否正确,我们深入源码一探究竟。
以上为ClassPathXmlApplicationContext的类结构图。AbstractApplicationContent实现了几大接口,介绍几个比较重要的接口
BeanFactory bean工厂,定义了getBean等方法。
ApplicationEventPublisher 事件发布接口
ResourceLoader 资源加载接口
先来看下ClassPathXmlApplicationContext 构造函数,调用了父类 AbstractApplicationContext的refresh()方法
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
//创建解析器,解析configLocations
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
/*
* 该方法是spring容器初始化的核心方法。是spring容器初始化的核心流程,是一个典型的父类模板设计模式的运用
* 根据不同的上下文对象,会掉到不同的上下文对象子类方法中
*
* 核心上下文子类有:
* ClassPathXmlApplicationContext
* FileSystemXmlApplicationContext
* AnnotationConfigApplicationContext
* EmbeddedWebApplicationContext(springboot)
*
* */
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//为容器初始化做准备,重要程度:0
// Prepare this context for refreshing.
prepareRefresh();
/**
重要程度:5
1、创建BeanFactory对象
* 2、xml解析
* 传统标签解析:bean、import等
* 自定义标签解析 如:<context:component-scan base-package="com.zjd"/>
* 自定义标签解析流程:
* a、根据当前解析标签的头信息找到对应的namespaceUri
* b、加载spring所有jar中的spring.handlers文件。并建立映射关系
* c、根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
* d、调用类的init方法,init方法是注册了各种自定义标签的解析类
* e、根据namespaceUri找到对应的解析类,然后调用paser方法完成标签解析
*
* 3、把解析出来的xml标签封装成BeanDefinition对象
* */
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/*
* 给beanFactory设置一些属性值,可以不看
* */
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
/*
* BeanDefinitionRegistryPostProcessor
* BeanFactoryPostProcessor
* 完成对这两个接口的调用
* */
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
/*
* 把实现了BeanPostProcessor接口的类实例化,并且加入到BeanFactory中
* */
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
/*
* 国际化,重要程度2
* */
// Initialize message source for this context.
initMessageSource();
//初始化事件管理类
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
//这个方法着重理解模板设计模式,因为在springboot中,这个方法是用来做内嵌tomcat启动的
// Initialize other special beans in specific context subclasses.
onRefresh();
/*
* 往事件管理类中注册事件类
* */
// Check for listener beans and register them.
registerListeners();
/*
* 这个方法是spring中最重要的方法,没有之一
* 所以这个方法一定要理解要具体看
* 1、bean实例化过程
* 2、ioc
* 3、注解支持
* 4、BeanPostProcessor的执行
* 5、Aop的入口
*
* */
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
以上就是spring容器启动的流程代码,具体的流程我们一步一步分析
首先看下这行代码
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
这行代码做了一些事情
- 创建BeanFactory对象
- xml解析
- 把解析出来的xml标签封装成BeanDefinition对象。那么BeanDefinition是个什么东东,简单理解就是存放了xml配置文件的一些属性,比如 <bean id = "a" class = “”xxx” /> spring解析之后,会把id,class等属性封装到BeanDefinition中。后续bean实例化的时候,会读取beanDefinition的内容,并进行实例化,和属性注入等操作。
分析如何创建BeanFactory对象
先看下源码,此方法中调用的2个方法均为抽象方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//核心方法,必须读,重要程度:5
refreshBeanFactory();
return getBeanFactory();
}
/**
* Subclasses must implement this method to perform the actual configuration load.
* The method is invoked by {@link #refresh()} before any other initialization work.
* <p>A subclass will either create a new bean factory and hold a reference to it,
* or return a single BeanFactory instance that it holds. In the latter case, it will
* usually throw an IllegalStateException if refreshing the context more than once.
* @throws BeansException if initialization of the bean factory failed
* @throws IllegalStateException if already initialized and multiple refresh
* attempts are not supported
*/
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
以下代码中我们着重分析loadBeanDefinitions(beanFactory),此方法中会读取相关xml,封装成BeanDefinition。
@Override
protected final void refreshBeanFactory() throws BeansException {
//如果BeanFactory不为空,则清除BeanFactory和里面的实例
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建DefaultListableBeanFactory
//BeanFactory 实例工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//设置是否可以循环依赖 allowCircularReferences
//是否允许使用相同名称重新注册不同的bean实现.
customizeBeanFactory(beanFactory);
//解析xml,并把xml中的标签封装成BeanDefinition对象
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
我们看到了一个比较重要的方法
loadBeanDefinitions(beanFactory);
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
//创建xml的解析器,这里是一个委托模式
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的
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.
initBeanDefinitionReader(beanDefinitionReader);
//主要看这个方法 重要程度 5
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//获取需要加载的xml配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
看下里面做了什么事情,首先创建了一个XmlBeanDefinitionReader ,此对象的作用主要是xml的读取。将读取xml的过程委托给了XmlBeanDefinitionReader
//XmlBeanDefinitionReader中的方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//获取Resource对象中的xml文件流对象
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//InputSource是jdk中的sax xml文件解析对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//主要看这个方法 ** 重要程度 5
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
讲了半天,我们终于进入主题doLoadBeanDefinitions,我发现spring的源码有个特征,真正干事的都是以doXXX开头的方法。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
//把inputSource 封装成Document文件对象,这是jdk的API
Document doc = doLoadDocument(inputSource, resource);
//主要看这个方法,根据解析出来的document对象,拿到里面的标签元素封装成BeanDefinition
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
把inputSource封装成Document后,传入 registerBeanDefinitions方法,进行BeanDefinition的封装动作。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//又来一记委托模式,BeanDefinitionDocumentReader委托这个类进行document的解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//主要看这个方法,createReaderContext(resource) XmlReaderContext上下文,封装了XmlBeanDefinitionReader对象
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 运用反射实例化BeanDefinitionDocumentReader 对象。并调用它的documentReader.registerBeanDefinitions(doc, createReaderContext(resource));方法。
下面我们进入
DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法,其中root为解析到的xml文件的所有配置信息。
protected void doRegisterBeanDefinitions(Element root) {
//省略一些不重要代码
preProcessXml(root);
//主要看这个方法,标签具体解析过程
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
}
此方法主要做了3件事情,看字面意思,1 处理xml前的准备工作。2标签解析工作。3解析之后的工作。
至此,我们分析了bean加载的大体流程,关于具体的解析工作,笔者将会在下篇文章介绍。