Java学习笔记系列

(一)Spring - beans 的两个核心类

2018-09-19  本文已影响0人  SonyaBaby

准备工作

容器基本用法

  1. bean
public class HelloWorldTest {
  
  public String hello = "Hello World";

  public String getHello() {
    return hello;
  }

  public void setHello(String hello) {
    this.hello = hello;
  }
}
  1. xml
<?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="helloWorldBean" class="test.HelloWorldBean"></bean>
</beans>
  1. App逻辑关联代码
@SuppressWarnings("deprecation")
public void testLoadProcedure(){
  BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
  HelloWorldBean helloWorld = (HelloWorldBean) beanFactory.getBean("helloWorldBean");
  System.out.println(helloWorld.getHello());
}

首先了解 beans 的两个核心类

1. 核心类一:DefaultListableBeanFactory

DefaultListableBeanFactory容器加载相关类图

XmlBeanFactory (Deprecated) 对DefaultListableBeanFactory进行拓展,新增个性化实现:XmlBeanDefinitionReader类型的reader属性,主要用于从XML文档中读取BeanDefinition,对于注册和获取bean依然使用父类方法。

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
}

2.核心类二:XmlBeanDefinitionReader

XmlBeanDefinitionReader配置文件读取相关类图.png

XmlBeanDefinitionReader中包含DocumentLoader、BeanDefinitionDocumentReader 对应属性值:

private DocumentLoader documentLoader = new DefaultDocumentLoader();
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
        DefaultBeanDefinitionDocumentReader.class;

AbstractBeanDefinitionReader中包含ResourceLoader resourceLoader属性:

@Nullable
private ResourceLoader resourceLoader;

简言之,XmlBeanDefinitionReader

  1. 通过继承 AbstractBeanDefinitionReader 中的方法,使用 ResourceLoader 读取资源对象Resource
  2. 通过 DocumentLoader 将Resource文件转换为Document文件
  3. 通过 DefaultBeanDefinitionDocumentReader 类对Document进行解析,通过使用BeanDefinitionParserDelegate

那么来捋一下BeanFactory初始化过程,

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
BeanFactory初始化时序图.png

Resource资源封装

根据时序图步骤1,可以看到是通过 ClassPathResource 进行Resource资源封装。Java 将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来实现不同的资源读取逻辑,一般handler的类型通过不同前缀识别(file: http: jar: .etc)。而Spring对内部使用到的资源实现了抽象结构:Resource接口封装底层资源。

public interface InputStreamSource {
  InputStream getInputStream() throws IOException;
}

public interface Resource extends InputStreamSource {
  boolean exists(); // 存在性
  boolean isReadable();// 可读性
  boolean isOpen(); // 当前是否打开状态
  URL getURL() throws IOException;// 资源→URL
  URI getURI() throws IOException;// 资源→URI
  File getFile() throws IOException;// 资源→File
  long contentLength() throws IOException;
  long lastModified() throws IOException;
  Resource createRelative(String var1) throws IOException; // 基于当前资源创建一个相对资源
  String getFilename(); // 不带路径信息的文件名
  String getDescription();// 详细打印出错的资源文件信息
}

对于不同来源的资源文件都有对应的Resource实现,文件、ClassPath资源、URL资源、InputStream资源、Byte数组资源等:

Resource资源文件关系图.png

通过Resource相关类对配置文件封装后(完成时序图步骤2),读取工作就交由XmlBeanDefinitionReader处理。进行时序图步骤3:XmlBeanFactory初始化。可以看出来真正执行的是第二个构造器。

public class XmlBeanFactory extends DefaultListableBeanFactory {
  private final XmlBeanDefinitionReader reader;

  public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, (BeanFactory)null);
  }

  public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader = new XmlBeanDefinitionReader(this);
    this.reader.loadBeanDefinitions(resource);
  }
}

this.reader.loadBeanDefinitions(resource);资源加载的真正实现,在分析资源加载之前,需要关注一点super(parentBeanFactory); 跟踪 super() 至 AbstractAutowireCapableBeanFactory 的 public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) 构造器:

  public AbstractAutowireCapableBeanFactory() {
    this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
    this.parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    this.allowCircularReferences = true;
    this.allowRawInjectionDespiteWrapping = false;
    this.ignoredDependencyTypes = new HashSet();
    this.ignoredDependencyInterfaces = new HashSet();
    this.factoryBeanInstanceCache = new ConcurrentHashMap(16);
    this.filteredPropertyDescriptorsCache = new ConcurrentHashMap(256);
    this.ignoreDependencyInterface(BeanNameAware.class);
    this.ignoreDependencyInterface(BeanFactoryAware.class);
    this.ignoreDependencyInterface(BeanClassLoaderAware.class);
  }

  public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) {
    this();
    this.setParentBeanFactory(parentBeanFactory);
  }

这里的ignoreDependencyInterface方法能够直接忽略给定接口的自动装配功能,为什么要忽略呢?
官方解释:
This will typically be used by application contexts to register dependencies that are resolved in other ways, like IOjbectFactory through IObjectFactoryAware or IApplicationContext through IApplicationContextAware. By default, IObjectFactoryAware and IObjectName interfaces are ignored. For further types to ignore, invoke this method for each type.

A{B},当Spring获取A的Bean时,如果B还未初始化,Spring会自动初始化B。但是如果B实现了BeanNameAware接口,B将不会被初始化。自动装配时忽略给定的依赖接口吗,通常被用来以其他方式解析Application上下文依赖,类似于 BeanFactory 通过 BeanFactoryAware 注入,ApplicationContext 通过 ApplicationContextAware注入。

我们继续关注 reader 如何加载bean。

加载Bean

通过 XmlBeanDefinitionReader 的reader属性,loadBeanDefinitions方法,加载整个资源resource,如下

1.
this.reader.loadBeanDefinitions(resource);

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

1.1   1.2
public EncodedResource(Resource resource) {
    this(resource, null, null);
}

1.3
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    ...
    // 通过属性来记录已经加载的资源
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    ...
    try {
1.3.1 ~ 1.3.4  // 获取到Resource的InputStream
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
1.3.5 ~ 1.3.6  // InputSource 并不是Spring的  包名:package org.xml.sax;
            InputSource inputSource = new InputSource(inputStream);
            ...
1.3.7       // 逻辑核心
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        ...
    }
    ...
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource); // 关注【获取Document】
        int count = registerBeanDefinitions(doc, resource); // 关注 【解析并注册BeanDefinitions】分析
        ...
1.3.8
        return count;
    }
  ...
}
loadBeanDefinitions时序图.png
  1. 用 EncodedResource 对 Resource 进行封装。顾名思义,和 Resource 编码处理相关。主要体现在getReader(),构造相应的编码属性。
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
    super();
    Assert.notNull(resource, "Resource must not be null");
    this.resource = resource;
    this.encoding = encoding;
    this.charset = charset;
}

public Reader getReader() throws IOException {
    if (this.charset != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.charset);
    }
    else if (this.encoding != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.encoding);
    }
    else {
        return new InputStreamReader(this.resource.getInputStream());
    }
}
  1. 获取输入流。根据封装好的 EncodedResource 对象获取Resource 的 InputStream,并构造为 InputSource。
public class InputSource {
  private String publicId;
  private String systemId;
  private InputStream byteStream;
  private String encoding;
  private Reader characterStream;
  ...  //省略 get set方法
}
  1. 调用 doLoadBeanDefinitions 。实际是做了三件事情,且必不可少。
    ① 获取对XML文件的验证模式
    ② 加载XML文件,并得到对应的Document
    ③ 根据返回的Document注册Bean信息
1
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
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}

3
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

简述过程就是:先对传入的resource做 EncodedResource 封装(是考虑到Resource存在编码要求),然后通过SAX读取XML文件(InputStream)准备InputSource对象,最后数据传入核心逻辑doLoadBeanDefinitions。继续学习这三个过程:

① 获取XML文件的验证模式

DTD(Document Type Definition)

<?xml version = "1.0" encoding="GB2312" standalone = "no"?>
<!DOCTYPE  beans PUBLIC "-//Spring//DTD BEAN2.0//EN"  "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
<!-- 引用语法 -->
<!DOCTYPE  根元素名  PUBLIC   “DTD名称”  "DTD文件的URL">

Spring-beans-2.0.dtd 如下:

<!ELEMENT beans (
    description?,
    (import | alias | bean)*
)>

<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-merge (true | false) "false">
<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
<!ATTLIST beans default-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>
...

XSD(XML Schemas Definition)

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">

Spring-beans-3.0.xsd 如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.springframework.org/schema/beans">

    <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>

    <xsd:annotation>
        <xsd:documentation><![CDATA[
        ...
        ]]></xsd:documentation>
    </xsd:annotation> 

    <!-- base types -->
    <xsd:complexType name="identifiedType" abstract="true">
        <xsd:annotation>
            <xsd:documentation><![CDATA[
    The unique identifier for a bean. The scope of the identifier
    is the enclosing bean factory.
            ]]></xsd:documentation>
        </xsd:annotation>
        <xsd:attribute name="id" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[
    The unique identifier for a bean. A bean id may not be used more than once
    within the same <beans> element.
                ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>
    ...
</xsd:schema>

读取验证模式

protected int getValidationModeForResource(Resource resource) {
    // 如果手动指定验证模式就使用
    int validationModeToUse = getValidationMode();
    if (validationModeToUse != VALIDATION_AUTO) {
        return validationModeToUse;
    }
    // 如果未指定则自动检测resource 如果存在DOCTYPE则使用VALIDATION_DTD,否则VALIDATION_AUTO
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    return VALIDATION_XSD;
}

public int getValidationMode() {
    return this.validationMode;
}

protected int detectValidationMode(Resource resource) {
    ...
    InputStream inputStream;
    try {
        inputStream = resource.getInputStream();
        return this.validationModeDetector.detectValidationMode(inputStream);
    }
    ...
}

自动检测转交至专门处理类 XmlValidationModeDetector (Detects whether an XML stream is using DTD- or XSD-based validation.):

public int detectValidationMode(InputStream inputStream) throws IOException {
    // Peek into the file to look for DOCTYPE.
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    try {
        boolean isDtdValidated = false;
        String content;
        while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            if (this.inComment || !StringUtils.hasText(content)) { // 跳过注释行或空行
                continue;
            }
            if (hasDoctype(content)) { // 检测包含DOCTYPE字样
                isDtdValidated = true;
                break;
            }
            if (hasOpeningTag(content)) { // 存在‘<’ 并且有内容时就无须继续查数据,验证模式DOCTYPE关键字在校验开始符号之前便会出现。
                // End of meaningful data...
                break;
            }
        }
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    }
    catch (CharConversionException ex) {
        return VALIDATION_AUTO;
    }
    finally {
        reader.close();
    }
}

private boolean hasDoctype(String content) {
    return content.contains(DOCTYPE);
}

private boolean hasOpeningTag(String content) {
    if (this.inComment) {
        return false;
    }
    int openTagIndex = content.indexOf('<');
    return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
            Character.isLetter(content.charAt(openTagIndex + 1)));
}

private static final String DOCTYPE = "DOCTYPE";

② 获取Document

获取XML验证模式后,即可进行 Document 加载,XMLBeanDefinitionReader 转交给 DocumentLoader 的实现类 DefaultDocumentLoader来完成

XMLBeanDefinitionReader :

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}

protected EntityResolver getEntityResolver() { 
    if (this.entityResolver == null) {
        // Determine default EntityResolver to use.
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        } else {
            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
        }
    }
    return this.entityResolver;
}

DefaultDocumentLoader :

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}

EntityResolver

public abstract InputSource resolveEntity (String publicId, String systemId)
  1. 接收 publicId 和 systemId 返回 InputSource 对象,例如验证模式为 XSD 的 xml 文件配置:
<?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">
...
</beans>
  1. 再看验证模式为 DTD 的 xml 文件配置:
<?xml version = "1.0" encoding="GB2312" standalone = "no"?>
<!DOCTYPE  beans PUBLIC "-//Spring//DTD BEAN2.0//EN"  "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">

验证文件的默认加载方式是从网络下载,用户体验不好,一般都将验证文件放置在自己的项目之中。将URL转换为自己工程里对应的地址文件,而 DelegatingEntityResolver 作为 EntityResolver 实现类:

public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
    if (systemId != null) {
        if (systemId.endsWith(DTD_SUFFIX)) {
            return this.dtdResolver.resolveEntity(publicId, systemId);
        }
        else if (systemId.endsWith(XSD_SUFFIX)) {
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }
    return null;
}

根据不同验证模式有不同的解析器解析。

    if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
        int lastPathSeparator = systemId.lastIndexOf('/');
        int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
        if (dtdNameStart != -1) {
            String dtdFile = DTD_NAME + DTD_EXTENSION;
            try {
                Resource resource = new ClassPathResource(dtdFile, getClass());
                InputSource source = new InputSource(resource.getInputStream());
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                if (logger.isTraceEnabled()) {
                    logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                }
                return source;
            }
            catch (IOException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                }
            }
        }
    }
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

若未声明,则去默认地址"META-INF/spring.schemas"下找寻

    if (systemId != null) {
        String resourceLocation = getSchemaMappings().get(systemId);
        if (resourceLocation != null) {
            Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
            try {
                InputSource source = new InputSource(resource.getInputStream());
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                if (logger.isTraceEnabled()) {
                    logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
                }
                return source;
            }
            catch (FileNotFoundException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
                }
            }
        }
    }

③ 解析并注册BeanDefinitions

根据②中获取到的Document,接下来重点:提取并注册bean

// XmlBeanDefinitionReader :
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 1. 实例化DefaultBeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 2. 记录注册前 BeanDefinition 的个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 3. 加载及注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 4. 返回本次注册beanDefinition数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

1. 
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanUtils.instantiateClass(this.documentReaderClass);
}

private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
        DefaultBeanDefinitionDocumentReader.class;

// DefaultBeanDefinitionDocumentReader:
3.
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

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.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;
}

profile 属性使用

...
<beans profile="dev">
      ...
</beans>
<beans profile="product">
      ...
</beans>

集成到Web环境中,在web.xml加入:

<context-param>
    <param-name>Spring.profiles.active</param-name>
    <param-name>dev</param-name>
</context-param>

有了这个特性我们可以在配置文件中部署两套配置分别适用于开发、生产环境。

程序获取beans节点并检测是否定义了profile属性,如果存在则去环境变量中寻找(environment),并对profiles进行拆分,解析每个profile。不定义则不会浪费性能去解析。

解析并注册BeanDefinition
处理完 profile 就可以进行 XML 读取

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    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 {
        delegate.parseCustomElement(root);
    }
}

bean的声明分为两大类(根据getNameSpaceURI()获取命名空间,并与http://www.Springframework.org/schema/beans 进行比对):

<bean id="test" class="test.TestBean"/>
<tx:annotation-driven>

详细见 (二)XML标签解析 -- 默认标签解析 parseDefaultElement


《Spring源码深度解析》 学习笔记

上一篇下一篇

猜你喜欢

热点阅读