spring bean什么时候加载
1个回答
展开全部
一个applicationContext.xml配置文件,这个不可少
一个bean,这里我没用接口,直接用一个普通的类做为Spring的bean
一个Junit测试类
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="studentBean" class="my.StudentBean"></bean>
</beans>
StudentBean
public class StudentBean{
public void getName(String name) {
System.out.println("你的名字是:" + name);
}
}
单元测试类
1 public class MyTest {
2
3 public static void main(String[] args) {
4 ClassPathResource res = new ClassPathResource("my/applicationContext.xml");
5
6 XmlBeanFactory bf = new XmlBeanFactory(res);
7
8 StudentBean bean = (StudentBean)bf.getBean("studentBean");
9
10 bean.getName("yangay");
11 }
12 }
运行单元测试,打印出“你的名字是:yangay”,测试类只有四行代码,但Spring到底为我们做了些什么?下面我们就基于这样的场景去分析bean的加载过程。
2. 初步分析
(1) 获取配置文件
ClassPathResource res = new ClassPathResource("my/applicationContext.xml");
这一句只是读入配置文件,并封装成Spring提供的Resource对象,供后面逻辑使用。
在Spring内部,有超过十个以Resource结尾的类或文件,他们处理不同类型的资源文件,如FileSystemResource、ClassPathResource、UrlResource等,处理过程大同小异,内部细节可以不必关心,跟其他组件逻辑几乎没关系。
(2) 解析配置文件并注册bean
XmlBeanFactory bf = new XmlBeanFactory(res);
这里面的逻辑相当复杂,涉及到众多Factory、Reader、Loader、BeanDefinition、Perser、Registry系列接口和类,但他们做的基本事情就是将applicationContext.xml配置的Bean信息构成BeanDefinition对象,然后放到Factory的map中(这一步就是所谓的注册),这样以后程序就可以直接从Factory中拿Bean信息了。
要跟踪这个处理过程,大致流程如下:
a. 构造XmlBeanFactory时,会调用Reader对象的loadBeanDefinitions方法去加载bean定义信息
b. 在Reader对象的doLoadBeanDefinitions验证文档(配置文件)模式,然后通过documentLoader对象处理资源对象,生成我们Document对象;
c. 调用BeanDefinitionDocumentReader对象的doRegisterBeanDefinitions去注册bean定义信息;
d. parseBeanDefinitions从xml文档根节点递归循环处理各个节点,对bean节点真正的处理工作委托给了BeanDefinitionParserDelegate,方法parseBeanDefinitionElement将一个bean节点转换成一个BeanDefinitionHolder对象,这才是最终的解析过程;
e. DefaultListableBeanFactory.registerBeanDefinition利用解析好的beanDefinition对象完成最终的注册,其实就是把beanName和beanDefinition作为键值对放到beanFactory对象的map;
(3) 实例化Bean
StudentBean bean = (StudentBean)bf.getBean("studentBean");
这一步Spring同样做了复杂的处理,但基本原理就是利用反射机制,通过bean的class属性创建一个bean的实例,例子中是创建了一个StudentBean对象。
(4) 调用对象的方法,没什么好说的。当然如果方法上做了事务、AOP之类的声明,这一步的处理就不会那么简单了。
3. 解析配置文件并注册bin对象
我们分析bean的注册过程,就是下面这行代码,他完成了配置文件的解析和bin的注册功能,我们看看Spring到底为我们做了多少事情。
XmlBeanFactory bf = new XmlBeanFactory(res);
public class XmlBeanFactory extends DefaultListableBeanFactory {
//这里为容器定义了一个默认使用的bean定义读取器
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//在初始化函数中使用读取器来对资源进行读取,得到bean定义信息。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
我们跟进去,发现他实际调用了XmlBeanDefinitionReader对象的loadBeanDefinitions方法。
3.1 XmlBeanDefinitionReader.loadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//封装资源文件
return loadBeanDefinitions(new EncodedResource(resource));
}
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
这个方法是整个资源加载的切入点,我们先大致看看这个方法的处理流程:
a. 封装资源文件
new EncodedResource(resource)
b. 获取输入流
从EncodedResource对象中获取InputStream并构造InputSource对象
c. 然后调用doLoadBeanDefinitions方法完成具体的加载过程
3.2 doLoadBeanDefinitions方法
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
这个方法的代码很长,如果不考虑异常处理,其实只做了三件事情:
a. 获取对XML文件的验证模式
b. 加载XML文件,并得到对应的Document对象
c. 根据返回的Document对象注册bean信息
这里对验证模式不进行讨论;
这里不对Document对象的加载过程进行讨论;
这里直接进入bean的注册方法registerBeanDefinitions
3.3 registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 这里定义解析器,使用XmlBeanDefinitionParser来解析xml方式的bean定义文件 - 现在的版本不用这个解析器了,使用的是XmlBeanDefinitionReader
if (this.parserClass != null) {
XmlBeanDefinitionParser parser =
(XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
return parser.registerBeanDefinitions(this, doc, resource);
}
// 具体的注册过程,首先得到XmlBeanDefinitionReader,来处理xml的bean定义文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getBeanFactory().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getBeanFactory().getBeanDefinitionCount() - countBefore;
}
当把文档转换为Document对象后,提取及注册bean就是我们的重头戏了。
这里并没有看到我们想要的代码,而是把工作委托给了BeanDefinitionDocumentReader对象去处理
3.4 BeanDefinitionDocumentReader.doRegisterBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
Element root = doc.getDocumentElement();
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
//这里得到xml文件的子节点,比如各个bean节点
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;
String namespaceUri = ele.getNamespaceURI();
if (delegate.isDefaultNamespace(namespaceUri)) {
//这里是解析过程的调用,对缺省的元素进行分析比如bean元素
parseDefaultElement(ele, delegate);
}else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
经过艰难险阻,山路十八弯,我们终于走到了核心逻辑的底部doRegisterBeanDefinitions,如果说以前一直是XML加载解析的准备阶段,
那么这个方法算是真正地开始进行解析了,我们期待的核心部分真正开始了。
这个方法的代码我们比较熟悉,读取Document对象,循环每一个bean节点,然后进行处理。
Spring有两类Bean,一个是默认的,一个是自定义的bean,这个方法对他们分别调用了不同方法进行处理。
3.5 对默认标签的处理 processBeanDefinition方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
a. 首先利用委托类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder对象bdHolder,经过这个方法,bdHolder实例已经包含我们配置文件中对bean的所有配置信息了,如name、class等。
b. 对bdHolder进行装饰
c. 解析完成后,要对bdHolder进行注册,同样,注册过程委托给了BeanDefinitionReaderUtils去处理
3.6 delegate.parseBeanDefinitionElement(ele)
这个方法便是对默认标签解析的全过程了,他将一个element节点转换成BeanDefinitionsHolder对象,其中ele和bdHolder中的属性是对应的。不论是常用的还是不常用的我们都看到了,尽管有些复杂属性还需要进一步解析,但丝毫不会影响我们兴奋的心情。
a. 提取元素的id和name属性
b. 进一步解析其他所有属性并统一封装到BeanDefinition类型的实例
c. 将获取到的信息封装到BeanDefinitionHolder实例中待续。。。
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
return new BeanDefinitionHolder(bd, beanName, aliasesArray);
跟进去,我们就看到解析的最底层了,如parseMetaElements,这里不再进行往下分析。
对于配置文件,解析也解析完了,装饰也装饰完了,已经把xml中bean元素的各属性封装到了BeanDefinition对象,已经可以满足后续的使用要求了,剩下的工作便是注册解析的BeanDefinition。
3.7 BeanDefinitionReaderUtils.registerBeanDefinition
这个方法并没有做太多事情,而是直接调用了BeanDefinitionRegistry的注册方法。BeanDefinitionRegistry是一个接口,有多个实现类,这里我们使用了默认的实现DefaultListableBeanFactory。
3.8 DefaultListableBeanFactory.registerBeanDefinition
代码啰嗦了一大堆,实际上所谓的注册,就是把beanName和beanDefinition对象作为键值对放到BeanFactory对象的beanDefinitionMap。
但Spring经常把简单的逻辑写的非常“啰嗦”,仔细分析代码,发现他完成了几个事情:
a. 对bean对象的校验
b. 检查beanFactory中是否已经有同名的bean,如果有,进行相应处理
c. 把bean对象放到beanDefinitionMap中(这就是最终所谓的注册)
d. 清除整个过程缓存的对象数据
以上便是Spring对bean解析注册的全过程,总结一下大致步骤:
1. 加载XML文件,封装成Resource对象
2. 调用Reader对象方法读取XML文件内容,并将相关属性放到BeanDefinition实例
3. 将BeanDefinition对象放到BeanFactory对象
一个bean,这里我没用接口,直接用一个普通的类做为Spring的bean
一个Junit测试类
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="studentBean" class="my.StudentBean"></bean>
</beans>
StudentBean
public class StudentBean{
public void getName(String name) {
System.out.println("你的名字是:" + name);
}
}
单元测试类
1 public class MyTest {
2
3 public static void main(String[] args) {
4 ClassPathResource res = new ClassPathResource("my/applicationContext.xml");
5
6 XmlBeanFactory bf = new XmlBeanFactory(res);
7
8 StudentBean bean = (StudentBean)bf.getBean("studentBean");
9
10 bean.getName("yangay");
11 }
12 }
运行单元测试,打印出“你的名字是:yangay”,测试类只有四行代码,但Spring到底为我们做了些什么?下面我们就基于这样的场景去分析bean的加载过程。
2. 初步分析
(1) 获取配置文件
ClassPathResource res = new ClassPathResource("my/applicationContext.xml");
这一句只是读入配置文件,并封装成Spring提供的Resource对象,供后面逻辑使用。
在Spring内部,有超过十个以Resource结尾的类或文件,他们处理不同类型的资源文件,如FileSystemResource、ClassPathResource、UrlResource等,处理过程大同小异,内部细节可以不必关心,跟其他组件逻辑几乎没关系。
(2) 解析配置文件并注册bean
XmlBeanFactory bf = new XmlBeanFactory(res);
这里面的逻辑相当复杂,涉及到众多Factory、Reader、Loader、BeanDefinition、Perser、Registry系列接口和类,但他们做的基本事情就是将applicationContext.xml配置的Bean信息构成BeanDefinition对象,然后放到Factory的map中(这一步就是所谓的注册),这样以后程序就可以直接从Factory中拿Bean信息了。
要跟踪这个处理过程,大致流程如下:
a. 构造XmlBeanFactory时,会调用Reader对象的loadBeanDefinitions方法去加载bean定义信息
b. 在Reader对象的doLoadBeanDefinitions验证文档(配置文件)模式,然后通过documentLoader对象处理资源对象,生成我们Document对象;
c. 调用BeanDefinitionDocumentReader对象的doRegisterBeanDefinitions去注册bean定义信息;
d. parseBeanDefinitions从xml文档根节点递归循环处理各个节点,对bean节点真正的处理工作委托给了BeanDefinitionParserDelegate,方法parseBeanDefinitionElement将一个bean节点转换成一个BeanDefinitionHolder对象,这才是最终的解析过程;
e. DefaultListableBeanFactory.registerBeanDefinition利用解析好的beanDefinition对象完成最终的注册,其实就是把beanName和beanDefinition作为键值对放到beanFactory对象的map;
(3) 实例化Bean
StudentBean bean = (StudentBean)bf.getBean("studentBean");
这一步Spring同样做了复杂的处理,但基本原理就是利用反射机制,通过bean的class属性创建一个bean的实例,例子中是创建了一个StudentBean对象。
(4) 调用对象的方法,没什么好说的。当然如果方法上做了事务、AOP之类的声明,这一步的处理就不会那么简单了。
3. 解析配置文件并注册bin对象
我们分析bean的注册过程,就是下面这行代码,他完成了配置文件的解析和bin的注册功能,我们看看Spring到底为我们做了多少事情。
XmlBeanFactory bf = new XmlBeanFactory(res);
public class XmlBeanFactory extends DefaultListableBeanFactory {
//这里为容器定义了一个默认使用的bean定义读取器
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//在初始化函数中使用读取器来对资源进行读取,得到bean定义信息。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
我们跟进去,发现他实际调用了XmlBeanDefinitionReader对象的loadBeanDefinitions方法。
3.1 XmlBeanDefinitionReader.loadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//封装资源文件
return loadBeanDefinitions(new EncodedResource(resource));
}
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
这个方法是整个资源加载的切入点,我们先大致看看这个方法的处理流程:
a. 封装资源文件
new EncodedResource(resource)
b. 获取输入流
从EncodedResource对象中获取InputStream并构造InputSource对象
c. 然后调用doLoadBeanDefinitions方法完成具体的加载过程
3.2 doLoadBeanDefinitions方法
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
这个方法的代码很长,如果不考虑异常处理,其实只做了三件事情:
a. 获取对XML文件的验证模式
b. 加载XML文件,并得到对应的Document对象
c. 根据返回的Document对象注册bean信息
这里对验证模式不进行讨论;
这里不对Document对象的加载过程进行讨论;
这里直接进入bean的注册方法registerBeanDefinitions
3.3 registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 这里定义解析器,使用XmlBeanDefinitionParser来解析xml方式的bean定义文件 - 现在的版本不用这个解析器了,使用的是XmlBeanDefinitionReader
if (this.parserClass != null) {
XmlBeanDefinitionParser parser =
(XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
return parser.registerBeanDefinitions(this, doc, resource);
}
// 具体的注册过程,首先得到XmlBeanDefinitionReader,来处理xml的bean定义文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getBeanFactory().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getBeanFactory().getBeanDefinitionCount() - countBefore;
}
当把文档转换为Document对象后,提取及注册bean就是我们的重头戏了。
这里并没有看到我们想要的代码,而是把工作委托给了BeanDefinitionDocumentReader对象去处理
3.4 BeanDefinitionDocumentReader.doRegisterBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
Element root = doc.getDocumentElement();
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
//这里得到xml文件的子节点,比如各个bean节点
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;
String namespaceUri = ele.getNamespaceURI();
if (delegate.isDefaultNamespace(namespaceUri)) {
//这里是解析过程的调用,对缺省的元素进行分析比如bean元素
parseDefaultElement(ele, delegate);
}else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
经过艰难险阻,山路十八弯,我们终于走到了核心逻辑的底部doRegisterBeanDefinitions,如果说以前一直是XML加载解析的准备阶段,
那么这个方法算是真正地开始进行解析了,我们期待的核心部分真正开始了。
这个方法的代码我们比较熟悉,读取Document对象,循环每一个bean节点,然后进行处理。
Spring有两类Bean,一个是默认的,一个是自定义的bean,这个方法对他们分别调用了不同方法进行处理。
3.5 对默认标签的处理 processBeanDefinition方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
a. 首先利用委托类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder对象bdHolder,经过这个方法,bdHolder实例已经包含我们配置文件中对bean的所有配置信息了,如name、class等。
b. 对bdHolder进行装饰
c. 解析完成后,要对bdHolder进行注册,同样,注册过程委托给了BeanDefinitionReaderUtils去处理
3.6 delegate.parseBeanDefinitionElement(ele)
这个方法便是对默认标签解析的全过程了,他将一个element节点转换成BeanDefinitionsHolder对象,其中ele和bdHolder中的属性是对应的。不论是常用的还是不常用的我们都看到了,尽管有些复杂属性还需要进一步解析,但丝毫不会影响我们兴奋的心情。
a. 提取元素的id和name属性
b. 进一步解析其他所有属性并统一封装到BeanDefinition类型的实例
c. 将获取到的信息封装到BeanDefinitionHolder实例中待续。。。
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
return new BeanDefinitionHolder(bd, beanName, aliasesArray);
跟进去,我们就看到解析的最底层了,如parseMetaElements,这里不再进行往下分析。
对于配置文件,解析也解析完了,装饰也装饰完了,已经把xml中bean元素的各属性封装到了BeanDefinition对象,已经可以满足后续的使用要求了,剩下的工作便是注册解析的BeanDefinition。
3.7 BeanDefinitionReaderUtils.registerBeanDefinition
这个方法并没有做太多事情,而是直接调用了BeanDefinitionRegistry的注册方法。BeanDefinitionRegistry是一个接口,有多个实现类,这里我们使用了默认的实现DefaultListableBeanFactory。
3.8 DefaultListableBeanFactory.registerBeanDefinition
代码啰嗦了一大堆,实际上所谓的注册,就是把beanName和beanDefinition对象作为键值对放到BeanFactory对象的beanDefinitionMap。
但Spring经常把简单的逻辑写的非常“啰嗦”,仔细分析代码,发现他完成了几个事情:
a. 对bean对象的校验
b. 检查beanFactory中是否已经有同名的bean,如果有,进行相应处理
c. 把bean对象放到beanDefinitionMap中(这就是最终所谓的注册)
d. 清除整个过程缓存的对象数据
以上便是Spring对bean解析注册的全过程,总结一下大致步骤:
1. 加载XML文件,封装成Resource对象
2. 调用Reader对象方法读取XML文件内容,并将相关属性放到BeanDefinition实例
3. 将BeanDefinition对象放到BeanFactory对象
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询