我们通过ClassPathXmlApplicationContext容器来探究IOC容器初始化的过程。
如以下代码:通过ApplicationContext创建Spring容器,该容器会读取配置文件" /bean.xml ",并统一管理由文件中定义好的bean实例对象。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/bean.xml");
下面是IOC容器创建的几个阶段:Resource 定位、 载入BeanDefintion、 将BeanDefinition注册到容器。
1、Resource 定位
Resource 是 Spring 中用于封装I/O操作的接口,在创建Spring容器时,会去访问 XML 配置文件,二进制流、URL等方式访问资源。其实类型如下。
- FileSystemResource:以文件绝对路径进行资源访问。
- ClassPathResourcee:以类路径的方式访问资源。
- ServletContextResource:web应用根目录的方式访问资源。
- UrlResource:访问网络资源的实现类。
- ByteArrayResource: 访问字节数组资源的实现类。
那对于Spring来说这些类型其又是如何访问的?这儿其实Spring提供了一个ResourceLoader接口用于实现不同的Resource 加载策略。通过该接口实例对象可以获取一个Resource 对象
// 通过提供的location参数获取Resouces实例
Resource getResource(String location);
// 获取ClassLoader,通过ClassLoader可以将资源载入JVM中
ClassLoader getClassLoader();
注:ApplicationContext 的所有实现类都实现了ResourceLoader接口,可以直接调用getResourceLoader方法获取Resource对象。不同的ApplicatonContext实现类使用getResource方法取得的资源类型不同。例如:
XmlWebApplicationContext.getResource获取的就是ServletContextResource实例
FileSystemXmlApplicationContext.getResource获取的就是FileSystemResource实例
ClassPathXmlApplicationContext.getResource获取的就是ClassPathResource实例
载入BeanDefintion
通过Resouce定位,我们就可以读取到我们bean的资源对象了,现在我们就可以载入BeanDefintion了。那BeanDefintion又是个什么东西呢?其实BeanDefintion就是一个数据结构,其是根据Resource中的bean来生产的,bean会在Spring IOC容器内以BeanDefintion的形式存在,BeanDefinition就是Bean在IoC容器中的存在形式。
以下是载入BeanDefintion的过程
1、首先其会创建一个BeanDefintionReader对象,然后将其作为参数传入loadBeanDefinitions()方法中。
// 该方法属于AbstractXMLApplication
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建BeanDefintionReader对象,并设置基本的上下文环境
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
// 用于获取BeanDefintion 对象
this.loadBeanDefinitions(beanDefinitionReader);
}
2、通过 loadBeanDefintions方法将用户定义的资源和容器本身的资源都载入到reader中
// 获取到用户定义的Resource资源位置
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
// 载入Resource
reader.loadBeanDefinitions(configResources);
}
// 获取容器自身的本地配置文件地址
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
3、接下来查看reader.loaderBeanDefintions(configResources) 方法,发现该方法属于AbstractBeanDefintionReader类,其父接口是BeanDefintionReader。
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
Resource[] var3 = resources;
int var4 = resources.length;
for(int var5 = 0; var5 < var4; ++var5) {
Resource resource = var3[var5];
// 加载所有资源,交给AbstractBeanDefintionReader的子类处理这些Resouce
count += this.loadBeanDefinitions((Resource)resource);
}
return count;
}
4、在此处我们可以发现loadBeanDefinitions(),其主要实现是XmlBeanDefintionReader来实现的。
5、回到XmlBeanDefintionReader类中查看loadBeadnDefintions()方法,我们发现其主要通过doLoadBeanDefinitions来实现bean的加载。
6、现在要我们看看doLoadBeanDefintion的具体内容,其主要是将resource资源文件读取到Document中,并注册到容器里。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException
{
// 将resource资源文件中的内容读取到document中
Document doc = this.doLoadDocument(inputSource, resource);
// 将document 文件的bean封装为BeanDefintion,并注册到容器
int count = this.registerBeanDefinitions(doc, resource);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
7、现在接着深入到registerBeanDefinitions()方法中,它主要对Spring Bean语义进行转化,变成BeanDefintion类型。首先获取DefaultBeanDefinitionDocumentReader实例,然后获取容器中的bean数量,通过documentReader中的registerBeanDefinitions方法进行注册和转化工作。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 获取DeanDefintionCocumentReader 实例
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
// 获取容器中要注册的bean数量
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
8、顺着上面的思路继续往下,在DefaultBeanDefinitionDocumentReader 中的registerBeanDefinitions 方法如图11所示,其获取document的根结点然后顺势访问所有的子节点。同时把处理BeanDefinition的过程委托给BeanDefinitionParserDelegate对象来完成。
protected void doRegisterBeanDefinitions(Element root) {
//处理BeanDefintion的过程委托给BeanDefintionParserDelegate实例来完成
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
this.preProcessXml(root);
// 核心方法,代理
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root);
this.delegate = parent;
}
BeanDefinitionParserDelegate类主要负责BeanDefinition的解析,涉及到JDK和CGLIB动态代理,BeanDefinitionParserDelegate代理类会完成对符合Spring Bean语义规则的处理,比如、、等的检测。
9、查看parseBeanDefinitions方法,其主要通过BeanDefinitionParserDelegate代理类中的parseBeanDefinitions方法,用来对XML文件中的节点进行解析。通过遍历import标签节点调用importBeanDefinitionResource方法对其进行处理,然后接着便利bean节点调用processBeanDefinition对其处理。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for(int i = 0; i < nl.getLength(); ++i) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element)node;
if (delegate.isDefaultNamespace(ele)) {
// 对节点元素进行处理的。从方法体的语句可以看出它对import标签、alias标签、bean标签进行了处理。
// 每类标签对应不同的BeanDefinition的处理方法。
this.parseDefaultElement(ele, delegate);
} else {
// 用户自己已节点处理方式
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
10、现在查看parseDefaultElement()方法,我们发现其是更具不同的标签进行不同的处理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}
11、现在我们选取processBeanDefinition()方法查看其是如何处理Bean的,首先通过delegate的parseBeanDefinitionElement方法传入节点信息,获取该Bean对应的name和alias。然后通过BeanDefinitionReaderUtils中的registerBeanDefinition方法对其进行容器注册,也就是将Bean实例注册到容器中进行管理。最后,发送注册事件。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 注册Bean实例
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
}
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
将BeanDefintion对象注册到容器
我们接着上面的registerBeanDefinition方法来看,Bean会被解析成BeanDefinition并与BeanName、Alias一同封装到BeanDefinitionHolder类中, 之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册到DefaultListableBeanFactory.beanDefinitionMap中。如果客户端需要获取Bean对象,Spring容器会根据注册的BeanDefinition信息进行实例化。
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
// 注册BeanDefintion
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
DefaultListableBeanFactory实现了上面调用BeanDefinitionRegistry接口的 registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法。这一部分的主要逻辑是向DefaultListableBeanFactory对象的beanDefinitionMap中存放beanDefinition,也就是说beanDefinition都放在beanDefinitionMap中进行管理。当初始化容器进行bean初始化时,在bean的生命周期分析里必然会在这个beanDefinitionMap中获取beanDefition实例。