【Spring官方文档原文理解翻译-持续更新中】
- Part III. Core Technologies
Part III. Core Technologies
这部分的参考文档覆盖了 Spring 的所有核心技术。
其中最重要的就是 Spring 框架的 IoC 容器。继彻底处理的 Spring 框架的 Ioc 容器之后,就是全面覆盖的 Spring 的 AOP 技术 。Spring 有自己的 AOP 框架,不仅概念上易于理解,而且成功解决了 Java 企业编程中 80% 的 AOP 需求。
本文覆盖了 Spring 的 AspectJ 集成 (在功能方面是最丰富的并且在 Java 企业空间中是最成熟的 AOP 实现)
-
Chapter 6, The IoC container
-
Chapter 7, Resources
-
Chapter 8, Validation, Data Binding, and Type Conversion
-
Chapter 9, Spring Expression Language (SpEL)
-
Chapter 10, Aspect Oriented Programming with Spring
-
Chapter 11, Spring AOP APIs
6. The IoC container
- Introduction to the Spring IoC container and beans
6.1. Introduction to the Spring IoC container and beans
本章覆盖了 IoC (控制反转:Inversion of Control ) 的实现原理。IoC 也叫做 DI (依赖注入:Dependency Injection)。这是对象借此定义它们的依赖的过程 ,也就是说,他们使用的其他对象 (其他对象:这里指对象的依赖) 仅通过构造器参数、工厂方法参数和构造好地或工厂方法返回地实例对象的属性 (这里指定位依赖的方式),当创建 bean 时容器就开始注入那些依赖,这一过程是根本地反转,因此才被叫做 控制反转
—— 应用自身控制实例化或者是类似于服务定位器模式这样的机制直接通过类的构造来定位它们自己的依赖。
org.springframework.beans
和 org.springframework.context
包是 IoC 容器的基石。BeanFactory 接口提供了 可以管理任何对象类型 的先进配置机制,而 ApplicationContext 是 BeanFactory
的子接口,对于消息资源处理(用于国际化),事件发布和类似于在网络应用中使用的 WebApplicationContext
的应用层特定上下文而言,添加 Spring 的 AOP 功能更方便。
长话短说,BeanFactory
提供框架配置和基础功能,而 ApplicationContext
则添加更多企业特有功能。ApplicationContext
是 BeanFactory
的一个完整超集,在 这一章节专门用于描述 Spring IoC
容器。有关于使用 BeanFactory
而不是 ApplicationContext
的详细信息,请查看 6.16 The BeanFactory
在 Spring 框架中,构成应用程序主干并且被 IoC 容器管理的对象叫做 bean。 bean 就是被 IoC 容器实例化、组装和管理的对象。 不然,bean 只不过是应用程序中的普通对象,bean 和其依赖的关系体现在容器使用的元数据数据配置中。
6.2. Container overview
org.springframework.context.ApplicationContext
接口作为 Spring 的 IoC 容器有责任实例化、配置和组装上述 bean。 容器通过读取元数据配置获取实例化、配置和组装的相关信息。元数据配置可以使用 XML 文件、Java 注解和 Java 代码。此外,还允许表示构成应用程序的对象以及他们之间复杂的依赖关系。
部分 ApplicationContext
接口的实现可以开箱即用,独立的应用程序中通常创建 ClassPathXmlApplicationContext 或者是 FileSystemXmlApplicationContext 的实例。尽管 XML 是定义配置元数据时使用的传统格式,但是通过极少的 XML 配置可以声明支持额外的元数据格式,让容器使用 Java 注解或代码。
在大多数应用程序的场景中,实例化一个或多个 Spring IoC 容器不需要清晰的用户代码。例如,在网络应用场景下,应用程序的 “ web.xml ” 文件中,八行左右的 XML 网络描述符模板通常就够用了 (详细信息请查看 “ Convenient AppliationContext instantiation for web applications ” 。如果您使用 Spring Tool Suite (Spring工具箱) 的 Eclipse 驱动开发环境,只需要轻点鼠标或者按键,就能轻松创建这种模板配置。
下面是一张 Spring 的工作示意图,应用类与配置元数据组合在一起,以便 ApplicationContext
的创建和初始化,然后您就有了完全配置和可执行的系统或应用程序。
Configuration metadata
如上图所示,这是 IoC 容器使用元数据配置的方式。这个元数据配置说明了,作为一个应用软件开发者,应该直接告诉容器如何实例化、配置和组装应用程序中的对象。
简单直观的 XML 格式元数据配置是传统做法。当前章节的大部分内容都使用元数据配置来解说 IoC 容器的关键概念和特性。
Note
XML 不是唯一的元数据配置方式,
Spring IoC
容器的元数据配置的写法是完全解耦的。现阶段大多数开发者的 Spring 应用程序都选择 基于Java 的配置方式
有关于使用其他形式的 Spring 容器元数据格式请查看:
-
基于注解的配置:Spring2.5 引进了对基于注解的元数据配置的支持
-
基于Java 的配置方式:从 Spring3.0 开始,Spring 的 Java 配置项提供的许多功能成为了 Spring 框架的核心部分,因此您可以使用 Java 代码而不是XML文件在应用程序类中定义外部 bean,要使用这些新的功能,请查看
@Configuration
、@Bean
、@Import
和@DependsOn
Spring 配置由一个或多个必须被容器管理的 bean 定义组成,XML元数据配置表现出这些 bean 配置就类似于顶级的 < beans / > 元素中的 < bean / >,而典型的 Java 配置会在 @Configuration
配置类中使用带有 @Bean
注解的方法
这些 bean 定义相当于真正构成应用程序的对象。通常我们会定义服务层对象、数据访问层(DAO)对象、 引入对象如:Struts 的 Action实例、基础结构对象如:Hibernate SessionFactories(Hibernate 会话工厂)、JMS Queue (Java Messaage Service Queue:Java消息服务队列) 等等。不过在容器中没有配置低粒度的领域对象,因为通常来说,DAO 的任务就是用来和业务逻辑一起创建并加载领域对象。然而,使用 Spring 的 AspectJ 同样可以配置非 IoC 容器创建的外部对象,详细信息请查看 Using AspectJ to dependency-inject domain objects with Spring
以下示例展示了 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="..." class="...">
<!-- 有关于 bean 的伙伴和配置,请访问此处 -->
</bean>
<bean id="..." class="...">
<!-- 有关于 bean 的伙伴和配置,请访问此处 -->
</bean>
<!-- 想查看更多 bean 定义,请访问此处 -->
</beans>
id
属性是一个字符串,用于标识单个的 bean
定义,class
属性使用全限定名定义 bean 的类型。id
的值指的是协同对象。此外,本示例中未展示引用协同对象的 XML,详细信息请查看 Dependencies。
Instantiating a container
安装一个 Spring IoC 容器是非常简单的,因为提供给应用程序或者 ApplicationContext 构造器的定位路径实际上就是资源字符串。它允许容器从各种外部资源中加载元数据配置,如本地文件系统、Java CLASSPATH (Java 类路径) 等等。
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "services.xml", "daos.xml"});
Note
当了解 Spring IoC 容器后,也许还想了解 Spring 的 Resource 抽象。如第七章资源所述,它提供了一个方便的机制,可以读取 URI 语法中定位的输入流。 这里特别指出的是,资源路径可以用于构造应用程序上下文就像 7.7 描述的 “ Application contexts and Resource paths ” 。
以下示例展示了服务层对象的 ( 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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 服务 -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- 有关于 bean 的其他伙伴和配置,请访问此处 -->
</bean>
<!-- 有关于更多的服务 bean 定义,请访问此处 -->
</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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- 有关于 bean 的其他伙伴和配置,请访问此处 -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- 有关于 bean 的其他伙伴和配置,请访问此处 -->
</bean>
<!-- 有关于数据访问对象的更多 bean 定义,请访问此处 -->
</beans>
在以上示例中,服务层由 PetSoreServiceImpl 类和 JpaAccountDao 及 JpaItemDao 两个数据访问类型对象组成 (基于 JPA 对象关系映射标准) 。property name
元素引用 JavaBean 的属性名,而 ref
元素引用另一个 bean 定义名称,在 id
和 ref
元素之间的链接表示协同对象之间的依赖关系。对于配置对象依赖项的其他细节,请查看 Dependencies。
Composing XML-based configuration metadata
基于 XML 的配置元数据对于多重 XML 文件 bean 定义空间是十分有用的,一般来说,独立的 XML 配置文件体现了逻辑层或架构模块。
您可以使用应用上下文构造器从所有的 XML 片段中加载 bean 定义,这就是构造器的多重 Resource 定位,就像之前的代码片段所展示的一样,使用一个或多个 < 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 定义需要从 service.xml、messageSource.xml 和 themeSource.xml 三个文件中加载,也就是说,外部 bean 需要从与其的关联的所有路径定义文件中进行导入,因此 service.xml 必须在相同的目录下或者类路径下导入,而且 messageSource.xml 和 themeSource.xml 必须在 resources 导入文件的位置下。如您所见,它会忽略前导斜杠,但是考虑到这些路径是关联的,最好不要使用斜杠。正在导入的文件内容包含顶级的 < bean / > 元素,依据 Spring 的方案而言,它一定是行之有效的 XML bean 定义。
Notes
可以但不推荐在父目录下使用关联的 ” …/ “ 路径去引用文件。这样做会在当前的应用中创建外部文件依赖。同样地,这种引用方式也不推荐用于 “ classpath ” URLs (例如:“ classpath:…/services.xml ”), 运行时环境会选择 “ 最近的 ” classpath根路径并且查看它的父目录。Classpath配置的变更也许会导致选择一个不同的、错误的目录。
您可以始终使用完全限定的资源位置而不是关联路径:例如,“ file:C:/config/services.xml ” or " classpath:/config/services.xml "。无论如何,您要知道,您这样做是将您的应用耦合到指定绝对位置。通常来说,我们最好对这样的绝对目录保持间接性,例如,通过 “ ${} ” 占位符在运行时根据 JVM 系统属性进行解析
Using the Container
ApplicationContext
是一个高级工厂接口,用于维护不同 bean 并且注册它们之间的依赖关系。使用方法 -> T getBean(String name, Class<T> requiredType)
可以获取 bean 实例。
ApplicationContext
能够读取 bean 定义并且访问它们,如下所示:
// 从配置文件中加载应用上下文并且创建和配置 bean
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
// 获取配置 bean 实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用 bean 配置实例
List<String> userList = service.getUsernameList();
您可以使用 getBean() 方法获取 bean 实例。ApplicationContext
接口很少有其他方法用于获取 bean,但是理论上您的项目代码应该从未使用过它们。实际上,您的项目代码应该根本就没有调用过 getBean() 方法并且没有依赖过 Spring APIs。举个例子,Spring 集成的网络框架,为可变的网络框架类提供依赖注入。例如:控制器和 JSF 管理的 bean
6.3 Bean overview
Spring IoC 容器管理一个或多个bean,这些 bean 是基于容器中的配置元数据来创建的。 例如,XML 表格中的 < bean / > 定义。
在容器内部,这些 bean 定义代表了 BeanDefinition 对象,其中包含(其他信息)以下元数据:
- 一个限定包类名:通常是正在定义的 bean 的实现类
- Bean 行为配置元素,他说明了 bean 在容器中应该怎么做(范围,生命周期,回调等等)
- 引用了完成其工作所需要的其他 bean;这些引用会调用同伴或者依赖
- 其他配置项会在新创建的对象中设置,例如,在一个管理连接池的 bean 中使用的连接数,或者是连接池上限大小
元数据转换为一套组成 bean 定义的属性
Table 6.1. The bean definition
Property | Explained in… |
---|---|
class (类) | “ Instantiating beans ”(实例化 bean) |
name (名称) | “Naming bean”(命名 bean) |
scope (范围) | 6.5节的“Bean scopes” (bean 的作用范围) |
constructor arguments 构造器参数 | “Depandency Injection” (依赖注入) |
properties (属性) | “Depandency Injection”(依赖注入) |
autoworing code (自动处理模式) | “Autowiring collaborators”(自动处理协同器) |
lazy-initialization mode (懒加载模式) | “Lazy-initialization beans”(懒加载 bean) |
initialization method (初始化方法) | “Initialization callbacks”(初始化方法回调) |
destruction method (回收方法) | “Destruction callbacks”(回收方法回调) |
除了包含如何创建特定 bean 信息的 bean 定义之外,ApplicationContext
实现也允许通过用户注册创建在外部容器中的现有对象。通过方法 getBeanFactory()
返回 BeanFactory 的实现 DefaultListableBeanFactory
访问 ApplicationContext的BeanFactory 来实现。DefaultListableBeanFactory
支持通过registerSingleton(..)
方法和registerBeanDefinition(..)
方法来注册,然而,
典型的应用程序仅通过 bean 定义元数据定义 bean。
Note
Bean 元数据和其手动提供的单例模式实例需要尽可能早注册,以便于容器在自动装配和其他反射步骤中正确使用它们。然而 Spring 在某种程度只支持重写现存的元数据和单例模式实例,不支持在运行时注册一个新的bean,否则可能会导致并发访问异常或者在 bean 容器中会出现不一致的状态
Naming beans
每个 bean 都有一个或多个标识符(标识符:bean的名称)。这些标识符必须在其承载的容器内是唯一的。一个 bean 通常有且只有一个标识符,但是如果需要多个,其他的标识符名称就是别名。
基于 XML 的配置,使用 id
和 (或) name
属性指定 bean 的标识符。id
属性允许您指定一个准确的 id
。按照惯例这些名称都是包含文字和数字的 (‘myBean’, ‘fooService’, 等等),但是也可能包括指定的字符。如果您想为 bean 定义其他的别名,您需要在 name
属性中指定它们,并通过逗号,分号或者是空格隔开。作为历史性说明,Spring 3.1 之前的版本中会将 id
属性作为 xsd:ID 类型,约束了可能的字符。但从 3.1 版本开始,它定义为一个 xsd:string 类型。请注意,bean id 的唯一性尽管不再用 XML 解析器强制执行,但是仍然由容器进行强制执行。
您不需要为 bean 提供 name 或者 id 属性。如果没有特别指定的 name 或者 id,容器为 bean 生成一个唯一的 name。无论如何,如果您想要使用 name 引用该bean,请通过使用ref
元素或者 服务定位器 样式查找,且必须提供一个 name。如果无法提供,那么不提供 name 的理由需要与 内部bean 和 自动装配的同伴 有关。
Bean 命名约定
约定指的是当命名 bean 时使用的标准 Java 规范。换句话说,bean 名称以小写字母开头。从那时起,就使用小驼峰法了。像这样的命名就会是 “
accountManager
” 、 “accountService
” 、“userDao
” , “loginController
” 等等。一致性的 bean 命名可以让您的配置易于阅读和理解,如果用到了 Spring AOP,在应用一组名称相关的 bean 时,它将会有很大帮助。
Aliasing a bean outside the bean definition
在 bean 定义中,通过仅限一个的 id
属性和任意数量 name
属性的名称组合,可以为 bean 提供多个名称。这些名称就相当于是同一个 bean 的别名,在某些场景下非常实用。例如,允许应用程序中的组件为一个特定于该组件的 bean 名称引用一个共同的依赖。
指定 bean 的所有的别名并不总是足够的,有时希望给在其他地方定义的 bean 引入一个别名。在大型系统中配置会分散在每个子系统中,这是常有的情况,每个子系统各有自己的一套对象定义的方式。如果是基于 XML 的配置元数据,可以使用 < alias / > 元素。
<alias name="fromName" alias="toName"/>
在这种情况下,同一容器中的 bean 会被指定为 fromName
,也可以使用别名定义,将其作为 toName
引用。
例如,子系统 A 的配置元数据引用名为 subsystemA-dataSource
的数据源,而子系统 B 的配置元数据也许会引用为 subsystemB-dataSource
的数据源。当组合使用这两个子系统的主应用程序时,主应用程序通过 myApp-dataSource
引用数据源。
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在,每个组件和主应用通过独一无二的名称引用数据源并且保证不会与其他的定义(定义:指有效创建的命名空间)发生冲突,但是它们指的是同一个bean
Java-configuration
如果您使用基于 Java 的配置,那么
@bean
注解用于提供别名,详细信息请查看 “ Using the @Bean annotation ”
Instantiating beans
bean 定义本质上是创建一个或多个对象的 “ 配方 ”。当需要时,容器查看 bean 的 “ 配方 ”,然后通过 bean 定义使用封装的配置元数据,创建(或获取)一个实例对象。
如果您使用基于 XML 的配置元数据,请在 < bean / > 元素的 class
属性中指定对象的类型(或 class)。class
属性,在内部的 BeanDefinition 实例中,通常是必须的类字段。(其他情况请查看 “ Instantiation using an instance factory method ” 和 6.7 的 “ Bean definition inheritance ”)
有两种方式可以使用 Class 属性:
- 通常来说,容器指定的 bean
class
属性直接通过反射直接调用自身的构造器来创建 bean ,等同于new operator()
的 Java 代码 - 实际指定的
class
属性包含创建对象的静态工厂方法 ,在极少数的情况下,在类创建的 bean 下,容器会调用静态工厂方法。从调用的静态工厂方法中返回的对象类型,可能是相同的 class 或者完全就是另一个 class。
内部类名称。 如果您想为静态嵌套类配置 bean 定义,必须使用嵌套类的二进制名称
例如,如果您
com.example
包中的有一个叫做Foo
的类,并且Foo
中有一个叫做 Bar 的静态嵌套类,bean 定义中 ‘class’ 属性的值将会是com.exaple.Foo$Bar注意:在名称中一定要使用 $ 符号将嵌套类与外部类分离
Instantiation with a constructor
当您通过构造器方法创建 bean 时,所有正常的类都可用且兼容 Spring。也就是说,正常开发的类不需要实现任何特定的接口或以特定方式进行编码。只需指定对应的 bean 类。 但是,这取决于 bean 使用的是什么类型的 IoC,(如果您嫌麻烦的话)只需要默认(空的)的构造器就行了。
Spring IoC 容器不限于管理真实的 bean,它近乎可以管理所有您想要它管理的类。 大多数的 Spring 用户更喜欢使用只有默认构造器(无参)以及可以根据容器中的属性建模对应的 setter
和 getter
方法的 JavaBean,容器中也可以有很多外来的、非 bean 类型的 class。举个例子,如果您需要不遵守 Javabean 规范的旧版连接池,Spring 也可以将其管理地井井有条。
您可以指定基于 XML 的配置元数据的 bean 类,如下所示:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造器 (如果需要) 提供参数的机制和实例化设置对象的实例属性的更多信息,请查看 Injecting Dependencies
Instantiation with a static factory method
当需要定义创建静态工厂方法的 bean 时, class
属性可以指定类包含的 static
工厂方法,同时另一个名为 factory-method
(工厂方法) 的属性能指定自身的工厂方法名称。您能够调用这个方法(稍后将作为可选参数进行描述)并且返回一个存活对象,随后您可以将其当做是通过构造器创建的。这种 beand 定义的一种用途是在遗留代码中调用 static
工厂。
下列 bean 定义指定了通过工厂方法调用创建的 bean,这种定义不会指定返回对象的 (class) 类型,仅能指定包含工厂方法的类,在这个示例中,createInstance()
方法必须是静态的。
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {
}
public static ClientService createInstance() {
return clientService;
}
}
有关于向工厂方法提供参数的机制和在对象创建后设置对象实例属性的信息,请查看 Dependencies and configuration in detail
Instantiation using an instance factory method
类似于通过 静态工厂方法 进行实例化的手段,使用实例工厂方法也可以从容器中调用现有 bean 的非静态方法从而创建一个新的 bean 实例。想要使用这种机制,要将 class
属性置空,并且在 factory-bean
属性中指定当前 (或父类 / 祖先) 容器中的 bean 名称,容器中会包含用于创建对象的实例方法。此外,还需使用 factory-method
属性设置自身的工厂名称。
<!-- 工厂 bean 包含 createInstance() 方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 通过定位器 bean 注入任何依赖 -->
</bean>
<!-- 通过工厂 bean 创建的 bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator() {
}
public ClientService createClientServiceInstance() {
return clientService;
}
}
独立的工厂类也可以包含多个工厂方法,如下所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 通过定位器 bean 注入任何依赖 -->
</bean>
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
<bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
private DefaultServiceLocator() {
}
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种行为说明工厂方法可以通过依赖注入 (DI) 管理和配置,详细信息请查看 Dependencies and configuration in detail
Note
在 Spring 官方文档中,工厂 bean 说的是 在 Spring 容器中配置的 bean ,它会通过 实例 或 静态 工厂方法 来创建对象。相比之下,
FactoryBean
则指的是 Spring 指定的 FactoryBean
6.4 Dependencies
典型的企业应用程序不只由一个单独的对象构成(或者说是 Spring 中的 bean),即便是最简单的应用程序也有很少的对象,它们协同工作最终形成用户所看到的连贯的、完整的应用程序。 下个章节解释了如何从多个独立的 bean 定义到完整实现的应用程序,其中的对象协同工作实现了目标。
Dependency Injection
依赖注入 (DI) 是对象以此定义它们的依赖的过程,也就是说,他们使用的其他对象 (其他对象:这里指对象的依赖) 仅通过构造器参数、工厂方法参数和构造好地或工厂方法返回地实例对象的属性 (这里指定位依赖的方式),当创建 bean 时容器就开始注入那些依赖,这一过程是根本地反转,因此才被叫做 控制反转
—— 应用自身控制实例化或者是类似于服务定位器模式这样的机制直接通过类的构造来定位它们自己的依赖。
当对象提供他们的依赖时,使用 DI 原则上使得代码更清晰,解耦效果更好。对象不查找他们的依赖,此外也不知道依赖的具体类型或位置。 照这样来看,类的测试会更简单,特别是当依赖项位于接口或抽象基类时,允许 unit 测试使用存储或模拟实现。
DI 存在于两个主要变体中,Constructor-based dependency injection 和 Setter-based dependency injection
Constructor-based dependency injection
基于构造器的依赖注入通过容器调用具有多个参数的构造器方法,每个参数都代表了一个依赖项。 这与使用固定参数的 static
工厂方法构造的 bean 是近乎等价的。这场讨论说明了,在处理参数上,构造器和 static 工厂方法的行为是类似的,两者并没有什么区别。以下示例展示了仅通过构造器进行依赖注入的类。注意,这个类平平无奇,是一个无容器指定任何接口、基类或注解的依赖的 POJO 对象。
public class SimpleMovieLister {
// SimpleMovieLister 依赖于 MovieFinder
private MovieFinder movieFinder;
// 构造器利于 Spring 容器注入 MovieFinder 依赖
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 省略实际使用注入的 MoviewFinder 的业务逻辑...
}
Constructor argument resolution
构造器参数匹配当前使用参数的类型,在 bean 定义的构造器参数中如果不存在潜在歧义(参数类型、参数个数和参数顺序不冲突)。那么当实例化bean时,bean 定义中的构造器参数会有序地为 bean 提供适当的构造器方法。思考下面的类:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// write your code here
}
}
代码无歧义,设想 Bar
和 Baz
类也没有继承关系。因此下列正常工作的配置中,< constructor-arg / > 元素不需要指定明确的构造器参数索引 和/或 类型。
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
当引用另一个 bean ,类型是已知的那么就能够进行匹配(如同前面的示例一样)。当使用到简单的类型时,例如,< value > true < / value> 元素,Spring 无法确定值的类型,因此不能按类型进行匹配。思考下面的类:
package examples;
public class ExampleBean {
// 年数计算最终答案
private int years;
// 回答生活,宇宙和一切
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
在上面的场景中,如果使用 type
属性明确地指定了构造器参数的类型,容器会使用类型匹配简单的类型。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
使用 index
属性明确指定 构造器参数的索引。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的歧义之外,指定索引还可以解析有两个相同类型参数的构造器的歧义,请注意,索引是基于零的。
同时,您也可以使用构造器参数名称消除值的歧义:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,使这个工作开箱即用,代码必须使用调试标志进行编译以便于Spring Boot 从构造器方法中查找参数名称。如果不能编译带有调试标志的代码(或不想),可以使用 @ConstructorProperties JDK 注解显式命名构造器参数。示例类必须如下所示:
package examples;
public class ExampleBean {
// 省略的字段
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Setter-based dependency injection
基于 setter 方法的依赖注入,是在调用无参构造或无参 static 工厂方法实例化 bean 之后,通过容器调用 bean 的 setter 方法实现的。
以下示例展示了仅通过使用纯 setter 方法进行依赖注入的类, 这个类是传统的 Java 对象,它是一个无容器指定任何接口、基类或注解的依赖的 POJO 对象。
public class SimpleMovieLister {
// SimpleMovieLister 依赖于 MovieFinder
private MovieFinder movieFinder;
// 构造器利于 Spring 容器注入 MovieFinder 依赖
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 省略实际使用注入的 MoviewFinder 的业务逻辑..
}
ApplicationContext
支持管理的 bean 基于构造器和基于 setter 方法的两种依赖注入方式。此外,它也支持构造器预先注入一些依赖之后,再使用基于 setter 方法进行的依赖注入的手段。 如果以BeanDefinition
的形式配置依赖,它将与 PropertyEditor
实例结合将一种格式转换为其他格式。然而,多数的 Spring 用户并不直接使用这些类(即,以编程方式)反而使用 XML bean
定义、注释组件(即,@Component
注释类,@Controller
注释类等等)、或基于 Java 的 @Configuration
类的 @Bean
方法。这些资源在内部转换为 BeanDefinition
的实例并用于加载完整的 Spring IoC 容器实例。
基于构造器的 和 基于 setter 方法的依赖注入方式哪个好?
由于可以混合使用基于构造器的和基于 setter 方法的依赖注入,因此这是一条很好的经验法则,构造器用于注入强制性依赖以及 setter 方法或配置方法用于注入可选依赖。请注意, 可以在 setter 方法上使用 @Required 注解使属性成为必须的依赖项。
Spring 团队通常提倡使用构造器注入,因为他能够实现应用程序的组件作为不可变对象并且会确保所需依赖不为
null
。此外,构造器注入组件总是在返回客户端(调用)代码中处于完全初始化状态。作为旁注,大量的构造器参数是一种糟糕的代码,这意味着类或许有太多的职责,应该重构代码以更好解决适当的关注点分离问题。setter 方法注入仅主要用于可选依赖项的类中合理的默认值。 否则,代码在任何地方使用的依赖必须进行非空检查。setter 方法注入的一个好处是 setter 方法使该类的对象可以重新配置或重新注入。因此,JMX MBean 管理的 setter 注入用例是令人信服的
使用对特定类最有效的依赖注入方式,有时,当处理未知来源的第三方类时,为您做出最佳选择。例如,如果第三方类没有暴露任何 setter 方法,那么构造器注入也许是唯一可用的依赖注入方式。
Dependency resolution process
容器以如下方式解析执行的 bean 依赖:
ApplicationContext
创建并初始化描述所有 bean 的配置元数据,而配置元数据可以指定为 XML、Java 代码、或注解的形式。- 每个 bean 其依赖关系以属性,构造器参数,或使用的是静态工厂方法而不是普通构造器提供的参数,实际创建 bean 时,会提供这些依赖项。
- 每个属性或构造器参数都要设置值的实际含义或引用容器中的其他 bean
- 每个属性或构造器参数的值从指定格式转换为该属性或构造器参数的实际类型,默认情况下,Spring 转换提供的值,从字符串格式到所有内置类型,例如,
int
、long
、String
、boolean
等等。
Spring 容器在创建时校验每个 bean 的配置,然而,在实际创建 bean 之前, 不会设置 bean 属性。创建容器时默认创建单例范围的并设置为预先实例化(默认值)的 bean。范围定义在 6.5, “Bean scope”,否则的话,只有在请求时才会创建 bean。创造 bean 会导致创建 bean 图, 因为 bean 的依赖项以及它们创建并分配依赖的依赖项(等等)。请注意,这些依赖中不匹配的依赖项可能出现较晚,也就是说,出现在第一次创建受影响的 bean 时…
6.9 Annotation-based container configuration
在配置 Spring 时,注解是否比 XML 更好?
基于注解的配置引入提出了这种方法是否比 XML 更好的问题,简短的回答视情况而定,而长篇大论的回答是每种方法都有它的优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注解在其声明时提供了大量的内容,导致更短更简洁的配置。然而, XML擅长在连接组件时不接触它们的源码或重新编译它们。 一些开发者更喜欢连接组件接触到源码,而其他人则认为带注解的类不再是 POJO(POJO:Plain Ordinary Java Object,意为简单的 Java 对象),此外,配置变的分散且难以控制
无论是什么选择,Spring 可以容纳两种风格的配置甚至可以将他们混合使用。通过它的 Java 配置选项来指定,
事实证明这是值得的。Spring 允许注解通过非侵入式的方式使用,无需接触目标组件源码,并且在工具方面, Spring Tool Suite 支持所有的配置风格。
6.12 Java-based container configuration
Basic concepts: @Bean and @Configuration
新的 Spring Java 配置中的核心组件支持 @Configuration
注释类和 @Bean
注释方法。
@bean
注解常常用于指示一个方法实例化、配置和初始化被 Spring IoC 容器管理的新对象。 对于这些熟悉 Spring 的 < beans / > XML配置的 @Bean
注解而言,它扮演的角色其实和 < bean / > 元素作用是相同的。任何带有 Spring @Component 注解的类都可以和 @Bean 注解一起使用,不过,他们常常和带有 @Configuration
注解的 bean 一起使用。
@Configuration
注释一个类就表示这个类它当前的主要目的是作为 bean 定义的来源。此外,@Configuration
类允许通过以下方式定义内部 bean 依赖,只需在同一类中调用其它的 @Bean
方法。最简单的 @Configuration
类的内容如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
6.16 The BeanFactory
BeanFactory
为 Spring 的 IoC 功能提供底层基础,但它仅用于直接集成第三方的框架。对于大多数 Spring 用户而言,它基本上是属于历史性质的。BeanFactory
以及相关接口,例如 BeanFactoryAware
、InitializingBean
和 DisposableBean
存在的目的是为了向后兼容大多数 Spring 集成的第三方框架,第三方组件常常不能使用更现代的等价组件例如 @PostConstruct
或 @PreDestroy
, 主要为了保持与 JDK1.4 的兼容或避免对 JSR-250 产生依赖。
本节介绍 BeanFactory
和 ApplicationContext
的不同之处以及如何通过经典单例直接访问 IoC 容器
10. Aspect Oriented Programming with Spring
到目前为止,我们在本文中介绍的所有内容都是纯 Spring AOP 的。在这里我们将能看到:如果有需求的话,您可以学习如何使用 AspectJ 编译器 / 编织器代替 Spring AOP ,或者除了 Spring AOP 之外,见识到超越 Spring AOP 单独提供的设备。
Spring 传输微小的 AsepectJ 方面库,这些库文件在你的分布中可独立获取就像 spring-aspects.jar
;您需要为了使用它而添加这个到 classpath 路径下。“ Using AspectJ to dependency inject domain objects with Spring ” 和 “ Configuring AspectJ aspects using Spring IOC ” 讨论了如何注入 AspectJ 使用AspectJ 编译器织入的方面的依赖,