翻译:core 技术
原文地址:https://docs.spring.io/spring/docs/5.0.x/spring-framework-reference/core.html#spring-core
版本:Version 5.0.7.RELEASE
这部分参考文档涵盖了 Spring Framework 不可或缺的所有技术。
其中最重要的是 Spring Framework 的控制反转容器(IoC)。控制反转容器全面覆盖了 Spring 的面向切面编程技术(AOP)。Spring 有自己的 AOP 框架,它易于理解,并且成功地解决了 Java 企业编程中 AOP 需求 80%的问题。
Spring 也支持与 AspectJ 的集成(目前在功能方面最为丰富 - 当然也是 Java 企业领域最成熟的 AOP 实现)。
1.IoC 容器
1.1.介绍 Spring IoC 容器和 bean
本章节涵盖 Spring 对 IoC 的实现原理。IoC 也被称为 DI(依赖注入),它是一个对象通过构造函数参数、工厂方法的参数、工厂方法或者构造器执行后在对象实例上设置的属性来定义自身依赖对象的过程。容器 在创建 bean 时会注入这些依赖关系。这个过程基本上是相反的,因此名为控制反转(IoC),bean 自身通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖关系的实例化。
org.springframework.beans
包和 org.springframework.context
包是 spring IoC 的基础。BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是 BeanFactory 的一个子接口。它增加了与 Spring AOP 功能更容易的集成; 消息资源处理(用于国际化),事件发布; 和特定于应用层的上下文(例如,用于 Web 应用程序中的 WebApplicationContext
)。
简言之,BeanFactory 提供了配置框架和基本功能,ApplicationContext 添加了更多企业特定的功能。ApplicationContext 是 BeanFactory 的超集,并在本章中专门用于描述 Spring 的 IoC 容器。有关使用 BeanFactory 而不是 ApplicationContext 的更多信息请参考 The BeanFactory。
在 Spring 中,构成应用程序的主干和由 Spring IoC 容器管理的对象称为 bean。bean 是一个由 Spring IoC 容器管理的对象,被其实例化、组装。除此之外,bean 只是应用程序中众多对象中的一个。Bean 和它们之间的依赖关系反映在配置中并被容器使用。
1.2.容器概述
接口 org.springframework.context.ApplicationContext
表示 Spring IoC 容器,并负责实例化,配置和组装上述 bean。容器通过读取配置元数据获取要实例化,配置和组装的对象的信息。配置元数据用 XML,Java 注解或 Java 代码表示,它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
几个对 ApplicationContextSpring 接口的实现都提供了对 Spring 的开箱即用。在独立的应用程序中,通常会创建一个 ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的实例。虽然 XML 是用于定义配置元数据的传统格式,但您可以通过提供少量的 XML 配置来指示容器使用 Java 注解或代码作为元数据格式。
在大多数应用场景中,用户代码不需要实例化 Spring IoC 容器的一个或多个实例。例如,在 Web 应用程序场景中,web.xml 中简单八行(或多行)样板代码通常就足够了(请参阅这里)。如果您使用的是 Eclipse 的 Spring 工具套件 ,只需点击几下鼠标或按键即可轻松创建此样板配置。
下图是 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 配置为下的元素。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
http://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.实例化容器
实例化 Spring IoC 容器非常简单。提供给 ApplicationContext 构造函数的位置路径实际上是个字符串,它指明容器从各种外部资源(如本地文件系统,Java classpath 等)加载配置元数据的路径。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
注:在了解了 Spring 的 IoC 容器之后,您可能想要了解有关 Spring Resource 抽象的更多信息 ,如 Resource 中所述,它提供了一种从 URI 中定义的位置读取 InputStream 的便捷机制。特别是,Resource 路径用于构建应用程序上下文,如 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">
<!-- 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
http://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 和 dao 类型的两个数据访问对象 JpaAccountDao 和 JpaItemDao(基于 JPA 对象/关系映射标准)组成。该 property name 元素是指 JavaBean 属性的名称,ref 元素指的是另一个 bean 定义的名称。元素 id 和 ref 之间的这种联系表达了协作对象之间的依赖关系。有关配置对象的依赖项的详细信息,请参阅 依赖项 Dependencies。
编写基于 XML 的配置元数据
让 bean 定义跨越多个 XML 文件会很有用。通常,每个单独的 XML 配置文件都代表架构中的逻辑层或模块。
您可以使 application context 构造函数从所有这些 XML 中加载 bean。此构造函数可以接受多个 Resource 位置,如上一节中所示。或者可以使用元素来从其他 xml 文件加载 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 定义(包括顶级元素)。
注:可以(但不建议)使用相对“../”路径引用父目录中的文件。这样做会对当前应用程序之外的文件创建依赖关系。特别是,不建议将此引用用于“classpath:”URL(例如,“classpath:../ services.xml”),其中运行时解析过程选择“最近的”classpath,然后查看其父目录。所以如果 classpath 配置更改则可能导致选择不同的、不正确的目录。
您始终可以使用完全限定的资源路径而不是相对路径:例如,“file:C:/config/services.xml”或“classpath:/config/services.xml”。但是,请注意您将应用程序的配置与特定的绝对位置耦合了。通常最好以间接方式使用绝对位置,例如,通过“${...}”占位符在运行时对 JVM 系统属性解析。
import 指令是 beans 命名空间本身提供的功能。也可以使用 Spring 提供的一系列 XML 命名空间,例如“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 文件。
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 定义):
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 的依赖性。
1.3.Bean 概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的,例如,以 XML 定义的形式 。
在容器本身内,这些 bean 定义表示为 BeanDefinition 对象,其中包含以下元数据(以及其他信息):
-
包限定的类名:通常是正在定义的 bean 的实际实现类。
-
Bean 行为配置元素,说明 bean 在容器中的行为方式(范围,生命周期回调等)。
-
引用 bean 执行其工作所需的其他 bean; 这些引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置设置,例如,在管理连接池的 Bean 中使用的连接数,或池的大小限制。
此元数据被转换为 bean 的一组属性。
表 1. bean 定义
属性 | 解释 |
---|---|
class 类 | 实例化 bean |
name 名称 | 命名 bean |
scope 范围 | bean 范围 |
constructor arguments 构造函数参数 | 依赖注入 |
properties 属性 | 依赖注入 |
autowiring mode 自动装配模式 | 自动装配依赖 |
lazy-initialization mode 延迟初始化模式 | 延迟初始化 |
initialization method 初始化方法 | 初始化回调 |
destruction method 销毁方法 | 销毁回调 |
除了包含有关如何创建 bean 的信息之外,这些 ApplicationContext 实现还允许用户注册在容器外部创建的对象。这是通过访问 ApplicationContext 中 BeanFactory 的 getBeanFactory()方法,它返回一个 DefaultListableBeanFactory 的实现——BeanFactory。DefaultListableBeanFactory 支持通过方法 registerSingleton(..)和 registerBeanDefinition(..)注册外部对象。但是,典型应用程序仅适用于通过元数据定义的 bean。
- 注:需要尽早注册 Bean 元数据和手动提供的单例实例,以便容器在自动装配和执行其他内部步骤期时正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但是在运行时注册新 bean(与对工厂的实时访问同时)并未得到官方支持,并且可能导致 bean 容器中的并发访问异常或产生不一致状态。
1.3.1.bean 命名
每个 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 提供名称或 ID。如果没有显式提供名称或标识,容器会为该 bean 生成唯一的名称。但是,如果要通过名称引用该 bean,则必须通过使用 ref 元素或 Service Locator 查找。不提供名称是为了与使用内部 bean 或自动装配协作者。
Bean命名约定
惯例是在命名 bean 时使用标准 Java 约定作为实例字段名称。也就是说,bean 名称以小写字母开头,从那起就是驼峰式的。这种名称的例子将是 accountManager,accountService,userDao,loginController 等等。
为 bean 命名使您的配置更易于阅读和理解,如果您使用的是 Spring AOP,那么在将通知应用于与名称相关的一组 bean 时,会很方便。
注:通过类路径中的组件扫描,Spring 按照上述规则为未命名的组件生成 bean 名称:实质上,采用简单的类名并将其初始字符转换为小写。但是,在(不常见的)特殊情况下,当有多个字符并且第一个和第二个字符都是大写字母时,原始名称将被保留。这些与 java.beans.Introspector.decapitalize
(Spring 在这里使用的)定义的规则相同。
为 bean 设置别名
在 bean 定义中,您可以为 bean 提供多个名称,方法是一个 id 属性上指定任意数量的其他 name。这些 name 可以是同一个 bean 的等效别名,并且在某些情况下很有用,例如允许应用程序中的每个组件通过使用特定于该组件本身的 bean 名称来引用公共依赖项。
但是,指定实际定义 bean 的所有别名并不总是足够的。有时需要为其他地方定义的 bean 引入别名。在大型系统中通常就是这种情况,配置文件在每个子系统之间分配,每个子系统具有其自己的一组对象定义。在基于 XML 的配置元数据中,您可以使用元素来完成此任务。
<alias name="fromName" alias="toName"/>
在这种情况下,名为 fromName 的 bean 也可以称为 toName。
例如,子系统 A 的配置元数据可以通过名称 subsystemA-dataSource 引用数据源 DataSource。子系统 B 的配置元数据可以通过名称 subsystemB-dataSource 引用数据源 DataSource。在编写使用这两个子系统的主应用程序时,主应用程序通过名称 myApp-dataSource 引用 DataSource 。要使所有三个名称引用您添加到 MyApp 配置元数据的同一对象,请使用以下别名定义
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在,每个组件和主应用程序都可以通过一个唯一的名称引用 dataSource,并保证不与任何其他定义冲突(有效地创建命名空间),但它们引用相同的 bean。
Java配置
如果您使用的是 Java 配置,@Bean 可以使用注释来提供别名,请参阅使用 @Bean 注释了解详细信息。
1.3.2.实例化 bean
bean 的定义(配置)本质上是用于创建一个或多个对象的方法。容器在被询问时查看定义 bean 的方法,并使用定义该 bean 的配置元数据来创建(或获取)实际对象。
如果使用基于 XML 的配置元数据,则要在元素的 class 属性中指定实例化的对象的类型(class)。这个 class 属性在内部是 BeanDefinition 的 Class 属性,一般是必需的。(有关例外,请参阅使用实例工厂方法和 Bean 定义继承进行实例化。)您可以通过以下两种方式之一使用 Class 属性:
-
通常,在容器本身通过反向调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,等同于使用 new 运算符的 Java 代码。
-
要指定包含能被调用以创建对象的 static 工厂方法的实际类,在不太常见的情况下,容器在类上调用 static 工厂方法来创建 bean。从调用 static 工厂方法返回的对象类型可以是同一个类或另一个类。
内部类名
如果要为static嵌套类配置bean,则必须使用嵌套类的二进制名称。
例如,如果你有一个类Foo在com.example包中,并且这个Foo类有一个嵌套的个static类Bar,那么bean定义中的'class'属性值将是:
com.example.Foo$Bar
请注意,使用$字符将嵌套类名与外部类名分开。
使用构造函数实例化
当通过构造方法创建 bean 时,所有普通类都可以使用并与 Spring 兼容。也就是说,正在开发的类不需要实现任何特定接口或以特定方式编码。简单地指定 bean 类就足够了。但是,根据您为特定 bean 使用的 IoC 类型,您可能需要一个默认(空)构造函数。
Spring IoC 容器几乎可以管理您希望它管理的任何类;它不仅限于管理真正的 JavaBeans。大多数 Spring 用户更喜欢实际的 JavaBeans,即只有一个默认(无参数)构造函数和 setter、getter 方法的 java 类。您还可以在容器中使用更多外部的非 bean 样式类。例如,如果您需要使用绝对不符合 JavaBean 规范的旧连接池,那么 Spring 也可以对其进行管理。
使用基于 XML 的配置元数据,您可以按如下方式指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关为构造函数提供参数的机制(如果需要)以及在构造对象后设置对象属性的详细信息,请参阅注入依赖项。
使用静态工厂方法实例化
定义使用静态工厂方法创建的 bean 时,使用 class 属性指定包含静态工厂方法的类,使用 factory-method 指定工厂方法本身的名称。您应该能够调用此方法(使用后面描述的可选参数)并返回一个随后可以将其视为通过构造函数创建的对象。这种 bean 定义的一个用途是在遗留代码中调用静态工厂。
以下 bean 定义指定通过调用 factory-method 创建 bean。该定义未指定返回对象的类型(类),仅指定包含工厂方法的类。在此示例中,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;
}
}
有关向工厂方法提供(可选)参数并在对象创建后设置对象属性的详细信息,请参阅依赖关系和配置。
使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法进行实例化会从容器调用现有 bean 的非静态方法来创建新 bean。要使用此机制,请将 class 属性设为空,并在 factory-bean 属性中指定当前(或父/祖先)容器中包含要调用以创建对象的实例方法的 bean 的名称。使用 factory-method 属性设置工厂方法本身的名称。
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含多个工厂方法,如下所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator 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();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明可以通过依赖注入(DI)来管理和配置工厂 bean 自身。请参阅依赖关系和配置。
注:在 Spring 文档中,工厂 bean(factory bean)指的是在 Spring 容器中配置的 bean,它将通过实例或静态工厂方法创建对象。相反,FactoryBean(注意大写)指的是特定于 Spring 的 FactoryBean。
1.4.依赖(Dependencies)
典型的企业应用程序不包含单个对象(即 Spring 中的 bean)。即使是最简单的应用程序也有一些对象来协同工作,以呈现给最终用户一个完整的应用程序。下一节将介绍如何使多个独立的 bean 对象协作来完成同一个任务。
1.4.1。依赖注入
依赖注入(DI)是一个过程,是对象通过设置构造函数参数、设置工厂方法参数或在返回对象实例后在对象实例上设置属性来定义它们的依赖关系的过程。然后,容器在创建 bean 时注入这些依赖项。这个过程基本上是反向的,因此名称是 Inversion of Control
(IoC),即 bean 本身通过使用类的直接构造器或服务定位器模式来控制其依赖项的实例化。
使用 DI 使代码更清晰,当对象提供其依赖项时解耦更高效。对象不查找其依赖项,也不知道依赖项的位置或类。因此,类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用 stub 或 mock。
DI 存在两个主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 由容器调用具有多个参数的构造函数来完成,每个参数表示一个依赖项。调用具有特定参数的静态工厂方法来构造 bean 几乎也是一样的,本节对处理构造函数参数和处理静态工厂方法的参数做相同讨论。以下示例显示了一个只能通过构造函数进行依赖注入的类。请注意,此类没有什么特别之处,它是一个 POJO,它不依赖于容器特定的接口、基类或注解。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
解析构造函数参数
使用参数的类型进行构造函数参数匹配。如果 bean 定义的构造函数参数中不存在歧义,那么在 bean 定义中定义的构造函数参数顺序就是在实例化 bean 时将这些参数提供给构造函数的顺序。看下面的类:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
假设 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 {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
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>
除了解决多个简单值的歧义之外,指定索引位置还可以解决构造函数具有相同类型的两个参数的歧义。请注意,索引位置从 0 开始。
构造函数参数名称
还可以使用构造函数参数名称进行匹配:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,
要使这种配置开箱即用,必须在启用调试的情况下编译代码
,以便 Spring 可以从构造函数中查找参数名称。如果无法在启用调试的情况下编译代码(或者不希望),则可以使用 @ConstructorPropertiesJDK 注解显式命名构造函数参数。然后,示例类必须如下所示:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于 Setter 的依赖注入
基于 setter 的 DI 是在调用无参构造函数或无参静态工厂方法来实例化 bean 之后,通过容器调用 bean 上的 setter 方法来完成的。
以下示例显示了一个只能使用 setter 注入进行依赖注入的类。这个类是传统的 Java 类。它是一个 POJO,它不依赖于容器特定的接口、基类或注解。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext 对它管理的 bean 支持基于构造器的 DI 和基于 setter 方法的 DI。在通过构造函数方法注入了一些依赖项之后,它还支持基于 setter 的 DI。以 BeanDefinition 与 PropertyEditor 结合的形式配置依赖项,以便将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户不直接用这些类,而是用 XML、注解(@Component、@Controller 等)或在由 @Configuration 注解的 Java 类里使用 @Bean 来定义 bean。然而,这些方法在内部都会转换为 BeanDefinition 实例,然后用于加载整个 Spring IoC 容器实例。
选择基于构造函数还是基于 setter 的 DI
由于您可以混合基于构造函数和基于 setter 的 DI,因此将构造函数用于强制依赖项,将 setter 方法或配置方法用于可选依赖项是一个很好的经验法则。请注意,在 setter 方法上使用 @Required 注解可使属性成为必需的依赖项。
Spring 团队通常提倡构造函数注入,因为它使应用程序组件成为不可变对象,并确保所需的依赖项不为 null。此外,构造函数注入的组件始终以完全初始化的状态返回给客户端(调用)。作为旁注,大量的构造函数参数是一个糟糕的代码设计,暗示该类可能有太多的功能,应该重构以更好地分离类的功能。
Setter 注入应主要仅用于可选依赖项,且该依赖项在类中指定了合理默认值。否则,必须在代码使用依赖项的任何位置执行非空检查。setter 注入的一个好处是 setter 方法使类的对象可以在以后重新配置或重新注入。因此,通过 JMX MBean 进行管理是 Setter 注入的一个引人注目的例子。
使用对特定类最有意义的 DI 方式。有时,在处理没有源代码的第三方类时,选择就很有限了。例如,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是唯一可用的 DI 形式。
依赖处理过程
容器按如下所示对 bean 依赖进行解析:
-
ApplicationContext 创建和初始化配置元数据所定义的全部 bean。配置元数据可以是 XML、Java 代码或注解。
-
对于每个 bean,不同于使用普通构造器,如果使用了属性、构造函数参数或 static-factory 方法,则 bean 的依赖由它们定义。实际创建 bean 时,会将这些依赖项提供给 bean。
-
每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 bean 的引用。
-
作为值的每个属性或构造函数参数,都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将提供的字符串转换成所有的内置类型,比如 int,long,String,boolean 等。
Spring 在创建容器时验证每个 bean 的配置。但是,在实际创建 bean 之前,不会设置 bean 属性。创建容器时会为 Bean 创建单例作用域并设置为预先实例化(默认值)。作用域在 Bean scopes 中定义。否则,仅在请求时才创建 bean。创建 bean 可能会导致创建一系列 bean,因为 bean 的依赖及其依赖的依赖(依此类推)都会被创建和分配。请注意,这些依赖项之间不匹配可能会较晚显示,即首次创建受影响的 bean 时才会显示。
循环依赖
如果主要使用构造函数注入,可能会创建无法解析的循环依赖关系场景。
例如:类 A 通过构造函数注入需要类 B 的实例,而类 B 通过构造函数注入需要类 A 的实例。如果将 A 类和 B 类的 bean 配置为相互注入,则 Spring IoC 容器会在运行时检测此循环引用,并抛出 BeanCurrentlyInCreationException
异常。
一种可能的解决方案是改为由 setter 注入而不是构造函数。或者为避免构造函数注入仅使用 setter 注入。换句话说,尽管不推荐使用,但您可以使用 setter 注入搞定循环依赖关系。
与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖强制其中一个 bean 在完全初始化之前被注入另一个 bean(一个经典的先有鸡还是先有蛋的场景)。
你通常可以信任 Spring 会做正确的事。它在容器加载时检测配置问题,例如检查不存在的 bean 和循环依赖的引用。当实际创建 bean 时,Spring 会尽可能晚地设置属性并解析依赖项。这意味着,如果 spring 容器加载正常,但是在创建该对象或其依赖项时出现问题,则在请求对象时才会生成异常。例如,bean 因缺少属性或属性无效而抛出异常。这可能会使一些配置问题很晚才被发现,所有 ApplicationContext 默认情况下创建单例 bean 并预先实例化。以在实际需要之前创建这些 bean 的一些时间和内存为代价,来换取在 ApplicationContext 创建时发现配置问题。您仍然可以覆盖此默认行为,以便单例 bean 将延迟初始化,而不是预先实例化。
如果不存在循环依赖,当一个或多个协作 bean 被注入依赖 bean 时,每个协作 bean 在被注入依赖 bean 之前完全被配置。这意味着如果 bean A 依赖于 bean B,Spring IoC 容器在调用 bean A 上的 setter 方法之前完全配置好了 bean B。换句话说,一旦 bean 被实例化(如果不是预先实例化的单例),则他的依赖项都已被设置并调用了相关的生命周期方法(如配置的 init 方法或 InitializingBean 方法)。
依赖注入的例子
以下示例将基于 XML 的配置元数据设置基于 setter 的 DI。Spring XML 配置文件的一小部分指定了一些 bean 定义:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
在上面的示例中,setter 被声明为与 XML 文件中指定的属性匹配。下面的示例使用基于构造函数的 DI:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
bean 定义中指定的构造函数参数将用作构造函数 ExampleBean 的参数。
现在考虑这个示例的变体,不使用构造函数,而是告诉 Spring 调用静态工厂方法来返回对象的实例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
静态工厂方法的参数是通过元素提供的,与实际使用的构造函数完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同,尽管在此示例中它是。实例(非静态)工厂方法将以基本相同的方式使用(除了使用 factory-bean 属性而不是 class 属性),因此这里不再讨论。
1.4.2.依赖关系和配置
如上一节所述,您可以将 bean 属性和构造函数参数定义为对其他 bean(协作者)的引用,或者作为内联定义的值。Spring 基于 XML 的配置元数据通过支持和的子元素类型来实现这一目的。
直值(基元,字符串等)
元素的 value 以人类可读的字符串形式指定属性或构造器参数。Spring 的转换服务将这些值从字符串转换为属性或参数的实际类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
以下示例使用 p 命名空间进行更简洁的 XML 配置。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
上面的 XML 更简洁; 但是,除非您在创建 bean 定义时使用 IntelliJ IDEA 或 Spring Tool Suite(STS)等支持自动补充属性 IDE,否则会在运行时而不是设计时才发现拼写错误。强烈建议使用此类 IDE 帮助。
也可以将 java.util.Properties 实例配置为:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring 容器通过使用 JavaBeans PropertyEditor 机制将元素内的文本转换为 java.util.Properties 实例。这是一个很快捷的方式,并且是 Spring 团队少数几个支持在属性上使用嵌套元素的地方之一。
idref 元素
idref 元素只是一种防错方法,可以将容器中另一个 bean 的 id(字符串值而不是引用)传递给或元素。
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
上面的 bean 定义与以下的完全等效(在运行时):
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式优于第二种形式,因为使用 idref 标签允许容器在部署时验证引用的命名 bean 实际存在。在第二个变体中,不对传递给 client bean 的 targetName 属性的值执行验证。只有在 client 实际实例化 bean 时才会发现错别字(很可能是致命的结果)。如果 client bean 是 prototype bean,则只能在部署容器后很长时间才能发现此错误和结果异常。
4.0 beans xsd 不再支持 idref 元素的 local 属性,因为它不再提供常规 bean 引用的值。若要升级到 4.0 架构只需将现有 idref local 引用更改为 idref bean。
与 Spring 2.0 早期的版本相比,元素一个共同的地方是它的值在配置 AOP 拦截器时都在 ProxyFactoryBeanbean 中定义。指定拦截器名称时使用元素可以防止拼写错误的拦截器 ID。
引用其他 bean(协作者)
ref 是或内部的最终元素。在这可以将 bean 的属性设置为对容器管理的另一个 bean(协作者)的引用。引用的 bean 是 bean 的依赖项,其属性将被设置,并且在设置属性之前根据需要按需初始化(如果协作者是单例 bean,它可能已由容器初始化)。所有引用最终都是对另一个对象的引用。作用范围和有效性取决于是否通过 bean,local,或 parent 属性指定其他对象的 ID 或名称。
通过标签的 bean 属性指定目标 bean 是最常用的形式,它允许创建对同一容器或父容器中任何 bean 的引用,而不管它是否在同一个 XML 文件中。bean 属性的值可以与目标 bean 的 id 属性相同,或者选择目标 bean 的 name 属性值。
<ref bean="someBean"/>
通过 parent 属性指定目标 bean 会创建对当前容器的父容器中的 bean 的引用。parent 属性的值可以与目标 bean 的 id 属性相同,也可以选择目标 bean 的 name 属性值,并且目标 bean 必须位于当前 bean 的父容器中。主要在拥有容器层次结构并且希望将现有 bean 包装在父容器中并使用与父 bean 具有相同名称的代理时才使用此属性。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
注:4.0 beans xsd 不再支持 local 该 ref 元素的属性,因为它不再提供常规 bean 引用的值。只需将现有 ref local 引用更改为 ref bean 升级到 4.0 架构。
内部 beans
在或元素内部使用将定义一个所谓的内部 bean。
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部 bean 不需要定义 id 或 name;如果指定了容器也不会使用它作为标识符。容器还会在创建内部 bean 时忽略 scope 属性:内部 bean 始终是匿名的,并且始终使用外部 bean 创建它们。不可能将内部 bean 注入到协作 bean 中,也不能将其放入封闭的 bean 中或者独立地访问它们。
作为一个极端情况,可以从自定义范围来接收销毁回调,例如,对于包含在单例 bean 中 scope 范围是 request 的内部 bean:内部 bean 实例的创建将绑定到包含它的 bean,但是销毁回调允许它活在 request 范围的生命周期里。这不是常见的情况,内部 bean 通常与包含它的 bean 的 scop 范围相同。
集合
使用,,,元素,可以映射 Java 的 Collection 类型 List,Set,Map,Properties。
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
映射 map 的键或值或者 set 的值可以设置成以下任何元素:
bean | ref | idref | list | set | map | props | value | null
合并集合
Spring 容器还支持集合的合并。应用程序开发人员可以定义一个父类型的,,或元素,并有子类型的,,或元素继承和覆盖父集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素覆盖父集合中指定的值。
关于合并的这一部分讨论了父子 bean 机制。不熟悉父子 bean 定义的读者可能希望在继续阅读 之前查看相关部分。
以下示例演示了合并集合:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意在 bean 元素上使用 merge=true 属性。当容器解析并实例化 bean 时,生成的实例有一个集合,其中包含子集合与父集合的合并结果,如下:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子集合的值继承自父集合的元素,子集合的 support 值将覆盖父集合的 support 值。
这一合并行为同样适用于,和集合类型。在元素中,保持有序集合的概念; 父级的值位于所有子级 List 的值之前。在 Map、Set 和 Properties 集合类型中,没有顺序存在。因此,对于容器在内部使用的关联 Map、Set 和 Properties 实现类型的集合类型,没有排序语义。
合并集合的局限性
您不能合并不同的集合类型(例如 Map 和 List),如果您尝试这样做,则会抛出 Exception。merge 属性必须在子集合上定义;在父集合定义是多余的,不会导致所需的合并。
强类型的集合
通过在 Java 5 中引入泛型类型,您可以使用强类型集合。也就是说,例如可以声明一种 Collection 类型,使得它只能包含 String 元素。如果您使用 Spring 依赖注入一个强类型 Collection 到 bean,您可以利用 Spring 的类型转换,以便强类型 Collection 实例的元素在被添加之前转换为适当的 Collection 类型。
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当为注入准备 bean 的 accounts 属性时,通过反射可获得 foo 关于强类型的元素类型的泛型信息 Map<String, Float>。因此,Spring 的类型转换将字符串 9.99,2.75,3.99 转换为实际的 Float 类型。
null 和空字符串
Spring 将属性等的空参数视为空 Strings。以下基于 XML 的配置元数据将 email 属性设置为空值,即 String("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
元素处理 null 的值。例如:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
以上配置等同于以下 Java 代码:
exampleBean.setEmail(null);
带有 p 命名空间的 XML
p 命名空间使您可以使用 bean 元素的属性而不是嵌套元素,来描述属性值或协作 bean。
Spring 支持具有命名空间的可扩展配置格式,这些命名空间基于 XML Schema 定义。本章中讨论的 beans 配置格式在 XML Schema 文档中定义。但是,p 命名空间未在 XSD 文件中定义,仅存在于 Spring 的 core 核心中。
以下示例显示了两个解析为相同结果的 XML 片段:第一个使用标准 XML 格式,第二个使用 p 命名空间。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
该示例显示了 bean 定义中名为 email 的 p 命名空间中的属性。这告诉 Spring 包含一个属性声明。如前所述,p 命名空间没有架构定义,因此您可以将属性的名称设置为配置的名称。
下一个示例包括另外两个 bean 定义,它们都引用了另一个 bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
如您所见,此示例不仅包含使用 p 命名空间的属性值,还使用特殊格式来声明引用属性。第一个 bean 定义用创建从 bean john 到 bean jane 的引用,而第二个 bean 定义用 p:spouse-ref="jane"属性来执行相同的操作。在这种情况下 spouse 是属性名称,而该-ref 部分表示这不是直接值,而是对另一个 bean 的引用。
注:p 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与 Ref 结束的属性冲突,而标准 XML 格式则不然。我们建议您仔细选择您的方法并将其传达给您的团队成员,以避免生成同时使用所有三种方法的 XML 文档。
带有 c 命名空间的 XML
与带有 p 命名空间的 XML 快捷方式类似,Spring3.1 中新引入的 c 命名空间允许使用行内属性来配置构造函数参数,而不是嵌套 constructor-arg 元素。
让我们回顾一下使用 c 命名空间的基于构造函数的依赖注入的示例::
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
c 命名空间使用与 p:one(-ref 标识引用 bean)相同的约定,通过 name 设置构造函数的参数。同样地,它需要声明,即使它没有在 XSD 架构中定义(但它存在于 Spring 核心内)。
对于构造函数参数名称不可用的罕见情况(通常如果编译的字节码没有调试信息),可以使用参数索引:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
注:由于 XML 语法,索引表示法要求存在前导的下划线"_",因为 XML 属性名称不能以数字开头(即使某些 IDE 允许它)。
在实践中,构造函数解析机制在匹配参数方面非常有效,因此除非确实需要,否则我们建议在整个配置中使用名称表示法。
复合属性名称
设置 bean 属性时,可以使用复合或嵌套属性名称,只要除最终属性名称之外的路径下的所有组件都不是 null。请考虑以下 bean 定义。
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
foo bean 具有 fred 属性,该属性具有 bob 属性,bob 具有 sammy 特性,并且最终 sammy 属性被设置为 123。为了使它工作,bean 被构造后 foo 的 fres 属性、fred 的 bob 属性一定不能为 null,否则会抛出 NullPointerException。
1.4.3.使用依赖
如果一个 bean 是另一个 bean 的依赖项,通常意味着将这个 bean 设置为另一个 bean 的属性。可以使用基于 XML 的配置元数据中的元素来完成此操作。但是,有时 bean 之间的依赖关系不那么直接; 例如,需要触发类中的静态初始化程序,如注册数据库驱动程序。在初始化使用 bean 之前,depends-on 属性可以显式强制初始化一个或多个 bean。以下示例使用 depends-on 属性表示对单个 bean 的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个 bean 的依赖关系,请提供 bean 名称列表作为 depends-on 属性的值,使用逗号,空格或分号作为有效分隔符:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
注:depends-onbean 定义中的属性既可以指定单例模式下 bean 的初始化时间依赖性,也可以指定相应的销毁时间依赖性。在给定的 bean 本身被销毁之前,首先销毁由 depends-on 定义的从属 bean。因此 depends-on 也可以控制关闭顺序。
1.4.4.延迟初始化 bean
默认情况下,ApplicationContext 会急切地创建和配置所有单例 bean 作为初始化过程的一部分。通常,这种预先实例化是可取的,因为这样可以立即发现配置或环境变量中的错误,而不是几小时甚至几天后才发现。如果不希望出现这种情况,可以通过将 bean 定义标记为延迟初始化来阻止单例 bean 的预实例化。延迟初始化 bean 告诉 IoC 容器在第一次请求时创建 bean 实例,而不是在启动时创建。
在 XML 中,此行为由元素 lazy-init 上的属性控制; 例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当上面的配置被 ApplicationContext 使用时,lazy 在 ApplicationContext 启动时不会急切地预先实例化,而 not.lazy 会被急切地预先实例化。
但是,当延迟初始化的 bean 是未进行延迟初始化的单例 bean 的依赖项时,ApplicationContext 会在启动时创建延迟初始化的 bean,因为它必须满足单例的依赖关系。也就是把延迟初始化的 bean 注入到不是延迟初始化的单例 bean 中。
您还可以通过使用元素的 default-lazy-init 属性来控制容器级别的延迟初始化; 例如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5.自动装配协作者
Spring 容器可以自动装配协作 bean 之间的关系。您可以通过检查 ApplicationContext 的内容,允许 Spring 自动为您的 bean 解析协作者(其他 bean)。自动装配有以下优点:
-
自动装配可以显著减少对指定属性或构造函数参数的需要。(其他机制在这方面也很有价值,如本章其他地方讨论的 bean 模板。)
-
当您的对象不断演进时,自动装配可以更新配置。例如,如果您需要向类添加依赖项,那么无须修改配置就可以自动满足依赖性。因此,在开发过程中,自动装配是特别有用的,因为不需要在代码库得更稳定的时候取消切换到显式装配的配置。
当使用基于 xml 的配置元数据时,您可以标签的 autowire 属性为 bean 指定 autowire 模式。自动装配功能有四种模式。您可以为每个 bean 指定自动装配的模式。
模式 | 说明 |
---|---|
no | (默认)没有自动装配。Bean 引用必须通过 ref 元素来定义。对于较大的部署,不建议更改默认设置,因为指定协作者提供了更大的控制能力和清晰性。在某种程度上,它记录了系统的结构。 |
byName | 由属性名自动装配。Spring 寻找与需要自动连接的属性同名的 bean。例如,如果一个 bean 的定义被设置为自动装配,并且它包含一个主属性(即,它有一个 setMaster(...)方法),Spring 将会寻找一个名为 master 的 bean,并使用它来设置属性。 |
byType | 如果容器中存在一个属性类型的 bean,则允许该属性被自动装配。如果不止一个存在,就会抛出一个致命的异常,这表明您可能不会使用该 bean 的 byType 自动装配。如果没有匹配的 bean,什么也不会发生;这个属性将不会设置。 |
constructor | 类似于 byType,但适用于构造函数参数。如果容器中的构造器参数类型不止一个 bean,那么就会出现一个致命错误。 |
通过 byType 或构造器自动装配模式,您可以将数组和类型集合连接起来。在这种情况下,为了满足依赖性,容器内所提供的所有自动装配候选者都是与预期类型匹配的。如果预期的键类型是 String,您可以自动装配强置类型的映射。自动装配的 Map 值将由所有与预期类型匹配的 bean 实例组成,而 Map 键将包含相应的 bean 名称。
您可以将自动装配与依赖检查结合起来,依赖检查在自动装配完成后执行。
自动装配的局限性和缺点
当在整个项目中统一使用时,自动装配是最有效的。如果一般不使用自动装配,开发人员可能会混淆使用它只装配一个或两个 bean 定义。
考虑以下自动装配的局限性和缺点:
-
在 property 和 constructor-arg 中设置的显式依赖总是覆盖自动装配。不能自动装配所谓的简单属性,如原语(primitives)、字符串和类(以及此类简单属性的数组)。这个限制是由于设计所致。
-
自动装配不像显式装配那么精确。尽管如上表所示,Spring 谨慎地避免可能会产生的意想不到的结果,但 Spring 管理的对象之间的关系不再被明确地记录下来。
-
从 Spring 容器生成文档的工具可能无法获得装配信息。
-
容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造器参数指定的类型相匹配。对于数组、集合或 Map 映射,这并不一定是一个问题。然而,对于期望单个值的依赖,这种模糊性并不是可以随意解决的。如果没有唯一的 bean 定义,就会抛出一个异常。
在后面的场景中,有几个选则:
- 放弃自动装配,使用显式装配。
- 为 bean 设置不自动装配,方法是将其 autowire-candidate 属性设置为 false,如下一节所述。
- 将单个 bean 定义指定为主要候选者,方法是将元素的 primary 属性设置为 true。
- 使用基于注解的配置实现更细粒度的控制,如在基于注解的容器配置中所描述的那样。
排除不需要自动装配的 bean
您可以将 bean 从自动装配中排除。在 Spring 的 XML 格式中,将元素的 autowire-candidate 属性设置为 false;容器使该 bean 的自动装配不可用(包括诸如 @Autowired 等注释风格配置)。
注:autowire-candidate 属性被设计为只影响基于类型的自动装配。它不影响名称的显式引用,即使指定的 bean 没有被标记为自动装配。因此,如果名称匹配的话,名称的自动装配将会注入一个 bean。
您还可以根据 bean 名称的模式匹配来限制自动装配的候选者。顶层的元素在 default-autowire-candidates 属性中接受一个或多个模式。例如,为了将自动装配的候选状态限制为任何名称以 Repository 结尾的 bean,可以提供要给*Repository 值。要提供多个模式,请在逗号分隔的列表中定义它们。对于 bean 中使用 autowire-candidate 属性总是优先级最高的,对于这样的 bean,模式匹配规则是不适用的。
这些技术对于那些您永远不希望通过自动装配将其注入到其他 bean 中的 bean 非常有用。这并不意味着被排除的 bean 本身不能使用自动装配添加依赖。相反,只不过这个 bean 本身不会自动装配到其他 bean 而已。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于