Spring框架核心技术


  • 1. The IoC Container
    • 1.1. Introduction to the Spring IoC Container and Beans
    • 1.2. Container Overview
      • 1.2.1. Configuration Metadata
      • 1.2.2. Instantiating a Container
        • Composing XML-based Configuration Metadata
        • The Groovy Bean Definition DSL
      • 1.2.3. Using the Container
    • 1.3. Bean Overview

这篇参考文档的部分涵盖了所有Spring框架绝对不可或缺的技术。

其中这些最重要的就是Spring框架的控制反转(Inversion of Control, IoC)容器。在Spring框架的IoC容器的全面处理之后,紧接着就是全面覆盖了Spring的面向切面编程设计(Aspect-Oriented Programming, AOP)技术。Spring框架有它自己的AOP框架,从概念上,这是易于理解并且成功解决了80%在Java企业编程中所需要的AOP问题。

还覆盖了Spring与AspectJ(在特点方面,是目前在Java企业空间中,最丰富的也是最成熟的AOP实现)的整合也被包含在内。

1. The IoC Container

此篇涵盖了Spring的控制反转(IoC)容器。

1.1. Introduction to the Spring IoC Container and Beans

此篇涵盖了Spring框架控制反转(IoC)的实现原则。IoC也被称之为依赖注入(Dependency Injection, DI)。它是一个对象仅通过构造器参数、工厂方法参数、在被构造器构造之后被设置在对象实例之上、或者从一个工厂方法返回方法定义它们的依赖(也就是说,它们与其它对象一起运作)的过程。当容器创建bean时,容器之后注入它们的依赖。从根本上来说,这一过程是反转的,bean自己控制实例化,或是通过直接使用类的构造或是像服务定位器模式这样的机制,来对它的依赖定位。

org.springframework.beansorg.springframework.context包都是Spring框架IoC容器的基础。BeanFactory接口提供一个高级的配置机制,其能够管理任何一个对象的类型。ApplicationContext是BeanFactory的子接口。它添加了:

  • 易于于Spring的AOP特性整合
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层面特定的上下文,例如对于使用在web应用中的WebApplicationContext

简言之,BeanFactory提供了配置框架和基础功能,而ApplicationContext添加了更多的针对企业功能。ApplicationContext完全是BeanFactory的超集,并且在本章中专门用于描述Spring的IoC容器。关于使用BeanFactory而不是ApplicationContext的更多信息,可以查看BeanFactory。

在Spring中,形成你的应用的骨干并且通过Spring IoC容器管理的对象被称之为bean。一个bean是一个实例化的、装配好的以及由通过IoC容器管理的对象。否则,一个bean只是在你应用中众多对象的其中一个。Bean以及它们之间的依赖,都被反射进容器所使用的配置元数据中。

1.2. Container Overview

org.springframework.context.ApplicationContext接口代表了Spring IoC容器,并且它负责实例化、配置和装配bean。容器得到它的指令,有要实例化、配置以及通过读入配置元数据装配的对象。配置元数据使用XML、Java注解亦或Java代码表示。它使得你表达组成你的应用程序的对象,并且在这些对象直接具有丰富的相互依赖关系。

ApplicationContext接口的实现都由Spring提供。在独立应用中,常用做法是创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。尽管XML对于定义配置元数据有着传统的格式,你可以通过少数量的XML配置,声明式地启用这些额外的元数据格式的支持,来使得容器使用Java注解或代码作为元数据格式。

在大多数应用场景中,实例化一个或多个Spring IoC容器的实例,是不需要明确用户代码的。例如,在web应用场景,通常情况下在应用程序中的web.xml文件,简单的八行样板web描述符XML便足够了(Convenient ApplicationContext Instantiation for Web Applications)。如果你使用Spring Tools for Eclipse(Eclipse提供的开发环境),你可以简单地创建样板配置。

下面的图片展示了Spring如何工作的高层视图。在ApplicationContext被创建和初始化之后,你的应用程序类与配置元数据绑定,以便于你有一个完全配置的和可执行的系统或应用。

image

1.2.1. Configuration Metadata

正如前面的图片所示,Spring IoC容器消耗了一种配置元数据的形式。这一配置元数据代表了作为开发人员的你,如何告知Spring容器来实例化、配置以及装配你应用中的对象。

传统意义上的配置元数据是由简单直观的XML格式提供,这也是本章用的最多的格式,用来传递Spring IoC容器的关键概念和特点。

基于XML的元数据并不是配置元数据的唯一允许形式。Spring IoC容器本身与这种配置元数据的实际编写格式是完全解耦的。最近,许多开发人员选择Java-based configuration作为它们的Spring应用。

关于在Spring容器中的其他元数据的形式的信息,可以查看:

  • Annotation-based configuration:在Spring 2.5引入支持基于注解的配置元数据。
  • Java-based configuration:从Spring 3.0开始,许多由Spring JavaConfig项目提供的特性,变成了Spring框架的核心部分。因此,你可以通过使用Java而不是XML,在你的应用程序外部定义bean。为了使用这些新特性,查看[@Configuration]*(https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html)、@Bean、@Import和@DependsOn注解。

Spring配置由至少一个并且通常多于一个由容器管理的bean定义组成。基于XML配置元数据配置这些bean为在顶层元素内的元素。Java配置通常使用在@Configuration类中的@Bean注解的方法。

这些bean定义对应于实际组成你的应用程序的对象。通常来讲,你定义服务层对象、数据访问对象(DAOs)、诸如Struts Action实例的presentation对象、诸如Hibernate的SesstionFactory的基础设施(infrastructure)对象、JMS Queues等等。通常,人们不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的责任。但是,你可以使用Spring的AspectJ的整合,来配置创建在IoC容器之外的对象。查看Using AspectJ to dependency-inject domain objects with Spring。

如下所示了基于XML的配置元数据的基本结构:

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


      
        
    

    
        
    

    


  1. id属性是每一个bean定义的标识符字符串。
  2. class属性定义了bean的类型,并且使用合格的全类名。

id属性的值指的是协作对象。对于协作对象,在此处的XML并没有展示。查看更多关于Dependencies的信息。

1.2.2. Instantiating a Container

位置路径或提供给ApplicationContext构造器的路径都是资源字符串,可以使得容器从各种外部资源加载配置元数据,例如本地文件系统和Java的CLASSPATH等。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

在你学习了关于SPring的IoC容器之后,你也许想要了解更多关于Spring的Resource抽象(描述在Resource),而这对于以URI语法定义的位置读入输入流,提供了一个方便的机制。尤其是,Resource路径被用来构造应用上下文,描述在Application Contexts and Resource Paths。

如下展示了服务层services.xml对象配置文件:

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


    

    
        
        
        
    

    


下面展示了数据访问对象daos.xml文件:

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


    
        
    

    
        
    

    


在之前的例子中,服务层由PetStoreServiceImpl类和两个数据访问对象JpaAccountDaoJpaItemDao(都是基于JPA对象-关系映射(JPA Object-Relational Mapping)标准)组成。property name元素指的是JavaBean属性的名字,ref元素指的是其它bean定义的名字。idref元素之间的联系,表达了合作对象之间的依赖。对于关于更多的配置一个对象的依赖,可以查看Dependencies。

Composing XML-based Configuration Metadata

在多个XML文件中的bean定义是很有用的。通常来讲,每一个单独的XML文件代表你的架构中的一个业务逻辑或模块。

你可以使用应用上下文构造器来加载所有XML片段的bean定义。正如之前章节所示,这个构造器输入了多个Resource位置。或者,使用一个或多个元素加载其他文件的bean定义。如下所示:


    
    
    

    
    

在之前的示例中,外部bean定义从三个文件加载:services.xmlmessageSource.xmlthemeSource.xml。所有的位置路径都是相对于使用加载的定义文件,如services.xml必须与使用了导入(import)的文件在相同的目录或classpath位置下,同时,messageSource.xmlthemeSource.xml必须在导入文件的位置之下的resource位置里。正如你所见,前面的斜杠被忽略。但是,给定的这些路径都是相对的,更好的形式是不要使用斜杠。根据Spring Schema,被导入的文件的内容,包括顶层的元素,都必须是合法的XML bean定义。

一个可行但不推荐的做法是参考文件在父目录使用"../"路径。若这么做就产生了对当前应用外部的文件的依赖。尤其是,对于classpath:的URL(例如,classpath:../services.xml)是不被推荐的,其中运行时解决程序(runtime resolution process)选择“最近的”classpath根目录,之后查找它的父目录。Classpath的改变也许会导致一个不同的、错误的目录选择。

你可以总是使用合法的全资源位置而不是相对位置:例如,file:C:/config/services.xmlclasspath:/config/services.xml。但是需要注意的是,你正在耦合你的应用配置与特定的绝对位置。一般来说,对于绝对位置,最好是保持使用一个指向性(indirection)占位符,例如通过使用在运行时在JVM系统中处理的“${...}”占位符。

命名空间中提供了导入指令(import directive)特性。进一步关于复杂的bean定义的配置,由Spring提供的在XML命名空间的部分是可用的,例如contextutil命名空间。

The Groovy Bean Definition DSL

关于外部化的配置元数据的进一步示例,bean定义也可以表达在Spring的Groovy Bean Definition DSL,如同Grails框架那样。通常来讲,诸如此类的配置在“.groovy”文件中,结构如下所示:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

这一配置风格在很大程度上等同于XML的bean定义,并且甚至还支持SPring的XML配置命名空间。它也允许通过importBeans指令导入XML bean定义文件。

1.2.3. Using the Container

ApplicationContext是一个高级工厂(factory)的接口,能够维护不同Bean及其依赖的注册。通过使用T getBean(String name, Class requiredType),你可以提取你的bean的实例。

如下所示,ApplicationContext使你可以读入bean定义并且访问它们:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List userList = service.getUsernameList();

若使用Groovy配置,引导(bootstrapping)看起来很相似。它有不同的Groovy-aware的上下文实现类(但也能处理XML bean定义)。如下所示Groovy配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最灵活的变体就是GenericApplicationContext与读入器代表(reader delegate)相结合,例如,如下所示的对于XML文件的XmlBeanDefinitionReader

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

对于Groovy文件你也可以使用GroovyBeanDefinitionReader,如下所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

你可以在同一个ApplicationContext上混合和匹配这样的读入代表(reader delegate),从不同的配置源读取bean定义。

之后你可以使用getBean提取你的bean实例。ApplicationContext接口有少量其它提取bean的方法,但是,理想情况下,你的应用代码应该从不使用它们。事实上,你的应用程序代码中根本不应该有对getBean()方法的调用,因此对Spring API完全没有依赖性。例如,Spring的web框架的整合提供了对于各种web框架组件的依赖注入,例如控制器(controller)和JSF管理的bean,使你通过元数据(例如autowire注解)在特定的bean上声明一个依赖。

1.3. Bean Overview

一个Spring IoC容器管理一个或多个bean。这些bean由你提供给容器的配置元数据创建(例如XML定义的形式)。

在容器本身内部,这些bean定义被表示为BeanDefinition对象,包含以下元数据:

  • 一个合法的包类名:通常来讲,被定义的bean的实际实现类。
  • bean的行为配置元素,表现为bean在容器中应该如何表现(域、生命周期回调等等)。
  • 对其它bean的引用,这些bean需要做它的工作。这些引用也称之为协作者(collaborator)或依赖(dependencies)。
  • 在新创建对象中设置其它配置——例如,池的大小限制或者管理连接池的bean中的连接数量。

这一元数据翻译成包装每一个bean定义的属性的集合。下表描述了这些属性:

Property Expained in...
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Contructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
Lazy initialization mode Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

除了包含如何创建特定的bean的bean定义之外,ApplicationContext实现允许创建在容器之外(由用户)的已存在的对象的注册。这是通过访问ApplicationContext的BeanFactory的getBeanFactory()方法完成的,此方法返回BeanFactory DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)registerBeanDefinition(..)方法支持这个注册。但是,典型的应用程序只与通过常规bean定义元数据定义的bean一起工作。

Bean元数据和手动供应的单一实例需要被注册的越早越好,以使容器在autowiring和其它自省(introspection)步骤中正确地推理它们。尽管重写现有的元数据和存在的单一实例在某种程度上是支持的,在运行期间(同时对工厂进行实时访问)新bean的注册并不是官方支持的,并且也许会导致并行(concurrent)访问异常、bean容器中不一致的状态或者二者都可能发生。