Spring技术内幕——Spring Framework的IOC容器实现(一)

一、SpringIOC容器概述
IOC容器和依赖反转的模式
在面向对象的系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架IOC容器来完成。他可以再解耦代码的同时提高了代码的可测试性。
依赖控制反转的实现由很多种方式,在Spring中,IOC容器是实现这个模式的载体,他可以再对象生成或者初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖。这种依赖注入是可以递归的,对象被逐层输入。简化了对象依赖关系的管理,在很大程度上简化了面向对象系统的复杂性。
关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中。是降低面向对象系统设计复杂性和提高面向对象系统可测试性的一个有效可行的解决方案。
应用控制反转后,当对象呗创建时,由一个调控系统内的所有对象的外界实体将其所依赖的对象的引用传递给他,即依赖被注入到对象中。所以,控制反转是关于一个对象如何获取他所依赖的对象的引用,在这里,反转指的责任的反转。
通过使用IOC容器,对象的依赖关系的管理被反转了,转到IOC容器中来了,对象之间的相互依赖关系由IOC容器进行管理,并由IOC容器完成对象的注入。简单的说,因为很多对象依赖关系的建立和维护并不需要和系统运行状态有很强的关联性,所以,可以把在面向对象编程中需要执行的诸如新建对象、为对象引用赋值等操作交由容器统一完成。这样依赖,这些散落在不同代码中的功能相同的部分就集中成为容器的一部分,也就是面向对象系统的基础设施一部分。
对象之间的相互依赖关系也是比较稳定的,一般不会随着应用的运行状态的改变而改变,这些特性使这些对象非常适合IOC容器来管理,虽然他们存在于应用系统中,但是应用系统并不承担管理这些对象的责任,而是通过依赖反转把责任交给了容器。了解了这些背景,Spring IOC容器的原理就不难理解了。在原理的具体实现上,Spring有着自己的独特思路,实现技巧和丰富的产品特性。
SpringIOC的应用场景
在Java EE企业应用开发中,IOC设计模式是解耦组件之间复杂关系的利器,Spring IOC模块就是这个模式的一种实现。
从获取基本服务上来看,Spring提供的服务和EJB容器提供的服务并没有太大的区别,只是在具体怎样获取服务的方式上,两者的设计有很大的不同:在Spring中,Spring IOC提供了一种基本的JavaBean容器,通过IOC模式管理依赖关系,并通过依赖注入和AOP切面增强了为JavaBean这样的POJO对象赋予事务管理、生命周期管理等基本功能;而对于EJB,一个简单的EJB组件需要编写远程/本地接口、Home接口以及Bean的实现类,而且EJB运行是不能脱离EJB容器的,查找其他EJB组件也需要通过诸如JNDI这样的方式,从而造成了对EJB容器和技术规范的依赖。也就是说Spring把EJB组件还原成了POJO对象或者JavaBean对象,降低了应用开发对传统J2EE技术规范的依赖。
在使用IOC容器,可以把资源获取的方向反转,让IOC容器主动管理这些依赖关系,将这些依赖关系注入到组件中,那么会让这些依赖关系的适配和管理更加灵活。在具体的实现中,接口注入(type 1 IOC)、setter注入(type 2 IOC)、构造器注入(type 3 IOC)是主要的注入方式。在Spring的IOC设计中,setter注入和构造器注入是主要的方式;相对而言,使用Spring时setter注入是常见的注入方式,而且为了防止注入异常,SpringIOC容器还提供了对特定依赖的检查。
二、IOC容器系列的设计与实现:BeanFactory和ApplicationContext
在SpringIOC容器的设计中,有两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,这个系列容器只实现了容器的最基本功能;另一个是ApplicationContext应用上下文,他作为容器的高级形态而存在。应用上下文在简单容器的基础上增加了许多面向框架的特性,同时对应用环境作了许多适配。下面我们就对SpringIOC容器的设计与实现进行简要的分析。
Spring的IOC容器系列
其实对于IOC容器的使用者来说,我们经常接触到的BeanFactory和ApplicationContext都可以看成是容器的具体表现形式。如果深入到Spring的实现中,实际代表着一系列功能各异的容器产品,知识容器的功能大小有各自的特点。
就像商品需要有产品规格说明一样,同样,作为IOC容器,也需要为他的具体实现指定基本的功能规范,这个功能规范的设计表现为接口类BeanFactory,他体现了Spring为提供给用户使用IOC容器所设定的最基本的功能规范。以百货商店出售的水桶为例,如果把IOC容器看成一个水桶,那么这个BeanFactory就定义了可以作为水桶的基本功能,比如,至少能装水,有个提手等。
Spring IOC容器的设计
前面我们已经了解了IOC容器的系列的概况。在Spring中,这个IOC容器是怎样设计的呢?我们看下IOC容器的接口设计图:
Spring技术内幕——Spring Framework的IOC容器实现(一)_第1张图片
三套接口体系:
1、从接口BeanFactory道HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一条主要的BeanFactory设计路径
2、以ApplicationContext应用上下文接口为核心的接口设计
3、以BeanFactory和ApplicationContext为核心的。
1、BeanFactory的应用场景
BeanFactory提供的是最基本的IOC容器的功能,关于这些功能的定义我们可以再接口BeanFactory中看到。
在Spring的代码实现中,BeanFactory只是一个接口类,并没有给出容器的具体实现,而我们看到的DefaultListableBeanFactory、XMLBeanFactory、ApplicationContext等都可以看成容器如见的某种功能的具体实现,也就是容器体系中的具体容器产品。下面我们来看看BeanFactory是怎样定义IOC容器的基本接口的。
用户使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分通过容器来获取FactoryBean产生的对象和获取FactoryBean本身。举例来说,如果
myJndiObject是一个FactoryBean,那么使用&myJndiObject得到的是FactoryBean,而不是myJNDIObject这个FactoryBean产生出来的对象。
BeanFactory接口设计了getBean方法,这个方法是使用IOC容器API的主要方法,通过这个方法,可以取得容器中管理的Bean,Bean的取得是通过指定名字来索引的。如果需要在获取Bean时对Bean的类型进行检查,BeanFactory接口定义了带有参数的getBean方法,这个方法的使用与不带参数的getBean方法类型,不同的是增加了对Bean检索的类型的要求。
用户可以通过BeanFactory接口方法中的getBean来使用Bean名字,从而在获取Bean时,如果需要获取的Bean是prototype类型的,用户还可以为这个prototype类型的Bean生成指定构造函数的对应参数。有了BeanFactory的定义,用户可以执行以下操作:

  • 通过接口方法containsBean让用户能够判断容器是否包含指定名字的Bean
  • 通过接口方法isSingleton类查询指定名字的Bean是否是Singleton类型的Bean。对于SingleTon属性,用户可以再BeanDefinition中指定。
  • 通过接口方法isTypeMatch来查询指定了名字的Bean的Class类型是否是特定的Class类型。
  • 通过接口方法getType来查询指定名字的Bean的Class类型
  • 通过接口方法getAliases类查询指定了名字的Bean的所有别名
    2、BeanFactory容器的设计原理
    BeanFactory接口提供了使用IOC容器的规范,我们以XMLBeanFactory的实现为例说明IOC容器的设计原理。
    Spring技术内幕——Spring Framework的IOC容器实现(一)_第2张图片
    可以看到,一个简单IOC容器系列最底层实现的XMLBeanFactory,只提供了最基本的IOC容器的功能。如果要扩展自己的容器的产品,我们需看看Spring是不是已经提供了线程的或相近的容器实现供我们参考。
    仔细阅读XMLBeanFactory的源码,XMLBeanFactory继承自DefaultListableBeanFactory这个类,他实际上包含了基本IOC容器所具有的重要功能,也是在很多地方都会用到的容器系列中的一个基本产品。在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IOC容器来使用,XmlBeanFactory在继承他又增加了新功能,他是一个与XML相关的BeanFactory,也就是说他是一个可以读取以XML文件方式定义的BeanDefinition的IOC容器。
    这些实现XML读取的功能是怎样实现的呢?对于XML文件定义信息的处理并不是由XMLBeanFactory直接完成的。在XMLBeanFactory中,初始化了一个XmlBeanDefinitionReader对象,有了这个Reader对象,那些以XML方式定义的BeanDefinition就有了处理的地方。我们可以看到,对这些XML形式的信息处理实际上由XMLBeanDefinitionReader来完成的。
    构造XMLBeanFactory这个IOC容器时,需要制定BeanDefinition的信息来源,而这个信息来源需要封装成Spring中的Resource类来给出。Resource是Spring用来封装I/O操作的类。比如:
ClassPathResources res = new ClassPathResources("beans.xml");

这样具体的ClassPathResource来构造需要的Resource,然后将Resource作为构造参数传递给XMLBeanFactory构造函数。这样IOC容器就可以方便的定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注入过程。
下面看下XMLBeanFactory的源码:

/**
 * Convenience extension of {@link DefaultListableBeanFactory} that reads bean definitions
 * from an XML document. Delegates to {@link XmlBeanDefinitionReader} underneath; effectively
 * equivalent to using an XmlBeanDefinitionReader with a DefaultListableBeanFactory.
 *
 * 

The structure, element and attribute names of the required XML document * are hard-coded in this class. (Of course a transform could be run if necessary * to produce this format). "beans" doesn't need to be the root element of the XML * document: This class will parse all bean definition elements in the XML file. * *

This class registers each bean definition with the {@link DefaultListableBeanFactory} * superclass, and relies on the latter's implementation of the {@link BeanFactory} interface. * It supports singletons, prototypes, and references to either of these kinds of bean. * See {@code "spring-beans-3.x.xsd"} (or historically, {@code "spring-beans-2.0.dtd"}) for * details on options and configuration style. * *

For advanced needs, consider using a {@link DefaultListableBeanFactory} with * an {@link XmlBeanDefinitionReader}. The latter allows for reading from multiple XML * resources and is highly configurable in its actual XML parsing behavior. * * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams * @since 15 April 2001 * @see org.springframework.beans.factory.support.DefaultListableBeanFactory * @see XmlBeanDefinitionReader * @deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory} and * {@link XmlBeanDefinitionReader} */ @Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }

其他IOC容器中,比如ApplicationContext,其实现的基本原理和XMLBeanFactory一样,也是通过持有或者扩展DefaultListableBeanFactory来获得基本的IOC容器功能的。
参考XmlBeanFactory的实现,我们以编程的方式使用DefaultListableBeanFactory,从中我们可以看到IOC容器使用的一些基本过程。例如:

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDeifinitionReader(factory);
reader.loadBeanDefinition(res);

这样我们就可以通过factory对象来使用DefaultListableBeanFactory这个IOC容器了。步骤如下:

  1. 创建IOC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息。
  2. 创建一个BeanFactory,这里使用DefaultListableBeanFactory
  3. 创建一个载入BeanDefinition的读取器,这里使用XMLBeanDefinitionReader来载入Xml文件形式的BeanDefinition,通过一个回调配置给BeanFactory
  4. 从定义好的资源位置读取配置信息 ,具体解析过程由XMLBeanDefinitionReader类完成。
    3、ApplicationContext的应用场景
    在Spring中,系统已经为用户提供了许多已定义好的容器实现,而不需我们事必躬亲。开发人员常用的ApplicationContext除了能够提供前面介绍的容器的基本功能外,还未用户提供了以下附加服务。所以说ApplicationContext是一个高级形态意义的IOC容器,他在BeanFactory基础上添加了很多附加功能。
    支持不同的信息源,我们可以看到ApplicationContext扩展了MessageSource接口,支持国际化
    访问资源。体现在ResourceLoader和Resource的支持上,我们可以从不同地方得到Bean定义资源
    支持应用事件,继承了接口ApplicationEvenPublisher,从而在上下文引入事件机制。事件和Bean的生命周期结合为Bean的管理提供了便利
    在ApplicationContext提供附加服务。使IOC容器的功能更丰富,面向框架的使用风格。
    4、ApplicationContext容器的设计原理
    在ApplicationContext容器中,我们以FileSystemXMLApplicationContext的实现为例
    在FileSystemXMLApplicationContext的设计中,我们看到ApplicationContext应用上下文的主要功能已经在FileSystemXMLApplicationContext的基类AbstractXMLApplicationContext中实现了,在FileSystemXMLApplicationContext中,作为一个具体的应用上下文,只需要实现和他自身设计相关的两个功能。
    一个功能是如果直接使用FileSystemXMLApplicationContext,对于实例化这个应用上下文的支持,同时启动IOC容器的refresh的过程,在FileSystemXMLApplicationContext源码如下:
/**
     * Create a new FileSystemXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of file paths
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

这个refresh过程会牵涉IOC容器启动的一些列复杂操作,同时,对于不同的容器实现,这些操作都是类似的,因此在基类中将他们封装好。所以,我们在FileSystemXml设计中看到的只是一个简单的调用。
另一个功能是FileSystemXMLApplicationContext设计具体相关的功能,这部分与怎样从文件系统加载Xml的Bean定义资源有关。通过这个过程可以再文件系统中读取以XML形式存在的BeanDefinition做准备,因为不同的应用上下文实现对应着不同的读取BeanDefinition的方式,在FileSystemXMLApplicationContext中实现代码如下:

/**
     * Resolve resource paths as file system paths.
     * 

Note: Even if a given path starts with a slash, it will get * interpreted as relative to the current VM working directory. * This is consistent with the semantics in a Servlet container. * @param path path to the resource * @return Resource handle * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath */ @Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path);

可以看到调用这个方法,可以得到FileSystemResource的资源定位。
未完待续……

你可能感兴趣的:(Spring,深入浅出Spring)