IOC Inversion of Control 控制反转,关键实现是DI Dependency Injection,就必然涉及到有一个容器保存系统中所有托管的bean。
那么Spring是如何找到这些托管的bean呢?关键有以下几步:
- 读取xml配置文件转换成Resource(现在流行的SpringBoot是另一套体系,但底层应该还是脱离不了Spring)
- 利用XAS框将Resource解析成Document对象,方便对各种标签的解析提取解析Document对象,封装成BeanDefinitions
- 将各类BeanDefinition注册进BeanRegistry
先看看关键的类 DefaultListableBeanFactory XmlBeanFactory,DefaultListableBeanFactory是整个bean加载的核心部分,是注册及加载bean的默认实现,XmlBeanFactory继承DLF,大部分对bean的解析和注册是在DFL实现的,XBF只是用了XmlBeanDefinitionReader读取xml配置文件。
XmlBeanDefinitionReader是读取xml文件、解析及注册的主要类,继承自AbstractBeanDefinitionReader中的方法,使用ResourceLoader将文件转换成Resource文件。
通过DocumentLoader对Resource文件进行转换成Document。用DefaultBeanDefinitionDocumentReader类对Document进行解析。
我们通常用这样的代码来启动一个bean工厂,这个工厂里就会包含了该配置文件里的所有托管对象。
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("application.xml"));
Spring定义了Resource接口抽象成内部使用到的底层资源:File, URL, Classpath 等等对应不同的Resource有不同的实现类,FileSystemResource,ClasspathResource,UrlResource,InputStreamResource,ByteArrayResource等
代码中的ClassPathResource实现也很简单, this.clazzLoader.getResourceAsStream(this.path) 通过这行代码在classpath路径下寻找对应的文件读取文件流返回。
可以看到XmlBeanFactory类没什么内容,只是增加了对xml读取配置文件的支持。
当封装好了Resource 剩下的解析和注册工作就交给XmlBeanDefinitionReader进行。
可以看到当resource被注入到factory的构造函数中,用reader开始读取resource并解析,这里是资源加载的开始。
在reader的 doLoadBeanDefinitions() 方法中先把文件流转成document,再解析document对象注册bean
这里用到的 DocumentBuilderFactory DocumentBuilder 都是 javax.xml.parsers 包下的类,也就是说Spring用JDK自带的技术解析XML。SAX 全称 Simple Api For Xml
loadDocument方法做了3件事:
- 创建 DocumentBuilderFactory
- 用factory创建DocumentBuilder
- 用builder解析文件流生成document
接着到 registerBeanDefinitions方法中,去解析doc注册bean,可以发现documentReader.registerBeanDifinitions() 源码中重要的地方之一是拿到root对象
documentReader引用的实例是DefaultBeanDefinitionDocumentReader类,在 registerBeanDefinitions() 方法中最关键的就是下面三行,其中 preProcessXml() 和 postProcessXml() 是空方法,是模板设计模式,留着给子类继承后去重写。
真正解析xml文件的开始,这里分为两个方向,对spring默认标签的解析和自定义标签解析。
protected void parseBeanDefinitions(Element root BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
// 遍历所有子节点,判断是默认标签,bean import alias 就去parseDefaultElement
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// 解析spring的自带标签,自带标签在spring xm<x>l中都有schema定义
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele delegate);
}
else {
// 解析用户自定义的xm<x>l元素
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
解析默认标签
private void parseDefaultElement(Element ele BeanDefinitionParserDelegate delegate) {
// 如果当前子元素是import元素
if (delegate.nodeNameEquals(ele IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 如果当前子元素是alias元素
else if (delegate.nodeNameEquals(ele ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 如果当前子元素是bean元素
else if (delegate.nodeNameEquals(ele BEAN_ELEMENT)) {
processBeanDefinition(ele delegate);
}
// beans元素,递归重新走一遍解析流程
else if (delegate.nodeNameEquals(ele NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
其中解析bean标签为代表最有意义,全部解析bean标签的工作都在BeanDefinitionParserDelegate 类中完成。主要就是通过枚举硬编码获取 Element对象的bean属性。
// 这个方法是解析xm<x>l bean标签,产生beanName,封装beanDefintion对象,然后流程后面会注册bean
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele @Nullable BeanDefinition containingBean) {
// 其实对已经用XAS框架解析了xm<x>l文件变成Element对象来说很容易,就是get(key)一样拿 id name 这些属性
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<>();
// 如果指定了 <bean id='xx' name='abc' /> name属性,将它分割成别名数组,默认用Id做bean的name
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No xm<x>l 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
// 第一次解析xm<x>l封装beanDefiniton时,这里为空
if (containingBean == null) {
// 检查beanName唯一性,保证IOC容器中所有beanName不冲突
checkNameUniqueness(beanName aliases ele);
}
// 重点,将代表bean元素的对象解析成 bean定义,这时已经包含了 id name class等属性
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele beanName containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
// 第一次解析时这里为空
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition this.readerContext.getRegistry() true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name if still possible
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
}
catch (Exception ex) {
error(ex.getMessage() ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition beanName aliasesArray);
}
return null;
}
// 真正解析bean标签,封装defintion的地方
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele String beanName @Nullable BeanDefinition containingBean) {
// 上一层方法生成了beanName
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 得有一个容器装bean属性吧,所以先创建一个 GenericBeanDefinition实例承载各种bean属性
AbstractBeanDefinition bd = createBeanDefinition(className parent);
// 这里就是各种get(key)硬编码的方式获取 element 中的bean属性,包括scope,init-method lazy-init,destory-method等属性
parseBeanDefinitionAttributes(ele beanName containingBean bd);
bd.setDesc<x>ription(DomUtils.getChildElementValueByTagName(ele DEsc<x>riptION_ELEMENT));
parseme<x>taElements(ele bd);
parseLookupOverrideSubElements(ele bd.getMethodOverrides());
parseReplacedMethodSubElements(ele bd.getMethodOverrides());
parseConstructorArgElements(ele bd);
parsePropertyElements(ele bd);
parseQualifierElements(ele bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
... 省略了catch代码块
finally {
this.parseState.pop();
}
return null;
}
GenericBeanDefinition是AbstractBeanDefinition的子类实现,xml bean标签的所有属性都可以在其中找到。执行完BeanDefinitionDelegate.parseBeanDefinitionElement(ele) 方法解析完bean所有属性得到bean定义。回到DefaultBeanDefinitionDocumentReader.processBeanDefinition() 方法中,下一步就是对bean定义的注册。
BeanDefinitionRegistry 相当于Spring配置信息的内存数据库,数据结构是map
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
// 可以看到bean定义将会被注册到 BeanDefinitionRegistry中,用beanName=bean id作为key
registry.registerBeanDefinition(beanName definitionHolder.getBeanDefinition());
// 注册bean的别名集合,可以通过别名找到beanName,再找到bean,相当于二级索引
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName alias);
}
}
}
BeanDefinitionRegistry是一个接口,它有3个实现,其中DefaultListableBeanFactory是xml配置默认的使用类。也就是说DefaultListableBeanFactory是xml配置的默认Bean注册器,bean定义最后被保存到成员变量中
到这里完成了对xml文件的读取成ClasspathResource,用SAX框架转成Document拿到root,在DefaultBeanDefinitionDocumentReader.parseBeanDefinitions() 中遍历所有子元素,它的子元素包括了 <bean/> <import/> <tx:/> 这种默认标签和自定义标签。
解析其中的默认标签和自定义标签。对于默认标签重点看了bean的解析,真正的解析动作在BeanDefinitionParserDelegate.parseBeanDefinitionAttributes()中做的,都是硬编码通过get(key)方式拿到bean定义好的属性。
封装到GenericBeanDefinition对象中,回到DefaultBeanDefinitionDocumentReader.processBeanDefintion() 中,
下一步对bean定义注册到 DefaultListableBeanFactory中(它是BeanDefinitionRegistry)。这一步完成后就回到DefaultBeanDefinitionReader遍历root元素的循环中,遍历下一个子元素进行解析。
OK 这里只是有了bean定义,bean定义是如何被实例化成托管对象,再解决托管对象的依赖注入问题的?
这就是另外的知识了。可以看另一片文章,IOC原理 加载bean