Spring源码阅读:容器的基本实现

1 容器的基础 XmlBeanFactory

我们来看一个Spring的示例代码:

public class BeanFactoryTest {
	public void testSimpleLoad() {
		BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
		MyTestBean bean = (MyTestBean)bf.getBean("myTestBean");
		assertEquals("testStr", bean.getTestStr());
	}
}

接下来,我们深入分析一下以下功能的代码实现:

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

以下是XmlBeanFactory的初始化时序图:

时序图从BeanFactoryTest测试类开始,首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了。

1.1 配置文件封装

Spring对其内部用到的资源实现了自己的抽象结构:Resource接口封装底层资源。

public interface Resource extends InputStreamSource {
	boolean exists();
    
	default boolean isReadable() {
		return exists();
	}
    
	default boolean isOpen() {
		return false;
	}

	default boolean isFile() {
		return false;
	}

	URL getURL() throws IOException;

	URI getURI() throws IOException;

	File getFile() throws IOException;

	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	long contentLength() throws IOException;

	long lastModified() throws IOException;

	Resource createRelative(String relativePath) throws IOException;

	@Nullable
	String getFilename();

	String getDescription();

}
  • Resource接口定义了3个判断当前资源的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。
  • Resource接口提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名的方法。
  • 为了方便操作,Resource接口还提供了基于当前资源创建一个相对资源的方法:createRelative()
  • 在错误处理中需要详细地打印出错的资源文件,因而Resource还提供了getDescription()方法用来在错误处理中打印信息。
  • 对于不同来源的资源文件都有相应的Resource实现,文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。

当通过Resource相关类完成了对配置文件进行封装后,配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理。在XmlBeanFactory的初始化过程中,使用的是Resource实例作为构造方法参数的办法,代码如下:

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    
	public XmlBeanFactory(Resource resource) throws BeansException {
        // 调用构造方法
		this(resource, null);
	}

	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
        // 使用XmlBeanDefinitionReader进行资源加载
		this.reader.loadBeanDefinitions(resource);
	}

}

在使用XmlBeanDefinitionReader解析Resource资源之前,有一个调用父类构造方法的过程super(parentBeanFactory),跟踪代码到父类。

public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
		if (IN_NATIVE_IMAGE) {
			this.instantiationStrategy = new SimpleInstantiationStrategy();
		}
		else {
			this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
		}

ignoreDependencyInterface方法的作用是忽略给定接口setter方法的自动装配(byName、byType...)。详情请见https://blog.csdn.net/qq_36951116/article/details/99587519 。另外有个类似的方法是ignoreDependencyType,它的作用是忽略给定类型的自动装配。

1.2 加载Bean

XmlBeanFactory的构造方法中,this.reader.loadBeanDefinition(resource)是整个资源加载的切入点。这个方法的处理过程如下:

  1. 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装。
  2. 获取输入流。从Resource中获取对应的InputStream并构造InputSource
  3. 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinition

2 获取XML的验证模式

在1.2节最后提到的loadBeanDefinition一共做了三件事。

  • 获取对XML文件的验证模式。
  • 加载XML文件,并得到对应的Document
  • 根据返回的Document注册Bean信息。
    我们首先来看第一件事:获取XML的验证模式。XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。
    DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确,使用DTD验证模式的XML文件必然会包含<!DOCTYPE>这一行。
    XSD本身是一种XML文档,XSD文档设计者可以通过XML Schema指定XML文档所允许的结构和内容,并据此检查文档是否是有效的。

2.1 验证模式的获取

Spring通过XmlBeanDefinitionReader中的getValidationModeForResource方法来获取对应资源的验证模式。

protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    if (validationModeToUse != VALIDATION_AUTO) {
        // 如果指定了验证模式,则使用指定的验证模式
        return validationModeToUse;
    }
    // 如果没指定验证模式,则自动检测
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    return VALIDATION_XSD;
}

2.2 获取Document

经过了验证模式准备的步骤就可以进行Document加载了,同样XmlBeanFactoryReader类对于文档读取没有亲力亲为,而是委托给了DocumentLoader去执行。这里DocumentLoader是一个接口,真正的执行类是DefaultDocumentLoader

// DefaultDocumentLoader.java
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}

与大多数通过SAX解析XML文档的套路差不多,Spring先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。loadDocument方法涉及一个参数EntityResolver,它的作用是项目本身可以提供一个寻找DTD声明的方法,比如我们将DTD文件放在项目某处,在实现时直接将此文档读取并返回给SAX即可,这样就避免了通过网络来寻找相应的声明。

3 解析及注册BeanDefinitions

把XML文件转化为Document对象后,程序就会被引入这个方法:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 加载及注册Bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

createBeanDefinitionDocumentReader()方法会返回一个DefaultBeanDefinitionDocumentReader对象,用于加载及注册Bean。进入DefaultBeanDefinitionDocumentReader后,发现这个方法先提取了root,然后将root作为参数继续BeanDefnition的注册。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}

doRegisterBeanDefinitions是真正开始解析XML的地方,它的作用是从给定的root参数开始,注册每个BeanDefinition。其代码如下:

protected void doRegisterBeanDefinitions(Element root) {
    // 专门处理解析
    BeanDefinitionParserDelegate parent = this.delegate;
	this.delegate = createDelegate(getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        // PROFILE_ATTRIBUTE = "profile"
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            // 处理profile属性
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }

    // 解析前处理,留给子类实现
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    // 解析后处理,留给子类实现
    postProcessXml(root);

    this.delegate = parent;
}

首先对profile属性进行处理,然后preProcessXmlpostProcessXml方法的实现都是空的,这里使用的是模板方法,留给子类实现。最后我们来看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)) {
                    // 如果是默认命名空间
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 如果是自定义命名空间
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

在Spring的XML配置里有两大类Bean声明,一个是默认的,如

<bean id="test" class="test.TestBean"/>

另一类是自定义的,如:

<tx:annotation-driven/>

而两种方式的读取及解析差别是非常大的,如果采用Spring默认配置,Spring知道该怎么做,如果是自定义的,就需要用户实现一些接口及配置。对于根节点或者子节点,该方法会使用node.getNamespaceURI()获取命名空间并与Spring中固定的命名空间http://www.springframework/org/schema/beans 进行比对,如果一致则认为是默认,使用parseDefaultElement解析,否则就认为是自定义,使用delegate.parseCustomElement解析。

原文地址:https://www.cnblogs.com/muuu520/p/14434281.html