Spring IoC容器的实现之一(BeanDefinition的加载与解析)
本文所用源码为spring-framework-5.1.2,点我直达,分支为v5.1.2.RELEASE_DYX
根容器 BeanFactory
-
位于spring-beans组件中的org.springframework.beans.factory包中
package org.springframework.beans.factory; import org.springframework.beans.BeansException; public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); boolean containsBean(String name); boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); } -
BeanFactory描述了整个IoC容器范围内的规范和原则,在注释上说明了BeanFactory的实现应该满足哪些契约或者规范,比如应该提供Bean与Bean之间的引用关系、生命周期方法、Bean的获得方式等
BeanFactory与FactoryBean详解
-
BeanFactory用于创建并管理对象,他是一个工厂;FactoryBean指Factory这个Bean本身,而它是一个特殊的Bean,其中有个getObject()方法,如果一个类实现了这个接口,那个这个类的实例就作为一个工厂,它的getObject方法返回的是工厂所创建的对象,而不是直接返回“xx工厂”这个实例(xxxFactoryBean)本身,假如对象A实现了FactoryBean这个接口,并在xml文件中配置,则通过getBean("A")方法返回的是A工厂所创建的对象,而不是A工厂这个实例本身,如果要返回A工厂这个实例本身呢,使用getBean("&A")方法
<bean id="person1" class="com.dyx.pojo.PersonFactoryBean"></bean>public class PersonFactoryBean implements FactoryBean<Person> { @Override public Person getObject() throws Exception { return new Person("test1","test2","test3"); } @Override public Class<?> getObjectType() { return Person.class; } @Override public boolean isSingleton() { return false; } }Object person1 = beanFactory.getBean("person1"); System.out.println(person1); Object person2 = beanFactory.getBean("&person1"); System.out.println(person2); -
FactoryBean是实现AOP的一个重要的基石,FactoryBean也是属于BeanFactory的管辖范围之内的
-
BeanFactory与FactoryBean的区别:
- BeanFactory是整个Spring框架中的根容器,它定义了IOC容器的最基本形式,并提供了IOC容器应遵守的的最基本的接口,也就是Spring IOC所遵守的最底层和最基本的编程规范。
- 在Spring代码中,BeanFactory只是个接口,并不是IOC容器的具体实现, 但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,都是附加了某种功能的实现。
- FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它本身也受BeanFactory进行管理
- 以XxxFactoryBean结尾,表示它是一个工厂Bean,但这个Bean不是简单的Bean,不同于普通Bean的是:它是实现了FactoryBean接口的Bean,根据该Bean的ID从BeanFactory中获取(getBean方法)的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。所以在BeanFactory中定义了
FACTORY_BEAN_PREFIX这个变量
DefaultListableBeanFactory及资源载入
- DefaultListableBeanFactory是BeanFactory的核心实现类,该类有个子类XmlBeanFactory,用于从xml文件中读取配置信息(完成资源的载入)并构建装配对象,但从Spring3.1开始,该类已经废弃,Spring官方推荐使用DefaultListableBeanFactory与XmlBeanDefinitionReader配合使用
- Spring将不同的资源文件抽象成为不同的Resource对象,使得后续处理只需要面对Resource,Resource表示资源的接口,不同来源的资源文件具有不同的接口实现
- XmlBeanDefinitionReader作为资源的读取器
ClassPathResource resource = new ClassPathResource("applicationContext.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource); - Spring加载资源并装配对象的过程
- 定义好Spring的配置文件,以xml配置文件为例
- 通过Resource对象将Spring配置文件进行抽象,抽象成为一个Resource对象
- 定义好Bean工厂(各种BeanFactory)
- 定义好XmlBeanDefinitionReader对象,并将工厂作为参数传递进去供后续回调使用
- 通过XmlBeanDefinitionReader对象读取(加载)之前抽象出的Resource对象,此过程包含XML文件的解析
- 本质上,Xml文件的解析是由XmlBeanDefinitionReader对象交由BeanDefinitionParserDelegate对象委托来完成的,实质上这里使用了委托模式
- IoC容器创建完毕,用户可以通过容器获取到所需的对象信息
XmlBeanDefinitionReader与Resource
ClassPathResource resource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
-
在DefaultListableBeanFactory中有个很重要的Map对象:BeanDefinitionMap对象,里面用于存储Bean的相关信息,这些信息使用BeanDefinition对象进行抽象,在BeanDefinitionMap对象中,Bean的名字作为Key,value为BeanDefinition
-
ClassPathResource用于从类路径下加载配置文件,该类含有三个成员变量path表示资源文件的存放路径,classLoader表示类加载器,clazz表示类(字节码)对象: new ClassPathResource("applicationContext.xml");
-
XmlBeanDefinitionReader的loadBeanDefinitions(resource) 方法用于加载资源文件,并解析相关引用信息,并装配:
-
上文中的
reader.loadBeanDefinitions(resource)又调用了loadBeanDefinitions(new EncodedResource(resource)),表示从指定的xml文件中加载BeanDefinition并返回所加载BeanDefinition的数量。 -
在
loadBeanDefinitions(EncodedResource encodedResource)方法中,实际上真正加载、解析、装配的方法是doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法,inputSource是resource中所含有的InputStream对象,该InputStream是一个含有xml配置文件的输入流对象,Resource接口又继承了InputStreamResource接口,encodedResource是classPathResource的包装对象,里面配置了编码信息InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 这个方法是真正完成从指定的xml文件中加载bean definition的 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } -
首先将xml配置文件解析成一个Document对象,这里使用的是W3C的sax解析机制
try { // document代表的是xml文档中的根对象 Document doc = doLoadDocument(inputSource, resource); // 注册BeanDefinition,并返回注册的个数 int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } ...而在doLoadDocument方法中:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { // documentLoader是一个用于加载xml中document的加载器, // 这里借用w3c的sax解析机制来获取xml中的document,Spring本身没有做任何解析xml的操作 return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); } -
调用registerBeanDefinitions(document,resource)方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 获取以前已经存在,即已经注册好的bean definition的数量, // 实际上就是org.springframework.beans.factory.support.DefaultListableBeanFactory中的beanDefinitionMap的size int countBefore = getRegistry().getBeanDefinitionCount(); // 真正的注册逻辑,很关键 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 记录本次加载的 BeanDefinition 个数 return getRegistry().getBeanDefinitionCount() - countBefore; } -
调用createBeanDefinitionDocumentReader()方法,创建一个BeanDefinitionDocumentReader接口对象,是解析BeanDefinition的文档读取器,他是一个接口,且只有一个实现:DefaultBeanDefinitionDocumentReader类,它是BeanDefinitionDocumentReader的默认实现,这个类会根据在spring-beans中的DTD与XSD中对bean的定义来读取相应的信息。
-
使用documentReader调用registerBeanDefinitions(doc, createReaderContext(resource))方法,实际上是调用DefaultBeanDefinitionDocumentReader对象的方法实现,而createReaderContext(resource)方法返回的是XmlReaderContext对象,是XmlBeanDefinitionReader的一个增强插件,里面封装了Resource、ProblemReporter、ReaderEventListener、SourceExtractor、XmlBeanDefinitionReader、NamespaceHandlerResolver等对象实例
// 打开document,并在<beans></beans>层级上初始化默认设置,随后解析其内的bean的定义 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; // doc.getDocumentElement()实际上就是xml文档的根元素root,这个方法的重要目的之一就是提取root,以便于再次将 root 作为参数继续 BeanDefinition 的注册 doRegisterBeanDefinitions(doc.getDocumentElement()); } -
在DefaultBeanDefinitionDocumentReader对象中,包含有XmlReaderContext与BeanDefinitionParserDelegate(这个类是解析xml的关键类)成员变量,上一步的方法调用将XmlReaderContext设置进来,并调用doRegisterBeanDefinitions(root)方法;在doRegisterBeanDefinitions方法中实际上调用preProcessXml(root)、parseBeanDefinitions(root, this.delegate)、postProcessXml(root)方法来真正地解析Document文档对象,this.delegate是BeanDefinitionParserDelegate委托对象,其中preProcessXml()和postProcessXml()默认是空方法,我们可以通过继承该DefaultBeanDefinitionDocumentReader类,并重写上述两个方法,自定义对Element文档对象的前置及后置处理,实现对xml文件的自定义扩展,这里体现了“模板方法”设计模式
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper(readerContext, root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; } -
实际上解析xml文件的是BeanDefinitionParserDelegate对象,它是一个委托对象,在BeanDefinitionParserDelegate类中,定义了在xml配置文件中可以使用的全部属性的值,在DefaultBeanDefinitionDocumentReader类的createHelper方法中构建委托对象并返回供后续使用,里面的initDefaults方法会初始化文档的一些默认配置
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) { BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment); delegate.initDefaults(root, parentDelegate); return delegate; } -
BeanDefinitionParserDelegate对象解析资源文档,并装配生成BeanDefinition,调用parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法
-
BeanDefinitionParserDelegate与资源解析
-
BeanDefinitionParserDelegate的字符串常量中封装了xml文件中常见的属性配置,如name、bean、id、class、lazy-init等
-
当文档中含有beans标签的嵌套时,采用递归回滚的方式进行解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createHelper(readerContext, root, parent);
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
-
在parseBeanDefinitions()方法中,判断当前解析元素是否属于默认的命名空间,如果是的话,就调用parseDefaultElement()方法,否则调用delegate上parseCustomElement()方法,只有http://www.springframework.org/schema/beans 会被认为是默认的命名空间。也就是说,beans、bean这些元素,会认为属于默认的命名空间,而像task:scheduled这些,就认为不属于默认命名空间。根节点beans的一个子节点bean,是属于默认命名空间的,所以会进入parseDefaultElement()方法
-
这里可能会有4种情况,import、alias、bean、beans,分别有一个方法与之对应,如果解析的是bean元素,所以会进入processBeanDefinition()方法,如果解析的是beans元素,则认定为嵌套元素,再递归解析
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } } -
这里主要有3个步骤,先是委托delegate对bean进行解析,然后委托delegate对bean进行装饰,最后由一个工具类来完成BeanDefinition的注册
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } } -
从设计原则上看,XmlBeanDefinitionReader负责资源文件的加载读取,而BeanDefinitionParserDelegate负责真正的解析工作,体现了单一职责原则
BeanDefinitionParserDelegate深入详解
- delegate.parseBeanDefinitionElement(ele)方法返回一个BeanDefinitionHolder对象,其内包含一个BeanDefinition beanDefinition、String beanName、String[] aliases的引用
- 对BeanDefinitionHolder对象进行装饰
- 调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());实际上是调用DefaultListableBeanFactory的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法,将BeanName和BeanDefinition存入BeanDefinitionMap中
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { // ... this.beanDefinitionMap.put(beanName, beanDefinition); // ... }
Spring Bean的创建与获取
-
上述过程并不涉及Bean的创建,只是处理XML的解析、BeanDefinition的注册,调用getBean方法,实际上调用的是AbstractBeanFactory类中的doGetBean方法
public <T> T getBean(String name, Class<T> requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); } -
Spring的Bean实际上是缓存在DefaultSingletonBeanRegistry类的众多HashMap中,其中之一是singletonObjects
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64); -
在创建Bean之前,首先需要将该Bean的创建标识设定好,表示该Bean已经或者是即将被创建,为的是增强缓存的效率
-
根据Bean的scope属性来确定是singleton或者是prototype,然后创建相应的Bean,调用getSingleton(String beanName, ObjectFactory singletonFactory)方法,参数中ObjectFactory接口的意义与FactoryBean类似,都是获取对象的接口,getObject()方法,在getSingleton()方法中使用匿名内部类调用createBean()方法,获得实例
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } } }); -
实际创建Bean的过程位于AbstractAutowireCapableBeanFactory类的doCreateBean方法中,通过反射来创建Bean的实例,在创建之前首先检查访问修饰符,如果不是public的,则调用setAccessible(true)来突破Java的语法限制,使得可以通过如私有的构造方法来创建实例,并且在创建时会检查依赖,如果有依赖,会先创建被依赖的对象实例
-
接下来,寻找Bean的属性值,完成属性的注入,instantiateBean()方法等,
-
将所创建的singleton实例添加到上述第二步的singletonObjects缓存中,供下次直接使用
IoC容器中的缓存
| 其中的几个重要的缓存 | 用途 |
|---|---|
| singletonObjects | 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 |
| earlySingletonObjects | 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 |
| singletonFactories | 存放 bean 工厂对象,用于解决循环依赖 |
| singletonsCurrentlyInCreation | 当前正在创建的bean的名称 |
-
解析并注册BeanDefinination时的大多数缓存位于DefaultSingletonBeanRegistry类当中。如果Bean是单例的,实例化后会将Bean存入singletonObjects缓存中,如果是prototype的,则不会存入缓存。
-
实例化Bean的时候,先从singletonObjects查找缓存,如果命中就可以直接返回,未命中的话先把Bean放入singletonsCurrentlyInCreation,说明自己正在创建中。
-
具体开始实例化。完成后,把beanName和对应的bean工厂放入singletonFactories。
-
依赖注入,当有循环依赖的时候,重复第1个步骤。
-
还是从singletonObjects查找缓存,虽然还是未命中,但是发现bean正在创建中了。
-
然后从singletonFactories中获取bean的工厂对象,拿到该Bean的对象。然后把这个Bean提前曝光,放入earlySingletonObjects。
-
注入完成,循环依赖问题解决。
ApplicationContext
-
ApplicationContext接口继承自ListableBeanFactory、HierarchicalBeanFactory接口,能够包含 BeanFactory的所有基本功能,并提供了更多的扩展功能。
-
在ApplicationContext的其中一个实现类中XmlWebApplicationContext中,该类中的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)与BeanFactory类的核心实现---------DefaultListableBeanFactory的子类加载资源并解析过程一致
-
XmlWebApplicationContext中指定了配置文件的默认为/WEB-INF/applicationContext.xml,如果不使用或者没有指定配置文件的位置,则会加载默认位置的配置文件
/** Default config location for the root context */ public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
-
-
开发中常用ClassPathXmlApplicationContext类