Spring Bean 大体架构介绍

这一节只是简单介绍SpringBean的大体架构图,主要是了解Spring启动,读入XML文件,分析XML文件注册Bean等。具体的实现没有涉及太多,可能还有不能理解的地方,没有关系!接着下面的学习你会更深入地了解Spring。

我们首先来认识两个spring的常用的类:

DefaultListableBeanFactory

XMLBeanFactory是继承自这个类的,主要用于从xml中读取BeanDefinition。BeanFactory增加了XMLBeanDefinitionReader类型的reader属性,使用reader对资源文件进行读取和注册。DefaultListableBeanFactory也是bean加载的核心部分。 可以看一下DefaultListableBeanFactory的结构图! 下面简单介绍一下,各个类的作用:

  • AliasRegistry:定义针对alias的简单增删改等操作。
  • SimpleAliasRegistry:主要使用map作为alias的缓存,是对AliasRegistry的实现。
  • SingletonBeanRegistry:定义对单例的注册和获取。接口。
  • BeanFactory:定义获取bean及bean的各种属性。
  • DefaultSingletonBeanRegistry:实现SingletonBeanRegistry。
  • HierarchicalBeanFactory:继承BeanFactory,也是在BeanFactory的基础上增加了parentFactory的支持。
  • BeanDefinitionRegistry:针对BeanDefinition的各种增删改查操作。
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry的基础上增加了对BeanFactory的特殊处理功能。
  • ConfigurableBeanFactory:提供配置Factory的各种方法。
  • ListableBeanFactory:根据各种条件获取Bean的配置清单
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
  • AutowireCapableBeanFactory:提供创建bean、自动注入、初始化、以及应用bean的后处理器。
  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory和AutowireCapableBeanFactory进行实现。
  • ConfigurableListableBeanFactory:BeanFactory的配置清单,指定忽略类型和接口等。
  • DefaultListableBeanFactory:综合上面所有的功能,主要是对bean注册后的处理。

XMLBeanDefinitionReader

XMLBeanDefinitionReader类图

上面是类的继承结构图,下面我们对其中用到的类进行大致介绍:

  • ResourceLoader:定义资源加载器,主要应用于对给定的资源文件地址,找到对应的资源文件返回对应的Resource
  • BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
  • EnvironmentCapable:定义获取Environment方法。
  • DocumentLoader:定义从资源文件加载到转换为Document的功能。
  • AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader的实现。
  • BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition的功能。
  • BeanDefinitionParserDelegate:定义解析Element的各种方法。

可以通过以下说明来解释XMLBeanDefinitionReader的工作流程:

  1. 通过继承AbstractBeanDefinitionReader的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件。
  2. 通过DocumentLoader对Resource进行转换,转换为Document文档。
  3. 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析。

XMLBeanFactory以及资源加载

下面我们来看一下XMLBeanFactory的执行过程,深入剖析一下:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));

对于这段代码,首先使用ClassPathResource来构造Resource资源对象的实例。这里涉及到Java将资源对象变成对于URL的处理(例如file:、http:、jar:、classpath:等)。如果直接使用这种方式需要了解URL的实现机制。因此Spring实现了自己的资源查找抽象结构!

classPathResource的结构图如下: classPathResource的类结构图

资源文件处理类图

  1. InputStreamSource封装返回任何能够返回InputStream的类。它只有一个方法定义getInputStream。
  2. Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。它定义了3个用于判断资源状态的方法:可读性(isReadable)、存在性(exists)、是否处于打开状态(isOpen),另外Resource还提供了不同资源之间的切换(file、url、classpath)。createRelative方法用于在当前路径创建相对路径文件的方法。getDescription用于在错误发生时,返回错误信息。对于不同的资源文件都有不同的实现类,比如FileSystemResource(文件资源)、ClassPathResource(classPath资源)、URLResource、InputStreamResource、ByteArrayResource等。

注意在日常的开发中,我们也可以直接使用Spring帮我们封装好的各种资源加载方式的类来处理对于的资源文件。

ClassPathResource工作简介

通过阅读源码我们发现Assert中定义了一些判断方法,不符合条件就报错,这里用它来检测传入的path是否合法。文件的加载,可以指定类加载类,也可以不指定类加载器。如果不指定类加载器,那么将由ClassUtils来帮我们查找一个类加载器,它首先找当前线程的上下文加载器,如果没有成功,那么会尝试普通类加载器,如果还没有就会去拿系统类加载器。用加载器的getResourceAsStream就可以拿到资源文件的输入流了。OK这就是ClasspathResource的实例化过程,下面我们看一下XMLBeanFactory是怎么使用它的!

XMLBeanFactory工作简介

你可以指定parentBeanFactory如果不指定为null,这个parentBeanFactory是在类AbstractBeanFactory中定义的。

public void setParentBeanFactory(BeanFactory parentBeanFactory) {
    if(this.parentBeanFactory != null && this.parentBeanFactory != parentBeanFactory) {
        throw new IllegalStateException("Already associated with parent BeanFactory: " + this.parentBeanFactory);
    } else {
        this.parentBeanFactory = parentBeanFactory;
    }
}

当然了不要忘记,AbstractAutowireCapableBeanFactory中初始化了一些配置信息:

public AbstractAutowireCapableBeanFactory() {
    // instantiationStrategy 为创建bean 实例的策略
    this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    this.parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    // 确定是否自动尝试去解析循环引用的bean
    this.allowCircularReferences = true;
    this.allowRawInjectionDespiteWrapping = false;
    // ignoredDependencyTypes 定义了在依赖检查和自动绑定时要忽略的依赖类型,是一组类对象,例如string,默认为没有。
    this.ignoredDependencyTypes = new HashSet();
    // ignoredDependencyInterfaces  定义了在依赖检查和自动绑定时要忽略的依赖接口,是一组类对象,默认情况下,只有beanFactory接口被忽略。
    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);
}

ignoreDependencyInterface的功能主要是忽略给定接口的自动装配过程。比如A包含B,正常A初始化后,会初始化B。如果B继承了BeanNameAware接口,B不会自动注入!自动装配忽略给定的依赖接口,典型的应用就是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入!这里有一个例子说明:spring中ServletContextAware接口使用理解

接着利用XmlBeanDefinitionReader来处理classpath资源,详细的处理过程后期会介绍。

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

XmlBeanDefinitionReader.loadBeanDefinitions时序图:

XmlBeanDefinitionReader处理流程

  1. 封装资源文件,将传入的Resource使用EncodedResource进行封装。
  2. 从Resource中获取InputStream输入流。并构造InputSource。使用sax的方式读取xml文档!
  3. 通过构造的EncodedResource和InputSource,继续调用doLoadBeanDefinitions。

EncodedResource就是进行Resource编码的封装,这个可以通过它的getReader方法查看得到!

下面介绍一下doLoadBeanDefinitions的实现:

  1. 调用doLoadDocument方法使用DefaultDocumentLoader从Resource中获取Document文档。这其中包括获取对xml的验证模式,加载xml文件,并得到对应的Document。
  2. 根据Document返回注册Bean信息。

下面介绍一下loadDocument方法的实现:

  1. 通过getValidationModeForResource方法获取验证xml模式(DTD和XSD).如果手工设置了验证模式,使用手工的;如果没有使用系统自动检测!自动检测就是查看文档是否有DOCTYPE字样,有就是DTD文档,否则就是XSD文档!
  2. 使用属性DocumentLoader加载文档。默认实现是DefaultDocumentLoader。这里没有太多要说的就是利用java SAX接口加载文档。注意解析的时候传入了一个EnttityResolver,EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明文件的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目的某处,在实现时直接将此文档读取并返回给SAX即可。这样就可以避免使用网络来查找DTD声明文档。DTD会在当前目录下找,而XSD会在Spring.schemas目录下去找!具体看代码实现!

下面介绍一下注册Bean的信息的实现:

  1. 实例化一个BeanDefinitionDocumentReader使用DefaultBeanDefinitionDocumentReader实例化。
  2. 统计当前BeanDefinition的个数,在实例化BeanDefinitionReader的时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类。里面有一个beanDefinitionMap的ConcurrentHashMap。
  3. 加载以及注册bean,最后返回注册bean的个数。

解析的过程是先处理profile属性。由于doRegisterBeanDefinitions调用的preProcessXml(root);和postProcessXml(root);都是空方法,其实是为了继承加上钩子方法而已。然后正式解析xml的正文部分。

关于Profile属性的处理,这个很少使用,但是还是挺有用的功能。

<beans profile="dev">
      <bean id="helloBean" class="com.gavin.bean.HelloBean">
             <property name="greet" value="GavinZhang"></property>
      </bean>
</beans>

<beans profile="production">
      <bean id="helloBean2" class="com.gavin.bean.HelloBean">
             <property name="greet" value="GavinZhang2"></property>
      </bean>
</beans>

然后可以集成到Web环境中,在web.xml中加入以下的代码:

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

有了这个特性我们就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便地进行切换开发、部署环境,最常用的就是更换不同的数据库。

解析并注册BeanDefinition:处理了profile的属性后就可以进行XML的读取了,跟踪代码进入parseBeanDefinitions(root, this.delegate);String在XML的配置有两大类Bean声明,一个是默认的<bean id="helloBean2" class="com.gavin.bean.HelloBean"/>,另一类是自定义的,比如<tx:annotation-driven/>。前两种的读取和解析差别是非常大的,如果采用Spring默认配置,Spring当然知道怎么做,但如果是自定义的,那么就需要用户实现一些接口及配置了。对于根节点或者子节点,如果是默认命名空间的话,采用parseDefaultElement方法。否则就使用delegate.parseCustomElement(ele);方法对自定义命名空间进行解析。而判断是否默认命名空间还是是自定义命名空间的办法是使用node.getNamespaceURI获取命名空间,并与spring的命名空间http://www.springframework.org/schema/beans进行比较。如果一直就是默认命名空间,否则就是自定义的命名空间。而默认的标签解析和自定的标签解析,下一章节做相应介绍。

results matching ""

    No results matching ""