• 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解析。

  • 相关阅读:
    @loj
    @loj
    @划水记@ THUWC2020 (?)
    @codeforces
    @loj
    Spark设计理念与基本架构
    Spark源码解析
    Spark Submitting Applications浅析
    Spark RDD基本概念与基本用法
    Storm基本原理概念及基本使用
  • 原文地址:https://www.cnblogs.com/muuu520/p/14434281.html
Copyright © 2020-2023  润新知