暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring之如何解析BeanFactory中的bean(上)

蹲厕所的熊 2018-08-11
333

蹲厕所的熊 转载请注明原创出处,谢谢!

容器的基本实现

作为各大公司的主流框架Spring,它提供了一系列核心的功能,依赖注入则是这些功能中最为重要的。而注入必须要先解析并管理众多的Bean,本文将通过BeanFactory的实现类XmlBeanFactory来一步步的带你了解bean是如何被解析的。

基本用法

任何一个类都可以作为一个bean来被Spring管理,我们先定义一个类: MyTestBean

  1. public class MyTestBean {

  2.    private String testStr = "testStr";


  3.    public String getTestStr() {

  4.        return testStr;

  5.    }


  6.    public void setTestStr(String testStr) {

  7.        this.testStr = testStr;

  8.    }

  9. }

复制

接着,把它变成一个由Spring管理的bean:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">


  5.    <bean id="myTestBean" class="com.github.MyTestBean" />

  6. </beans>

复制

在上面的配置中我们看到了bean的声明方式。接着,我们来尝试获取该bean:

  1. public class XmlBeanFactoryTest {


  2.    public static void main(String[] args) {

  3.        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("com/github/spring-bean.xml"));

  4.        System.out.println(beanFactory.getBean("myTestBean"));

  5.    }

  6. }

复制

控制台的结果表明MyTestBean已经被Spring所管理,默认它是以单例的形式存在的。

结构图

XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是XmlBeanFactory的继承关系结构图:

简单说一下图中各个类的作用:

  • AliasRegistry:定义对alias的简单增删改等操作。

  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。

  • BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作。

  • BeanFactory:定义获取bean及bean的各种属性。

  • HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。

  • ListableBeanFactory:根据各种条件获取bean的配置清单。

  • ConfigurableBeanFactory:根据配置Factory的各种方法。

  • SingletonBeanRegistry:定义对单例的注册及获取。

  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。

  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。

  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。

  • AutowireCapableBeanFactory:提供创建bean,自动注入,初始化以及应用bean的后处理器。

  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现。

  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。

  • DefaultListableBeanFactory:综合上面所有功能,主要是对Bean注册后的处理。

XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。

源码分析

好了,我们通过XmlBeanFactory的源码一步步揭开它的神秘面纱吧~

注:因异常处理和日志打印的代码比较多,下面会把所有的异常处理和日志打印的代码忽略,只保留核心的处理流程。

XmlBeanFactory

点开XmlBeanFactory的源码,映入眼帘的只有它的两个构造方法和一个reader属性:

  1. public class XmlBeanFactory extends DefaultListableBeanFactory {


  2.    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);


  3.    public XmlBeanFactory(Resource resource) throws BeansException {

  4.        this(resource, null);

  5.    }


  6.    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {

  7.        super(parentBeanFactory);

  8.        // 通过reader加载bean

  9.        this.reader.loadBeanDefinitions(resource);

  10.    }

  11. }

复制

在Spring中,所有的资源都是通过Resource来定义的,它通过一系列的方法描述了资源,并且它还能够获取到资源的输入流,因为之前详细的说过 Spring的资源管理,这里就不赘述了。

构造函数接把收到的Resource(我们定义bean的spring配置文件)交给reader去处理。可以试着猜想一下loadBeanDefinitions要做的事:

  • 读取Resource文件并找到一个可以解析该文件的类。

  • 解析文件里的bean标签,并把它转化成一个真正的Bean类。

  • 实例化Bean并把它放入Spring的上下文中,供之后使用。

虽然大体的思路是这样,但绝不是仅仅这么简单的过程,我们继续往下进入XmlBeanDefinitionReader类。

XmlBeanDefinitionReader

  1. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

  2.    // 把Resource用EncodedResource包装,这里因为没有指定编码所以和Resource没区别

  3.    return loadBeanDefinitions(new EncodedResource(resource));

  4. }


  5. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

  6.    // threadLocal记录已经加载的资源

  7.    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

  8.    if (currentResources == null) {

  9.        currentResources = new HashSet<EncodedResource>(4);

  10.        this.resourcesCurrentlyBeingLoaded.set(currentResources);

  11.    }

  12.    // 把encodedResource放入set集合中

  13.    if (!currentResources.add(encodedResource)) {

  14.        throw new BeanDefinitionStoreException(

  15.                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");

  16.    }

  17.    try {

  18.        InputStream inputStream = encodedResource.getResource().getInputStream();

  19.        try {

  20.            // InputSource这个类是org.xml.sax包下的,用来解析xml文件使用

  21.            InputSource inputSource = new InputSource(inputStream);

  22.            if (encodedResource.getEncoding() != null) {

  23.                inputSource.setEncoding(encodedResource.getEncoding());

  24.            }

  25.            // 核心加载逻辑

  26.            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

  27.        }

  28.        finally {

  29.            inputStream.close();

  30.        }

  31.    } catch (IOException ex) {

  32.        throw new BeanDefinitionStoreException(

  33.                "IOException parsing XML document from " + encodedResource.getResource(), ex);

  34.    } finally {

  35.        // 资源清理

  36.        currentResources.remove(encodedResource);

  37.        if (currentResources.isEmpty()) {

  38.            this.resourcesCurrentlyBeingLoaded.remove();

  39.        }

  40.    }

  41. }

复制

接着来看核心的加载逻辑:

  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)

  2.        throws BeanDefinitionStoreException {

  3.    // org.w3c.dom.Document

  4.    Document doc = doLoadDocument(inputSource, resource);

  5.    return registerBeanDefinitions(doc, resource);

  6. }


  7. protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {

  8.    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,

  9.            getValidationModeForResource(resource), isNamespaceAware());

  10. }

复制

doLoadDocument会资源解析成w3c规范的Document类,之后进行真正的解析以及注册。

验证模式的获取

在doLoadDocument中会通过getValidationModeForResource方法获取到XML文件的验证模式:

  1. protected int getValidationModeForResource(Resource resource) {

  2.    int validationModeToUse = getValidationMode();

  3.    if (validationModeToUse != VALIDATION_AUTO) {

  4.        return validationModeToUse;

  5.    }

  6.    // 如果未指定则使用自动检测

  7.    int detectedMode = detectValidationMode(resource);

  8.    if (detectedMode != VALIDATION_AUTO) {

  9.        return detectedMode;

  10.    }

  11.    return VALIDATION_XSD;

  12. }

复制

有兴趣的同学可以先去了解一下DTD和XSD的区别,然后再来分析这一段代码就非常容易理解了。方法的实现还是很简单的,无非是如果设定了验证模式则优先使用指定的验证模式,否则使用自动检测的方式。而自动检测验证模式的功能是在函数detectValidationMode方法中实现的。

  1. protected int detectValidationMode(Resource resource) {

  2.    // 使用XML校验模式探测器来检查resource是哪种类型的XML

  3.    return this.validationModeDetector.detectValidationMode(resource.getInputStream());

  4. }


  5. public int detectValidationMode(InputStream inputStream) throws IOException {

  6.    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

  7.    try {

  8.        boolean isDtdValidated = false;

  9.        String content;

  10.        while ((content = reader.readLine()) != null) {

  11.            content = consumeCommentTokens(content);

  12.            // 如果读取的行是空或者是注释则略过

  13.            if (this.inComment || !StringUtils.hasText(content)) {

  14.                continue;

  15.            }

  16.            if (hasDoctype(content)) {

  17.                isDtdValidated = true;

  18.                break;

  19.            }

  20.            // 读取到<开始符号,验证模式一定会在开始符号之前

  21.            if (hasOpeningTag(content)) {

  22.                // End of meaningful data...

  23.                break;

  24.            }

  25.        }

  26.        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);

  27.    } catch (CharConversionException ex) {

  28.        return VALIDATION_AUTO;

  29.    } finally {

  30.        reader.close();

  31.    }

  32. }

复制

只要我们理解了XSD和DTD的使用方法,理解上面的代码应该不会太难,Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。

获取Document

回到上面doLoadDocument的逻辑,它会委托DefaultDocumentLoader进行document的加载。

  1. public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,

  2.                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {


  3.    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);

  4.    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);

  5.    return builder.parse(inputSource);

  6. }

复制

对于这部分代码其实并没有太多可以描述的,因为通过SAX解析XML文档的套路大致都差不多,Spring在这里并没有什么特殊的地方,同样首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进行解析inputSource来返回Document对象。

这里有一个很重要的参数 entityResolver
,这个参数是通过 getEntityResolver()
函数获取的返回值:

  1. protected EntityResolver getEntityResolver() {

  2.    if (this.entityResolver == null) {

  3.        ResourceLoader resourceLoader = getResourceLoader();

  4.        if (resourceLoader != null) {

  5.            this.entityResolver = new ResourceEntityResolver(resourceLoader);

  6.        }

  7.        else {

  8.            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());

  9.        }

  10.    }

  11.    return this.entityResolver;

  12. }

复制

那么这个entityResolver有什么用呢?

EntityResolver的用法

关于什么是EntityResolver,官网给出了这样的解释:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver
方法向SAX驱动器注册一个实例,也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找对应的DTD定义,以便对文档进行一个验证。默认的寻找规则是通过网络(实现上就是声明的DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因。

EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。

首先看entityResolver的接口方法声明:

  1. public abstract InputSource resolveEntity (String publicId, String systemId);

复制

它接收两个参数:publicId和systemId,并返回一个inputSource对象。这里我们以特定配置文件来进行讲解。

(1)如果我们在解析验证模式为XSD的配置文件:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

  5. </beans>

复制

会读取以下两个参数:

  1. publicId: null

  2. systemId: http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

复制

(2)如果我们在解析验证模式为DTD的配置文件:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">

  3. <beans>

  4. ...

  5. </beans>

复制

会读取到以下两个参数:

  1. publicId: -//Spring//DTD BEAN 2.0//EN

  2. systemId: http://www.Springframework.org/dtd/Spring-beans-2.0.dtd

复制

由于验证文件默认的加载方式是通过URL进行网络下载获取,这样会造成延迟,用户体验也不好,一般的做法都是将验证文件放置在自己的工程里,那么怎么做才能将这个URL转换为自己工程里对应的地址文件呢?

回到之前的 getEntityResolver
方法。方法中根据是否存在resourceLoader来分别实例化不同的EntityResolver。而ResourceEntityResolver是DelegatingEntityResolver的子类,最终都会调用DelegatingEntityResolver的构造函数:

  1. public DelegatingEntityResolver(ClassLoader classLoader) {

  2.    this.dtdResolver = new BeansDtdResolver();

  3.    this.schemaResolver = new PluggableSchemaResolver(classLoader);

  4. }

复制

DelegatingEntityResolver只是一个代理类,真正处理resolveEntity的时候会根据不同的systemId路由到不同的类去处理:

  1. public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {

  2.    if (systemId != null) {

  3.        if (systemId.endsWith(DTD_SUFFIX)) {

  4.            return this.dtdResolver.resolveEntity(publicId, systemId);

  5.        }

  6.        else if (systemId.endsWith(XSD_SUFFIX)) {

  7.            return this.schemaResolver.resolveEntity(publicId, systemId);

  8.        }

  9.    }

  10.    return null;

  11. }

复制

而这里最重要的schemaResolver(PluggableSchemaResolver)在构造函数中指定了XSD模式文件映射地址: META-INF/spring.schemas

  1. public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";


  2. public PluggableSchemaResolver(ClassLoader classLoader) {

  3.    this.classLoader = classLoader;

  4.    this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;

  5. }

复制

在大多数的spring的jar包中都会发现这个文件的踪影,随便打开spring-aop的jar包,打开该文件,其中的内容为:

  1. http\://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsd

  2. http\://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsd

  3. http\://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsd

  4. http\://www.springframework.org/schema/aop/spring-aop-3.1.xsd=org/springframework/aop/config/spring-aop-3.1.xsd

  5. http\://www.springframework.org/schema/aop/spring-aop-3.2.xsd=org/springframework/aop/config/spring-aop-3.2.xsd

  6. http\://www.springframework.org/schema/aop/spring-aop-4.0.xsd=org/springframework/aop/config/spring-aop-4.0.xsd

  7. http\://www.springframework.org/schema/aop/spring-aop-4.1.xsd=org/springframework/aop/config/spring-aop-4.1.xsd

  8. http\://www.springframework.org/schema/aop/spring-aop-4.2.xsd=org/springframework/aop/config/spring-aop-4.2.xsd

  9. http\://www.springframework.org/schema/aop/spring-aop-4.3.xsd=org/springframework/aop/config/spring-aop-4.3.xsd

  10. http\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-4.3.xsd

复制

解析及注册BeanDefinitions

当把文件转换为Document后,接下来就是最重要的提取以及注册bean的方法 registerBeanDefinitions

  1. public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

  2.    // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader

  3.    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

  4.    // 获取已经注册的bean数

  5.    int countBefore = getRegistry().getBeanDefinitionCount();

  6.    // 加载及注册bean

  7.    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

  8.    // 记录本次加载的BeanDefinition个数

  9.    return getRegistry().getBeanDefinitionCount() - countBefore;

  10. }

复制

继续往下进入registerBeanDefinitions方法:

  1. public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {

  2.    this.readerContext = readerContext;

  3.    Element root = doc.getDocumentElement();

  4.    doRegisterBeanDefinitions(root);

  5. }

复制

方法很简单,仅仅提取出document的root元素交给下一个方法处理:

  1. protected void doRegisterBeanDefinitions(Element root) {

  2.    // 供<beans>标签递归调用

  3.    BeanDefinitionParserDelegate parent = this.delegate;

  4.    this.delegate = createDelegate(getReaderContext(), root, parent);


  5.    if (this.delegate.isDefaultNamespace(root)) {

  6.        // 解析profile属性

  7.        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);

  8.        if (StringUtils.hasText(profileSpec)) {

  9.            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(

  10.                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);

  11.            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {

  12.                return;

  13.            }

  14.        }

  15.    }


  16.    // 解析前处理,留给子类实现

  17.    preProcessXml(root);

  18.    parseBeanDefinitions(root, this.delegate);

  19.    // 解析后处理,留给子类实现

  20.    postProcessXml(root);


  21.    this.delegate = parent;

  22. }

复制

profile属性的使用

profile属性可能我们用的比较少,我们用一个官方的例子来说明:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">


  5.    <beans profile="dev">

  6.        ...

  7.    </beans>


  8.    <beans profile="production">

  9.        ...

  10.    </beans>

  11. </beans>

复制

集成到web开发环境时,在web.xml中加入以下代码:

  1. <context-param>

  2.    <param-name>Spring.profiles.active</param-name>

  3.    <param-value>dev</param-value>

  4. </context-param>

复制

有了这个特性我们就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境了。

解析并注册BeanDefinition

处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinitions:

  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {

  2.    // 对beans的处理

  3.    if (delegate.isDefaultNamespace(root)) {

  4.        NodeList nl = root.getChildNodes();

  5.        for (int i = 0; i < nl.getLength(); i++) {

  6.            Node node = nl.item(i);

  7.            if (node instanceof Element) {

  8.                Element ele = (Element) node;

  9.                if (delegate.isDefaultNamespace(ele)) {

  10.                    // 对bean的处理

  11.                    parseDefaultElement(ele, delegate);

  12.                }

  13.                else {

  14.                    // 对bean的处理

  15.                    delegate.parseCustomElement(ele);

  16.                }

  17.            }

  18.        }

  19.    }

  20.    else {

  21.        delegate.parseCustomElement(root);

  22.    }

  23. }

复制

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

  1. <bean id="test" class="xx.xx" />

复制

另一类就是自定义的,如:

  1. <tx:annotation-driven />

复制

而上面对于bean的不同流程的处理就是针对这些情况来考虑的。

因为微信正文不能超过50000字,接下来的标签解析内容放在下篇文章中来讲解~



如果读完觉得有收获的话,欢迎点赞、关注、加公众号【蹲厕所的熊】,查阅更多精彩历史!!!

文章转载自蹲厕所的熊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论