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

准备工作

容器基本用法

  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容器加载相关类图
  • AliasRegistry 定义对alias的简单增删改等操作

    void registerAlias(String name, String alias);
    void removeAlias(String alias);
    boolean isAlias(String name);
    String[] getAliases(String name);
    
  • BeanDefinitionRegistry 定义对BeanDefinition的各种增删改等操作

    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
    void removeBeanDefinition(String beanName);
    BeanDefinition getBeanDefinition(String beanName);
    boolean containsBeanDefinition(String beanName);
    String[] getBeanDefinitionNames();
    int getBeanDefinitionCount();
    boolean isBeanNameInUse(String beanName);
    
  • SimpleAliasRegistry 使用map作为alias的缓存,实现 AliasRegistry

    private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
    
  • SingletonBeanRegistry 定义对单例的注册及获取

    void registerSingleton(String beanName, Object singletonObject);
    Object getSingleton(String beanName);
    boolean containsSingleton(String beanName);
    String[] getSingletonNames();
    int getSingletonCount();
    Object getSingletonMutex(); //返回此注册表使用的单例互斥锁(适用于外部协作者)
    
  • DefaultSingletonBeanRegistry 实现SingletonBeanRegistry各方法

  • FactoryBeanRegistrySupport 在DefaultSingletonBeanRegistry 基础上新增对FactoryBean的处理

    • 注意区分FactoryBean和BeanFactory
      FactoryBean: Interface to be implemented by objects used within a BeanFactory which are themselves factories for individual objects.If a bean implements this interface(FactoryBean<T>), it is used as a factory for an object to expose, not directly as a bean instance that will be exposed itself.
      NB: A bean that implements this interface cannot be used as a normal bean.A FactoryBean is defined in a bean style, but the object exposed for bean references ({getObject()}) is always the object that it creates

      // <T> the bean type
      T getObject() throws Exception;
      
    • BeanFactory即这个bean是工厂,作用就是配置、新建、管理各种Bean

    • FactoryBean即这个bean是工厂类型的,我们通过 getBean(xxxFactoryBean) 获得是该工厂xxxFactoryBean所产生的 T 的实例,而不是 xxxFactoryBean 自身的实例。想要获取到 xxxFactoryBean 本身的实例,在名称前加“&”

  • BeanFactory 定义获取bean及bean的各种属性
    The root interface for accessing a Spring bean container.This interface is implemented by objects that hold a number of bean definitions, each uniquely identified by a String name.

  • HierarchicalBeanFactory 定义在BeanFactory基础上增加对parentFactory的支持

    //Return the parent bean factory, or {@code null} if there is none.
    BeanFactory getParentBeanFactory(); 
    
  • ConfigurableBeanFactory 定义提供配置Factory的各个方法

  • ListableBeanFactory 定义提供根据各种条件获取bean的配置清单

    boolean containsBeanDefinition(String beanName);
    int getBeanDefinitionCount();
    String[] getBeanDefinitionNames();
    String[] getBeanNamesForType(ResolvableType type);
    String[] getBeanNamesForType(@Nullable Class<?> type);
    String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean   allowEagerInit);
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;  
    String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
    Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType);
    <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType);
    
  • AbstractBeanFactory 综合ConfigurableBeanFactory和FactoryBeanRegistrySupport

  • AutowireCapableBeanFactory 定义 提供创建bean、自动注入、初始化及应用bean后的处理器

    <T> T createBean(Class<T> beanClass);
    void autowireBean(Object existingBean);
    Object configureBean(Object existingBean, String beanName);
    Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck);
    Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck);
    void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck);
    void applyBeanPropertyValues(Object existingBean, String beanName);
    Object initializeBean(Object existingBean, String beanName);
    Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName);
    Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName);
    void destroyBean(Object existingBean);
    <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType);
    Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName);
    Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
          @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter);
    
  • AbstractAutowireCapableBeanFactory综合AbstractBeanFactory并对AutowireCapableBeanFactory进行实现。

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

    void ignoreDependencyType(Class<?> type);
    void ignoreDependencyInterface(Class<?> ifc);
    void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue);
    boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor);
    BeanDefinition getBeanDefinition(String beanName);
    Iterator<String> getBeanNamesIterator();
    void clearMetadataCache();
    void freezeConfiguration(); // 不可修改 Configuration
    boolean isConfigurationFrozen();
    void preInstantiateSingletons(); // 确认非延迟的已实例化,同样也要考虑到FactoryBeans。
    
  • DefaultListableBeanFactory 综合上述所有功能,主要是对bean注册后的处理。

    public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {...}
    
    public boolean isAllowBeanDefinitionOverriding()  {...}
    
    public void setAllowEagerClassLoading(boolean allowEagerClassLoading) {...}
    
    public void setDependencyComparator(@Nullable Comparator<Object> dependencyComparator) {...}
    
    public Comparator<Object> getDependencyComparator() {...}
    
    public AutowireCandidateResolver getAutowireCandidateResolver() {...}
    

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;
  • ResourceLoader 定义资源加载器。根据给定的资源文件地址返回对应的Resource

    Resource getResource(String location);
    @Nullable
    ClassLoader getClassLoader(); // 可以直接通过ResourceLoader获取到ClassLoader
    
  • BeanDefinitionReader 定义资源文件读取并转换为BeanDefinition的各个功能

    BeanDefinitionRegistry getRegistry();
    @Nullable
    ResourceLoader getResourceLoader();
    @Nullable
    ClassLoader getBeanClassLoader();
    BeanNameGenerator getBeanNameGenerator();
    int loadBeanDefinitions(Resource resource);
    int loadBeanDefinitions(Resource... resources);
    int loadBeanDefinitions(String location);
    int loadBeanDefinitions(String... locations);
    
  • EnvironmentCapable 定义获取Environment方法

    Environment getEnvironment();
    
  • AbstractBeanDefinitionReader 对 BeanDefinitionReader 和 EnvironmentCapable 的实现

  • DocumentLoader 定义从Resource转换为Document的功能

    Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
          ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
    
  • BeanDefinitionDocumentReader 定义读取Document并注册BeanDefinition,例如默认实现DefaultBeanDefinitionDocumentReader:

    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    
  • BeanDefinitionParserDelegate 解析Element的各种方法

简言之,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约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。
  • 比较XML文件和DTD文件看XML文档是否符合规范,元素和标签使用是否正确。
  • DTD包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体/符号规则。
  • 使用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">
<!-- 引用语法 -->
<!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)

  • 即XML 模式定义。XML Schemas描述XML文档的结构
  • 通过XML Schema指定一个XML文档所允许的结构和内容(可以说是检验文档的有效性)。
  • XML Schema本身就是XML文档
  • 使用XML Schema:
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);
}
  • 解析一个XML,SAX首先读取该XML文档上的声明,根据声明寻找相应的DTD。
  • EntityResolver 作用:项目本身就可以提供一个如何寻找DTD声明的方法,而不必去网络上下载。

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

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

  • 加载DTD类型的BeansDtdResolver的resolveEntity方法是直接截取到systemId的xxx.dtd去当前路径下寻找
    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);
                }
            }
        }
    }
  • 加载XSD类型的PluggableSchemaResolver的resolveEntity方法是默认到"META-INF/spring.schemas"文件中寻找systemId对应的XSD文件并加载
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 进行处理
  • 根据 BeanDefinitionParserDelegate (delegate) 对 Element (root) 进行解析及注册

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源码深度解析》 学习笔记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,227评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,755评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,899评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,257评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,617评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,757评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,982评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,715评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,454评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,666评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,148评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,512评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,156评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,112评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,896评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,809评论 2 279
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,691评论 2 272

推荐阅读更多精彩内容