容器的基本用法
我们来先看看bean的定义:
public class MyTestBean {
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
bean并没有任何特别之处,就是一个纯粹的POJO,我们再来看看配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.Springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www. Springframework.
org/schema/beans/Spring-beans.xsd">
<bean id="myTestBean" class="bean.MyTestBean"/>
</beans>
在上面的配置中我们已经看到了bean的声明方式,spring的入门示例就是这样的了,接着我们写代码来测试一下
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
@Test
public void testSimpleLoad(){
BeanFactory bf = new XmlBeanFactory(new ClassPathResource ( "beanFactoryTest.xml"));
MyTestBean bean=(MyTestBean) bf.getBean("myTestBean");
assertEquals("testStr",bean.getTestStr());
}
}
利用xmlBeanFactory解析xml文件,并将bean注入到bean工厂中,这样我们就可以直接使用getBean()方法来获取到具体的bean实例了;下面我们将分析,spring是如何解析xml,并将对应的实例注入到bean工厂里的。
功能分析
有经验的读者可以很清晰的分析到,无非就一下几点
- 读取配置文件beanFactoryTest.xml
- 根据配置文件beanFactoryTest.xml中的配置找到对应的类的配置,并实例化
- 调用实例化后的实例
作为一个世界级的优秀作品,里面的实现逻辑肯定没有这么简单,以上只是一个简单的开场,让大家了解一下spring的大致面貌,下面我们将深入去分析org.springframework.beans.jar 这个包下面的具体实现。
为避免篇幅过长,本片只阐述解析xml的过程及注册beanDefinitions的过程。
核心类介绍
先看一下整体的结构,主要是是集中的factory下面:
下面来介绍一下最核心的两个类,先大致了解一下其作用,然后我们再去分析
1. DefaultListableBeanFactory
xmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是spring注册及加载bean的默认实现。XmlBeanFactory使用了使用了自动以的Xml自定义读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory以及BeanDefinitionRegistry接口。
先来看看DefaultListableBeanFactory的UML图:
下面我们来看一下每个类具体的作用:
- AliasRegistry:定义对alias简单的增删改查
- SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
- SingletonBeanRegistry:定义对单例的注册以及获取
- BeanFactory:定义获取bean以及bean的各种属性
- BeanDefinitionRegistry:定义beanDefinition的各种增删改查操作
- DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry的各种函数实现
- HierachicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能至上增加了对parentFactory的支持
- FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加对FactoryBean的特殊处理功能
- ConfigurableBeanFactory:提供配置Factory的各种方法
- ListableBeanFactory:根据各种条件获取bean的配置清单
- AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能
- AutowireCapableBeanFactory:提供创建bean、自动注入、初始化已经应用bean的后置处理器
- AbstractAutowireCapableBeanFactory:综合对AbstractBeanFactory并对AutowireCapableBeanFactory进行实现
- ConfigurableListableBeanFactory:BeanFactory的配置清单,指定合理类型及接口等
- DefaultListableBeanFactory:综合上面所有的功能,主要对Bean注册后的处理
XmlBeanFactory对DefaultListableBeanFactory进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用父类继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性,看代码
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* Create a new XmlBeanFactory with the given resource,
* which must be parsable using DOM.
* @param resource XML resource to load bean definitions from
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
* @param resource XML resource to load bean definitions from
* @param parentBeanFactory parent bean factory
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
分析这么一大堆类的功能,主要还是为了引出今天的主角XmlBeanDefinitionReader
2. XmlBeanDefinitionReader
XML配置文件的读取是Spring中最重要的功能,因为Spring大部分功能都是基于配置切入的,那么我们基于XmlBeanDefinitionReader来梳理一下资源文件的读取解析及注册的大致脉络,同样,需要先来看UML图以及各个类的功能。
- BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能
- EnvironmentCapable:定于获取Environment的方法
- AbstractBeanDefinitionReader:对EnvironmentCapable和BeanDefinitionReader定义功能的实现
- ResourceLoader:定义资源加载器,主要用于根据给定的资源文件地址返回对应的Resource
- DocumentLoader:定义从资源文件加载到转为Document的功能
- BeanDefinitionParserDelegate:定义document中各种Element的解析方法
下面三个类是XmlBeanDefinitionReader使用到的类,并不在继承和实现结构里面,所以我用其它颜色标出。那XmlBeanDefinitionReader主要包含以下几个处理步骤:
- 通过继承自AbstractBeanDefinitionReader重的方法,来使用ResourceLoader将资源文件转为对应的Resource文件
- 通过DocumentLoader对资源文件进行转换,将Resource文件转换为Document文件
- 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析
容器的基础XmlBeanFactory
到这里我们一家对Spring容器有一个基本的了解了,尽管你会感到很模糊,但是不要紧,我们来逐步分析一下
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"))
这一行的代码逻辑时先使用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续就可以使用Resource提供的各种服务来操作了,然后就可以进行XmlBeanFactory的初始化了。关于如何从一个文件获取Resource资源文件,这里暂不详细描述。一句话概述:在java中,不同来源的资源可以抽象成URL,通过注册不同的处理器来处理不同来源的资源的读取逻辑,而Spring对其内部使用的资源实现了自己的抽象结构,并使用Resource接口来封装底层资源。
加载Bean
之前我们提到的XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader的loadBeanDefinitions方法,
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
这句代码时整个切入点的核心,我们一同来分析一下,先来看一下这个方法loadBeanDefinitions(resource)的时序图
上图不需要看懂,只是为了让你看看什么叫做山路十八弯,从上面的时序图,我们了解到
- 封装资源文件,当进入到XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装
- 获取输入流,从Resource中获取对应的InputStream并构造InputSource
- 通过构造函数的InputSource和Resource实例继续调用函数doLoadBeanDefinitions。
真正核心的部分在于doLoadBeanDefinitions方法,部分代码如下
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
我们一起来看这个方法做了啥
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 解析获取到文档
Document doc = doLoadDocument(inputSource, resource);
// 注册BeanDefinition
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
不考虑异常catch的话,上面代码只做了2件事,每件都是必不可少
- 加载XML文件,并得到对应的Document
- 根据返回的document注册Bean信息
解析及注册BeanDefinition
继续上面的分析,当获取到了XML文件的Document实例对象之后,就会引入registerBeanDefinitions(doc, resource)方法,一起来分析一下
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//统计之前的BeanDefinition的个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 加载及注册Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
进入到createBeanDefinitionDocumentReader方法中,发现真正的实例化类型是DefaultBeanDefinitionDocumentReader,进入这个类后,发现registerBeanDefinitions这个方法主要就是提取root,以便于以后再将root作为参数继续BeanDefinition的注册。
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
经历艰难险阻,我们终于到了核心逻辑底层doRegisterBeanDefinitions方法,我们期待的地方才刚刚开始
protected void doRegisterBeanDefinitions(Element root) {
// 专门解析处理
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("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;
}
通过看到上面,大家可能会疑惑,什么是profile属性呢?
简单解释一下:通过profile标记不同的环境,可以通过设置spring.profiles.active和spring.profiles.default激活指定profile环境。如果设置了active,default便失去了作用。如果两个都没有设置,那么带有profiles的bean都不会生成。
profile其实就是我们日常开发环境下面的各种配置,dev、test、prod,这样区分开了之后,比如数据库就能按照不同的环境来配置不同的链接。
处理完了profile就可以进行XML的读取了,请看parseBeanDefinitions的实现
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 对beans的处理
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)) {
// 对bean的处理
parseDefaultElement(ele, delegate);
}
else {
// 对bean的处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 自定义的bean解析
delegate.parseCustomElement(root);
}
}
上面的代码逻辑很清晰,因为在Spring的XML配置中有两大类Bean声明,一个是默认的,如
<bean id="test" class="test.TestBean"/>
另一个就是自定义的,如
<tx:annotation-driven>
具体判定方法是获取命名空间namespace后与spring中固定的命名空间进行对比,感兴趣可以自行了解。
再看parseDefaultElement方法
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);
}
// 对bean标签的处理
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
这里是具体对XML中标签的处理,通过解析bean标签,来实现bean的注册。但是bean标签的解析也是最复杂的,下面我们进入函数processBeanDefinition
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 委托BeanDefinitionParserDelegate的parseBeanDefinitionElement方法进行元素的解析,经过这个方法后
// bdHolder实例已经包含了我们配置文件中的各种属性了,例如class、name、id、alias等
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 若bdHolder != null的情况下,如存在默认标签的子节点下再有自定义属性,还需再次对自定义标签的解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
// 解析完成后,委托BeanDefinitionReaderUtils进行bean的注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
// 发出响应事件,通知相关监听器,这个bean已经完成了加载
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
大致逻辑总结一下:
- 委托BeanDefinitionParserDelegate的parseBeanDefinitionElement方法进行元素的解析,经过这个方法后, bdHolder实例已经包含了我们配置文件中的各种属性了,例如class、name、id、alias等
- 若bdHolder != null的情况下,如存在默认标签的子节点下再有自定义属性,还需再次对自定义标签的解析
- bdHolder解析完成后,委托BeanDefinitionReaderUtils进行bean的注册
- 最后发出响应事件,通知相关监听器,这个bean已经完成了加载
这个bean的加载过程已经完成了,比较重要的方法parseBeanDefinitionElement,这个方法就像剥洋葱一样,一层一层的将xml的标签解析,并将数据全部生成到BeanDefinitionHolder中, 然后使用BeanDefinitionReaderUtils将其注册到BeanDefinitionRegistry中,来看代码
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
// 使用beanName做唯一的标识
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
// 如果有别名的话,注册所有的别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
到这里,bean已经注册到BeanDefinitionRegistry中了,注册bean 的方式也有两种,第一是通过beanName注册registerBeanDefinition
,第二是通过别名注册registerAlias。
1. 通过beanName注册BeanDefinition
对于BeanDefinition的注册,这里很多人都会认为,就是将BeanDefinition放入map中,并将BeanName作为key。确实,Spring就是这样做的,只不过除此之外,它还做了一些其他事情,主要代码如下
// 进行合法性的校验
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 对bean的一些异常情况的处理
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
}
// 加入到map缓存
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
// 清除加载过程中留下的缓存
clearByTypeCache();
}
总结一下流程
- 对AbstractBeanDefinition的校验
- 对Bean已经注册的情况进行处理,如果设置了不允许bean的覆盖,则抛出异常,否则直接覆盖
- 加入map缓存
- 清除之前留下的BeanName的缓存
2. 通过别名注册BeanDefinition
理解了以上的逻辑,再看这个流程就比较简单了,
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized (this.aliasMap) {
if (alias.equals(name)) {
this.aliasMap.remove(alias);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
}
else {
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
if (logger.isInfoEnabled()) {
logger.info("Overriding alias '" + alias + "' definition for registered name '" +
registeredName + "' with new target name '" + name + "'");
}
}
checkForAliasCircle(name, alias);
this.aliasMap.put(alias, name);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");
}
}
}
}
流程如下:
- alias与beanName相同情况下,则不需要处理直接删除掉原有的alias
- alias覆盖处理,若aliasName已经使用并指向另一个beanName则需要用户的设置进行处理
- alias循环检查,当A->B存在时,若再次出现A->C->B则会抛出异常
- 注册alias
以上就是整个bean加载的过程了,其实早期使用Spring的时候还是能很清楚的看到XML文件的配置,随着Spring的迅速发展,在越来越傻瓜式的简单操作下,sprign封装了大量的逻辑在里面,当然也导致我们对底层的细节越来越陌生。如果你想了解更多关于spring的原生知识的话,欢迎留言或者私信。本期内容就这些,下期我们来讲bean的加载。
觉得可以辛苦一键三连,看客老爷万福金安!!!