接上篇:Spring 框架概述
version 5.1.8.RELEASE
这部分参考文档涵盖了 Spring Framework 所有绝对不可或缺的技术。
其中最重要的是 Spring Framework 的控制反转(IoC)容器。在介绍完 Spring 框架的 IoC 容器之后,紧接着全面介绍 Spring 的面向切面编程(AOP)技术。Spring Framework 有自己的 AOP 框架,它在概念上易于理解,并且成功地定位了 Java 企业编程中 AOP 需求的 80% 最佳击球点。
Spring 提供与 AspectJ (目前功能最丰富,也是Java企业领域中最成熟的 AOP 实现)的集成。
1. IoC容器
本章介绍 Spring 的控制反转(IoC)容器。
1.1 Spring IoC 容器和 Bean 简介
本章介绍了 Spring Framework 控制反转(IoC)的实现原理。IoC 也称为依赖注入(DI)。通过这个机制,对象可以通过构造方法参数、工厂方法参数以及通过工厂方法构建或返回的实例上设置的属性来定义它们的依赖关系(即它们使用的其他对象)。然后容器在创建 bean 时注入这些依赖项。这个过程从根本上反转了 Bean 自身通过直接调用构造方法或服务定位模式等机制来控制实例化或定位其依赖的模式,因此叫做控制反转。
org.springframework.beans
和 org.springframework.context
包是 Spring 框架的 IoC 容器的基础。BeanFactory
接口提供了一种能够管理任何类型对象的高级配置机制。ApplicationContext
是 BeanFactory 的子类。它补充的内容有:
- 更容易与 Spring 的 AOP 功能集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用层特定的上下文,例如在 Web 应用程序中使用的 WebApplicationContext
简而言之,BeanFactory
提供了配置框架和基本功能,ApplicationContext
添加了更多针对企业级的功能。ApplicationContext
是 BeanFactory
完整的超集,在本章中仅用它描述 Spring IoC 容器。有关使用 BeanFactory
而不是 ApplicationContext
的更多信息 请参考 BeanFactory。
在 Spring 中,构成应用程序架构并由 Spring IoC 容器管理的对象称为 beans。bean 是一个由 Spring IoC 容器实例化、组装或管理的对象。除此之外,bean 只是应用程序中众多对象之一。Bean 及其之间的依赖关系反映在容器使用的配置元数据中。
1.2 容器概览
org.springframework.context.ApplicationContext
接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据获取有关需要实例化、配置和组装对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示,通过它可以表示构成应用的对象以及对象之间丰富的依赖关系。
Spring 提供了多种 ApplicationContext
接口实现。在传统单机应用中,通常会创建一个 ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的实例。虽然 XML 是定义配置元数据的传统格式,但你也可以通过提供少量 XML 配置声明容器启用对其他元数据格式的支持后使用 Java 注解或代码作为元数据格式。
在大多数应用程序方案中,不需要显式使用代码来实例化 Spring IoC 容器实例。例如,在 Web 应用程序场景中,应用程序文件 web.xml 中一个 8 行左右的 web 描述模板 XML 就足够了(请参阅 Web 应用程序快速实例化 ApplicationContext)。如果你使用 Spring Tool Suite(一个基于 Eclipse 的开发环境),只需点击几下鼠标或按几下键盘即可轻松创建此模板配置。
下图是 Spring 工作原理的高级视图。应用程序类与配置元数据相结合,在 ApplicationContext
创建并初始化之后,即可拥有完全配置且可执行的系统或应用程序。
1.2.1 配置元数据
如上图所示,Spring IoC 容器使用一系列配置元数据。这些配置元数据描述了Spring 容器在应用程序中如何实例化,配置和组装对象。
传统配置元数据使用简单直观的 XML 格式,本章大部分内容也是用 XML 来表达 Spring IoC 容器的关键概念和功能。
XML 不是唯一的配置元数据格式。Spring IoC 容器与实际编写配置元数据的格式完全解耦。目前,许多开发人员为其 Spring 应用程序选择基于 Java 的配置。
有关在 Spring 容器中使用其他形式的元数据的信息,请参阅:
- 基于注解的配置:Spring 2.5 引入了对基于注解的配置元数据的支持。
- 基于Java的配置:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能成为 Spring Framework 核心的一部分。因此,你可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参阅
@Configuration
,@Bean
,@Import
,和@DependsOn
注解。
Spring 配置信息由至少一个(通常不止一个) 必须由容器进行管理的 bean 定义组成。基于 XML 的配置元数据将这些 bean 配置为顶级元素 <beans/>
内的 <bean/>
元素。基于 Java 的配置通常在使用 @Configuration
注解的类中使用带有 @Bean
注解的方法。
这些 bean 定义对应构成应用程序的实际对象。我们一般会定义服务层对象,数据访问对象(DAO),展示层对象(例如 Struts Action 实例),基础结构对象(例如 Hibernate SessionFactories、JMS Queues 等)。一般不会在容器中配置细粒度的域对象,因为通常由 DAO 和业务逻辑负责创建和加载域对象。然而,你可以使用 Spring 集成 AspectJ 来配置非 IoC 容器创建的对象。请参阅使用 Spring 和 AspectJ 进行域对象的依赖注入。
以下示例显示了基于 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
https://www.springframework.org/schema/beans/spring-beans.xsd\">
<bean id=\"...\" class=\"...\">①②
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id=\"...\" class=\"...\">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
① id 属性是一个标识单个 bean 定义的字符串
② class 属性使用完整的类名定义 bean 的类型
id 属性的值表示协作对象。在此示例中未包含用于引用协作的对象的 XML。有关更多信息,请参阅依赖。
1.2.2 实例化容器
提供给 ApplicationContext 构造函数的位置路径是一个资源字符串,它允许容器从各种外部资源加载配置元数据,例如本地文件系统、Java 环境变量等。
ApplicationContext context = new ClassPathXmlApplicationContext(\"services.xml\", \"daos.xml\");
在了解了 Spring 的 IoC 容器之后,你可能想要了解有关 Spring
资源
抽象化(参考资源
描述)的更多信息,特别是Resource
路径用于构建应用程序上下文(请参考应用程序上下文和资源路径),它提供了一种便捷的机制从 URI 语句中定义的位置读取 InputStream。。
以下示例展示了服务层对象配置文件(services.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
https://www.springframework.org/schema/beans/spring-beans.xsd\">
<!-- services -->
<bean id=\"petStore\" class=\"org.springframework.samples.jpetstore.services.PetStoreServiceImpl\">
<property name=\"accountDao\" ref=\"accountDao\"/>
<property name=\"itemDao\" ref=\"itemDao\"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
以下示例展示了数据访问对象文件(daos.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
https://www.springframework.org/schema/beans/spring-beans.xsd\">
<bean id=\"accountDao\"
class=\"org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao\">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id=\"itemDao\" class=\"org.springframework.samples.jpetstore.dao.jpa.JpaItemDao\">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的示例中,服务层由 PetStoreServiceImpl
类和两个数据访问对象 JpaAccountDao
和 JpaItemDao
(基于 JPA 对象关系映射标准)组成。property
元素的 name
属性指的是 JavaBean 属性的名称,ref
属性指向另一个 bean 定义的名称。元素 id
和 ref
之间的这种联系表达了协作对象之间的依赖关系。有关配置对象的依赖关系的详细信息,请参阅依赖关系。
编写基于XML的配置元数据
通常,每个单独的 XML 配置文件都对应着架构中的逻辑层或模块,让 bean 定义在多个 XML 文件中生效会非常有用。
如上一节中所示,应用程序上下文构造函数可以使用多个 Resource 位置,它可以从这些 XML 片段中加载 bean 定义。另外也可以使用一个或多个 <import/>
元素从其他文件加载 bean 定义。以下示例展示了如何执行此操作:
<beans>
<import resource=\"services.xml\"/>
<import resource=\"resources/messageSource.xml\"/>
<import resource=\"/resources/themeSource.xml\"/>
<bean id=\"bean1\" class=\"...\"/>
<bean id=\"bean2\" class=\"...\"/>
</beans>
在前面的例子中,从三个文件中加载外部 Bean 定义,分别是 services.xml、messageSource.xml 和 themeSource.xml。对于执行导入的定义文件来说,所有路径都是相对路径,因此 services.xml 必须与执行导入的文件位于相同的目录或环境变量, messageSource.xml 和 themeSource.xml 必须位于导入文件路径下方的 resources 目录中。正如你所见,前边的斜杠会被忽略掉。鉴于提供的都是相对路径,所以最好不要使用斜杠。这些文件中包括根据 Spring Schema 定义的正确的 XML Bean 在内的内容都会被导入,包括顶级元素 <beans/>
。
虽然可以使用相对路径“../”引用父目录中的文件,但不建议这样使用,因为这样做会使得当前应用依赖程序之外的文件。非常不建议使用
classpath:
URL(例如,classpath:../services.xml)引用文件,因为运行时解析过程会选择“最近”的环境变量根目录,然后查找其父目录。环境变量配置的更改可能导致目录选择不正确。可以使用完整的资源位置替代相对路径,例如,file:C:/config/services.xml 或 classpath:/config/services.xml。然而需要注意应用程序的配置将会与特定的绝对路径耦合。通常最好为这些绝对路径保持间接联系,例如通过在运行时通过“$ {...}”占位符替代 JVM 系统属性。
命名空间本身提供了导入指令的功能。Spring 提供的一系列 XML 命名空间中提供了除普通 bean 定义之外的其他配置功能,例如 context 和 util 命名空间。
Groovy Bean 定义 DSL
作为外化配置元数据的另一个示例,bean 定义也可以在 Spring 的 Groovy Bean 定义 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 使用容器
ApplicationContext
是一个高级工厂接口,主要负责维护不同 bean 及其依赖项的注册。通过使用 T getBean(String name, Class<T> 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<String> userList = service.getUsernameList();
使用 Groovy 配置时,初始化程序看起来非常相似。不同的是使用了支持 Groovy 的上下文实现类(也支持 XML bean 定义)。以下示例展示了 Groovy 配置:
ApplicationContext context = new GenericGroovyApplicationContext(\"services.groovy\", \"daos.groovy\");
最灵活的使用方式是 GenericApplicationContext
与读取器委派结合使用,例如针对 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
中混合使用此类读取器委托, 从不同的配置源读取 bean 定义。
你可以使用 getBean
方法来获取 Bean 实例。ApplicationContext
接口还有一些其他方法可以获取 bean,但理想情况下你的应用程序不应该使用它们。实际上,你的应用程序代码根本不应该调用 getBean()方法,也应该不依赖于 Spring API。例如,Spring 集成的 Web 框架为各种 Web 框架组件(如控制器和 JSF 托管的 bean)提供依赖注入,以便通过元数据声明对特定 bean 的依赖关系,例如自动装配注解。