1 IoC容器
1.1 SpringIOC容器和Bean介绍
对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(即,它们使用的其他对象) . 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转)
org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。BeanFactory接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext是BeanFactory的子接口。ApplicationContext的补充说明:
- 更容易与 Spring 的 AOP 特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用层特定的上下文,例如WebApplicationContext 在 Web 应用程序中使用。
BeanFactory提供了配置框架和基本功能,ApplicationContext 添加了更多企业特定的功能。ApplicationContext是BeanFactory的一个完整的超集,仅在本章描述Spring的IoC容器时使用。关于更多使用BeanFactory 的信息看BeanFactory 而不是ApplicationContext。
1.2 容器概述
org.springframework.context.ApplicationContext接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示。它可以让您组成应用程序的表达对象以及这些对象之间丰富的相互依赖关系。
Springt提供了ApplicationContext几个实现,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。虽然 XML 一直是定义配置元数据的传统格式,但您可以通过提供少量 XML 配置来声明性地启用对这些附加元数据格式的支持,从而指示容器使用 Java 注释或代码作为元数据格式。
在大多数应用场景,不需要的显式的代码实例化一个或多个SpringIOC容器。比如说,在web应用场景,应用程序文件中的web.xml
简单八行(左右)通常就足够了(请参阅Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用 Spring Tools for Eclipse(一个 Eclipse 驱动的开发环境),您可以通过点击几下鼠标或按键轻松创建这个样板配置。
下图显示了 Spring 如何工作的高级视图。您的应用程序类与配置元数据相结合,在ApplicationContext创建和初始化后,您就有了一个完全配置且可执行的系统或应用程序。
图 1. Spring IoC 容器
1.2.1. 配置元数据
如上图所示,Spring IoC 容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化、配置和组装应用程序中的对象。
配置元数据传统上提供以简单直观的 XML 格式,本章的大部分内容使用这种格式来传达 Spring IoC 容器的关键概念和特性。
基于 XML 的元数据并不是唯一允许的配置元数据形式。Spring IoC 容器本身与实际写入此配置元数据的格式完全分离。现在,许多开发人员为他们的 Spring 应用程序选择 基于 Java 的配置。
有关在 Spring 容器中使用其他形式的元数据的信息,请参阅:
- 基于注解的配置:Spring 2.5 引入了对基于注解的配置元数据的支持。
- 基于 Java 的配置:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多特性成为 Spring Framework Core的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参阅 @Configuration, @Bean, @Import,和@DependsOn注解。
Spring配置容器管理必须由至少一个(通常是多个)bean定义组成。基于 XML 的配置元数据将这些 bean 配置为顶级
这些 bean 定义对应于构成应用程序的实际对象。通常,您定义服务层对象、数据访问对象 (DAO)、表示对象(例如 StrutsAction实例)、基础设施对象(例如 Hibernate SessionFactories、JMSQueues等)。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是 DAO 和业务逻辑的责任。但是,您可以使用 Spring 与 AspectJ 的集成来配置在 IoC 容器控制之外创建的对象。请参阅:使用 AspectJ 通过 Spring 依赖注入域对象。
以下示例显示了基于 XML 的配置元数据的基本结构:
- 该id属性是一个字符串,用于标识单个 bean 定义。
- 该class属性定义 bean 的类型并使用完全限定的类名。
该id属性的值是指协作对象。此示例中未显示用于引用协作对象的 XML。有关更多信息,请参阅依赖项。
1.2.2. 实例化容器
提供给ApplicationContext构造函数的一个或多个位置路径是资源字符串,允许容器从各种外部资源(例如本地文件系统、Java 等)加载配置元数据CLASSPATH。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了 Spring 的 IoC 容器之后,您可能想了解更多关于 Spring 的Resource
抽象(如参考资料中所述),它提供了一种从 URI 语法中定义的位置读取 InputStream 的便捷机制。特别是, Resource
路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。
以下示例显示了服务层对象(services.xml)配置文件:
以下示例显示了数据访问对象daos.xml文件:
在前面的示例中,服务层由PetStoreServiceImpl
类和两个类型JpaAccountDao
和JpaItemDao
(基于 JPA 对象-关系映射标准)的数据访问对象组成。该property name
元素是指JavaBean属性的名称,以及ref
元素指的是另一个bean定义的名称。id
和ref
元素之间的这种联系表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅 依赖项。
编写基于 XML 的配置元数据
跨越多个 XML 文件定义bean会很有用。通常,每个单独的 XML 配置文件都代表您架构中的一个逻辑层或模块。
您可以使用应用程序上下文构造函数从这些 XML 片段加载所有bean 定义。该构造函数采用多个Resource
位置,如上一节所示 。或者,使用一个或多个
元素从另一个文件或多个文件加载 bean 定义。以下示例显示了如何执行此操作:
在前面的例子中,外部Bean定义是从三个文件加载: services.xml,messageSource.xml,和themeSource.xml。所有位置路径都相对于执行导入的定义文件,因此services.xml必须与执行导入的文件位于同一目录或类路径位置, messageSource.xml并且themeSource.xml必须位于resources导入文件所在位置下方的位置。如您所见,前导斜杠被忽略。然而,鉴于这些路径是相对的,最好根本不使用斜杠。根据 Spring Schema,被导入文件的内容包含在顶级元素
可以但不建议使用相对“../”路径引用父目录中的文件。这样做会创建对当前应用程序之外的文件的依赖关系。特别是,不建议将此引用用于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 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. 使用容器
这ApplicationContext是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用 方法 T getBean(String name, Class
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 配置,引导看起来非常相似。它有一个不同的上下文实现类,它是 Groovy 感知的(但也理解 XML bean 定义)。以下示例显示了 Groovy 配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是GenericApplicationContext结合读者委托——例如,XmlBeanDefinitionReaderfor结合XML 文件,如下例所示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
您还可以使用GroovyBeanDefinitionReader结合 Groovy 文件,如以下示例所示:
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 | 延迟初始化的bean |
Initialization method | 初始化回调 |
Destruction method | 销毁回调 |
除了包含有关如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext实现还允许注册在容器外(由用户)创建的现有对象。这是通过getBeanFactory()方法返回 BeanFactoryDefaultListableBeanFactory实现访问 ApplicationContext 的 BeanFactory 来完成的。DefaultListableBeanFactory 通过registerSingleton(..)和 registerBeanDefinition(..)方法支持此注册。但是,通常应用程序仅使用通过常规 bean 定义元数据定义的 bean。
Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时注册新 bean(与实时访问工厂同时),并可能导致并发访问异常、bean 容器中的状态不一致,或两个都。
1.3.1. Bean命名
每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。但是,如果它需要多个,则可以将多余的视为别名。
在基于 XML 的配置元数据中,您可以使用id属性、name属性或两者来指定 bean 标识符。通常,这些名称是字母数字('myBean'、'someService' 等),但它们也可以包含特殊字符。如果要为 bean 引入其他别名,也可以在name 属性中指定它们,用逗号 ( ,)、分号 ( ;) 或空格分隔。作为历史记录,在 Spring 3.1 之前的版本中,id属性被定义为一种xsd:ID类型,它限制了可能的字符。从 3.1 开始,它被定义为一种xsd:string类型。请注意,bean 的id唯一性仍然由容器强制执行,但不再由 XML 解析器强制执行。
您不需要为 bean提供 aname或 an id。如果您没有明确提供 a name或id,则容器会为该 bean 生成一个唯一的名称。但是,如果您想通过名称引用该 bean,通过使用ref元素或服务定位器样式查找,您必须提供名称。不提供名称的动机与使用内部 bean和自动装配协作者有关。
Bean 命名约定
约定是在命名bean时使用标准Java约定对实例字段名称。bean 名称以小写字母开头,并从那里开始使用驼峰式大小写。此类名称的示例包括accountManager、 accountService、userDao、loginController等等。
始终如一地命名 bean 使您的配置更易于阅读和理解。此外,如果您使用 Spring AOP,则在将建议应用于一组按名称相关的 bean 时会很有帮助。
通过类路径中的组件扫描,Spring 为未命名的组件生成 bean 名称,遵循前面描述的规则:本质上,采用简单的类名并将其初始字符转换为小写。但是,在有多个字符且第一个和第二个字符都是大写的(不寻常的)特殊情况下,原始大小写被保留。这些与定义的规则相同java.beans.Introspector.decapitalize(Spring 在这里使用)。
在 Bean 定义之外给 Bean 取别名
在 bean 定义本身中,您可以通过使用id属性指定的最多一个名称和属性中任意数量的其他名称的组合,为 bean 提供多个名称name。这些名称等同于同一个 bean 的别名,并且在某些情况下很有用,例如让应用程序中的每个组件通过使用特定于该组件本身的 bean 名称来引用公共依赖项。
然而,在实际定义 bean 的地方指定所有别名并不总是足够的。有时需要为在别处引入定义的 bean 别名。这在大型系统中很常见,其中配置在每个子系统之间拆分,每个子系统都有自己的一组对象定义。在基于 XML 的配置元数据中,您可以使用
在这种情况下,命名的 bean(在同一容器中)fromName也可以在使用此别名定义后称为toName。
例如,子系统 A 的配置元数据可能引用名为 的数据源subsystemA-dataSource。子系统 B 的配置元数据可以通过名称来引用数据源subsystemB-dataSource。在组合使用这两个子系统的主应用程序时,主应用程序通过名称引用 DataSource myApp-dataSource。要让所有三个名称都引用同一个对象,您可以将以下别名定义添加到配置元数据中:
现在,每个组件和主应用程序都可以通过一个唯一的名称来引用 dataSource,并且保证不会与任何其他定义发生冲突(有效地创建一个命名空间),但它们引用的是同一个 bean。
java配置
如果您使用 Javaconfiguration,该@Bean
注解可用于提供别名。有关详细信息,请参阅使用@Bean
注释。
1.3.2.实例化Bean
bean 定义本质上是创建一个或多个对象的方法。当被询问时,容器会查看命名 bean 的方法,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。
如果使用基于 XML 的配置元数据,则指定要在元素的class属性中实例化的对象的类型(或类)
- 通常,在容器本身通过反射调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,有点等同于Java 代码new运算符
- 指定包含static被调用以创建对象的工厂方法的实际类,在不太常见的情况下,容器调用 static类上的工厂方法来创建 bean。调用static工厂方法返回的对象类型可能是同一个类,也可能完全是另一个类。
嵌套类名
如果要为嵌套类配置 bean 定义,可以使用嵌套类的二进制名称或源名称。例如,如果您有一个类SomeThing在com.example包中,并且SomeThing该类有一个static名为OtherThing的嵌套类,则它们之间可以用美元符号 (OtherThing或者是 com.example.SomeThing.OtherThing。
使用构造函数实例化
当您通过构造函数方法创建 bean 时,所有普通类都可以被 Spring 使用并与 Spring 兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。但是,根据您对该特定 bean 使用的 IoC 类型,您可能需要一个默认(空)构造函数。
Spring IoC 容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBean。大多数 Spring 用户更喜欢实际的 JavaBeans,它只有一个默认(无参数)构造函数和适当的 setter 和 getter,它们以容器中的属性为模型。您还可以在您的容器中拥有更多异国情调的非 bean 风格的类。例如,如果您需要使用绝对不符合 JavaBean 规范的遗留连接池,Spring 也可以管理它。
使用基于 XML 的配置元数据,您可以按如下方式指定 bean 类:
有关向构造函数提供参数(如果需要)和在构造对象后设置对象实例属性的机制的详细信息,请参阅注入依赖项。
使用静态工厂方法实例化
在定义使用静态工厂方法创建的 bean 时,使用class 属性来指定包含static工厂方法的类和命名factory-method为指定工厂方法本身名称的属性。您应该能够调用此方法(带有可选参数,如下所述)并返回一个活动对象,随后将其视为通过构造函数创建的。这种 bean 定义的一种用途是static在遗留代码中调用工厂。
以下 bean 定义指定通过调用工厂方法来创建 bean。定义中没有指定返回对象的类型(类),只指定包含工厂方法的类。在这个例子中,createInstance() 方法必须是静态方法。以下示例显示了如何指定工厂方法:
以下示例显示了一个可以与前面的 bean 定义一起使用的类:
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
属性设置工厂方法本身的名称。以下示例显示了如何配置此类 bean:
以下示例显示了相应的类
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含多个工厂方法,如下例所示:
以下示例显示了相应的类
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;
}
}
这种方法表明工厂 bean 本身可以通过依赖注入 (DI) 进行管理和配置。请参阅详细的依赖关系和配置。
在 Spring 文档中,“工厂 bean”是指在 Spring 容器中配置并通过实例或静态工厂方法创建对象的 bean 。相比之下, FactoryBean
(注意大写)是指特定于 Spring 的FactoryBean实现类。
确定Bean运行时的类型
确定特定 bean 的运行时类型并非易事。bean 元数据定义中的指定类只是一个初始类引用,可能与声明的工厂方法相结合,或者是FactoryBean可能导致 bean 的不同运行时类型的类,或者在实例的情况下根本没有设置 -级别工厂方法(通过指定factory-bean名称解析)。此外,AOP 代理可以使用基于接口的代理包装 bean 实例,并限制暴露目标 bean 的实际类型(仅其实现的接口)。
找出特定 bean 的实际运行时类型的推荐方法是使用指定的 bean 名称调用BeanFactory.getType。这考虑了上述所有情况,并返回和调用BeanFactory.getBean返回的对象类型相同 bean 名称。
1.4. 依赖关系
典型的企业应用程序不包含单个对象(或 Spring 用语中的 bean)。即使是最简单的应用程序也有一些对象,它们协同工作以呈现最终用户所看到的连贯应用程序。下一节将解释如何从定义多个独立的 bean 定义到完全实现的应用程序,其中对象协作以实现目标。
1.4.1. 依赖注入
依赖注入 (DI) 是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回。然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。
DI 原则使代码更清晰,当对象提供依赖关系时,解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。特别是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI 存在两种主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数代表一个依赖项。调用static带有特定参数的工厂方法来构造 bean 几乎是等效的,本讨论将static类似地处理构造函数和工厂方法的参数。以下示例显示了一个只能使用构造函数注入进行依赖注入的类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final 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...
}
请注意,这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注解的 POJO。
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型发生。如果 bean 定义的构造函数参数中不存在潜在的歧义,那么在 bean 定义中定义构造函数参数的顺序就是在实例化 bean 时将这些参数提供给适当的构造函数的顺序。考虑以下类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设ThingTwo和ThingThree类不通过继承相关,则不存在潜在的歧义。因此,以下配置工作正常,您不需要在
当另一个 bean 被引用时,类型是已知的,并且可以发生匹配(就像前面的例子一样)。当使用简单类型时,例如
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
构造函数参数类型匹配
在上述场景中,如果您通过type属性显式指定构造函数参数的类型,容器可以使用简单类型的类型匹配,如下例所示:
构造函数参数索引
您可以使用该index属性显式指定构造函数参数的索引,如以下示例所示:
除了解决多个简单值的歧义之外,指定索引还可以解决构造函数具有两个相同类型参数的歧义。
构造函数参数名称
您还可以使用构造函数参数名称进行值消歧,如以下示例所示:
请记住,要使这项工作开箱即用,您的代码必须在启用调试标志的情况下进行编译,以便 Spring 可以从构造函数中查找参数名称。如果您不能或不想使用调试标志编译代码,则可以使用 @ConstructorProperties JDK 注释显式命名构造函数参数。示例类必须如下所示:
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 是通过容器在调用无参数构造函数或无参数static工厂方法来实例化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支持管理基于构造函数和的Setter DI的Bean。在已经通过构造函数方法注入了一些依赖项之后,它还支持基于 setter 的 DI。您可以以BeanDefinition的形式配置依赖项,并将其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不直接(即以编程方式)使用这些类,而是使用 XMLbean 定义、带注解的组件(即用@Component、 @Controller等注释的类)或@Bean基于 Java 的@Configuration类中的方法。这些源然后在内部转换为实例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,它的依赖关系以属性、构造函数参数或静态工厂方法的参数(如果您使用它而不是普通构造函数)的形式表示。在实际创建 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 的引用和循环依赖。Spring 在真正创建 bean 时尽可能晚地设置属性并解析依赖项。这意味着,如果创建该对象或其依赖项时出现问题,则已正确加载的 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 定义,如下所示:
以下示例显示了相应的ExampleBean类:
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:
以下示例显示了相应的ExampleBean类:
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 调用static工厂方法来返回对象的实例:
以下示例显示了相应的ExampleBean类:
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;
}
}
static工厂方法的参数由
1.4.2. 详细依赖和配置
如上一节所述,您可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用或作为内联定义的值。为此,Spring 的基于 XML 的配置元数据支持其
和
元素中的子元素类型
直接值(原语、字符串等)
在value所述的属性String
为属性或参数的实际类型。以下示例显示了正在设置的各种值:
以下示例使用p-namespace进行更简洁的 XML 配置:
前面的 XML 更简洁。但是,拼写错误是在运行时而不是设计时发现的,除非您在创建 bean 定义时使用支持自动属性完成的 IDE(例如IntelliJ IDEA或Spring Tools for Eclipse)。强烈建议使用此类 IDE 帮助。
您还可以配置一个java.util.Properties实例,如下所示:
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
Spring 容器通过使用 JavaBeans机制将
idref 元素
所述idref元件是一个简单的防错方法对通过id(一个字符串值-而不是参考)在该容器另一个bean的一个
前面的 bean 定义片段与以下片段完全等效(在运行时):
第一种形式比第二种形式更可取,因为使用idref
标记可以让容器在部署时验证引用的命名 bean 是否实际存在。在第二个变体中,不对传递给beantargetName
属性的值执行验证client
。只有在client
实际实例化 bean时才会发现拼写错误(最有可能是致命的结果)。如果client
bean 是原型bean,则可能只有在部署容器很久之后才能发现此错误和由此产生的异常。
4.0 bean XSD不再支持idref元素的本地属性,因为它不再提供常规bean引用的值。当升级到4.0模式时,将现有的idref本地引用更改为idref bean。
其中一个共同的地方(至少在早期比Spring 2.0版本)
元素带来的值在配置AOP拦截在 ProxyFactoryBean
bean定义。
在指定拦截器名称时使用元素可防止您拼错拦截器 ID。
对其他 Bean 的引用(合作者)
ref 元素是
通过标记的bean属性指定目标 bean是最通用的形式,它允许创建对同一容器或父容器中的任何 bean 的引用,无论它是否在同一 XML 文件中。bean属性的值 可以id与目标bean的属性相同,也可以与目标bean的name属性中的值之一相同。以下示例显示了如何使用ref元素:
通过parent属性指定目标 bean会创建对当前容器的父容器中的 bean 的引用。parent 属性的值可以id与目标 bean的属性或目标 bean 属性中的值之一相同name。目标 bean 必须在当前容器的父容器中。您应该主要在具有容器层次结构并且希望使用与父 bean 同名的代理将现有 bean 包装在父容器中时使用此 bean 引用变体。以下清单显示了如何使用该parent属性:
class="org.springframework.aop.framework.ProxyFactoryBean">
4.0 bean XSD不再支持ref元素的本地属性,因为它不再提供常规bean引用的值。当升级到4.0 schema.descendant时,将现有的ref本地引用更改为ref bean
内部bean
A
内部bean定义不需要定义的 ID 或名称。如果指定,容器也不会使用他作为标识符。容器在创建时也会忽scope略标,因为内部 bean 始终是匿名的,并且始终与外部 bean 一起创建。不可能独立访问内部 bean 或将它们注入除封闭 bean 之外的协作 bean 中。
作为一个极端情况,可以从自定义范围接收销毁回调——例如,对于包含在单例 bean 中的请求范围内的 bean。内部 bean 实例的创建与其包含的 bean 相关联,但销毁回调让它参与请求范围的生命周期。这不是一个常见的场景。内部 bean 通常只是共享它们包含的 bean 的作用域。
集合
,
[email protected]
[email protected]
[email protected]
just some string
这些值是map的键或值或set的值,也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring 容器还支持合并集合。应用程序开发人员可以定义父,,
,,
关于合并的这一节讨论了父子 bean 机制。不熟悉父和子 bean 定义的读者可能希望在继续之前阅读相关部分。
以下示例演示了集合合并:
[email protected]
[email protected]
[email protected]
[email protected]
请注意在bean 定义的merge=true属性的
[email protected]
[email protected]
[email protected]
子Properties集合的值设置继承父所有属性元素
这一合并行为同样适用于,和
元素的特定情况下,与List集合类型(即ordered 值集合的概念)相关联的语义得到维护。父级的值在所有子级列表的值之前。在Map,Set和Properties集合类型的情况下,没有顺序存在。因此,对于容器内部使用的关联Map、Set和Properties实现类型的集合类型,没有有效排序语义。
集合合并的限制
您不能合并不同的集合类型(例如 aMap和 a List)。如果您确实尝试这样做,Exception则会抛出适当的。merge必须在较低的继承子定义上指定该属性。merge在父集合定义上指定属性是多余的,不会导致所需的合并
强类型集合
随着 Java 5 中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。如果您使用 Spring 将强类型依赖注入Collection到 bean 中,则可以利用 Spring 的类型转换支持,以便在将强类型Collection 实例的元素添加到Collection. 以下 Java 类和 bean 定义显示了如何执行此操作:
public class SomeClass {
private Map accounts;
public void setAccounts(Map accounts) {
this.accounts = accounts;
}
}
当bean的accounts属性something准备注入时,关于强类型元素类型的泛型信息Map
Null 和空字符串值
Spring 将属性等的空参数视为空字符串。以下基于 XML 的配置元数据片段将email属性设置为空 String值 ("")。
前面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
该
前面的示例等效于以下 Java 代码:
exampleBean.setEmail(null);
带有 p 命名空间的 XML 快捷方式
p-namespace 允许您使用bean元素的属性(而不是嵌套
Spring 支持具有命名空间的可扩展配置格式,这些格式基于 XML 模式定义。beans
本章讨论的配置格式是在 XML Schema 文档中定义的。但是,p 命名空间并未在 XSD 文件中定义,仅存在于 Spring 的核心中。
以下示例显示了两个解析为相同结果的 XML 片段(第一个使用标准 XML 格式,第二个使用 p 命名空间):
该示例显示了email在 bean 定义中调用的 p 命名空间中的一个属性。这告诉 Spring 包含一个属性声明。如前所述,p 命名空间没有模式定义,因此您可以将属性的名称设置为属性名称。
下一个示例包括另外两个 bean 定义,它们都引用了另一个 bean:
此示例不仅包括使用 p 命名空间的属性值,而且还使用特殊格式来声明属性引用。第一个 bean 定义用于
p 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与以 结尾的属性冲突Ref,而标准 XML 格式则不然。我们建议您谨慎选择您的方法并将其传达给您的团队成员,以避免同时使用所有三种方法生成 XML 文档。
带有 c 命名空间的 XML 快捷方式
与带有 p-namespace类似,Spring 3.1 中引入的 c-namespace 允许内联属性来配置构造函数参数而不是嵌套constructor-arg
元素。
以下示例使用c:
命名空间执行与 Constructor-based Dependency Injection 相同的操作:
该c:命名空间使用p:相同的约定作为一个(尾部-ref的bean引用),根据他们的名字设置构造函数的参数。同样,它需要在 XML 文件中声明,即使它没有在 XSD 模式中定义(它存在于 Spring 核心中)。
对于构造函数参数名称不可用的极少数情况(通常如果字节码是在没有调试信息的情况下编译的),您可以使用参数索引的回退,如下所示:
由于 XML 语法,索引表示法需要存在前导_,因为 XML 属性名称不能以数字开头(即使某些 IDE 允许)。相应的索引符号也可用于
实际上,构造函数解析机制在匹配参数方面非常有效,因此除非您确实需要,否则我们建议在整个配置中使用名称表示法。
复合属性名称
您可以在设置 bean 属性时使用复合或嵌套的属性名称,只要路径中除最终属性名称之外的所有组件都不是null. 考虑以下 bean 定义:
所述something bean具有fred属性,该属性具有bob属性,其具有sammy属性,并且最终sammy属性被设置为值123。为了使其工作,l在 bean 被构造之后fred属性 ofsomething和bob属性fred不能nul。否则,抛出一个 NullPointerException。
1.4.3. 使用depends-on
如果一个 bean 是另一个 bean 的依赖项,这通常意味着一个 bean 被设置为另一个 bean 的属性。通常,您使用基于 XML 的配置元数据中的 元素来完成此操作。但是,有时 bean 之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化程序时,例如数据库驱动程序注册。
depends-on
在初始化使用此元素的 bean 之前,该属性可以显式地强制初始化一个或多个 bean。以下示例使用该depends-on
属性来表达对单个 bean 的依赖:
要表达对多个 bean 的依赖,请提供 bean 名称列表作为depends-on属性值(逗号、空格和分号是有效的分隔符):
该depends-on
属性可以指定初始化时依赖项,并且仅在单例bean的情况下,还可以指定相应的销毁时依赖项。depends-on
在给定的 bean 本身被销毁之前,首先销毁与给定 bean定义关系的依赖 bean 。这样,depends-on
也可以控制关机顺序。
1.4.4. 延迟初始化的 Bean
默认情况下,ApplicationContext
实现会在初始化过程中急切地创建和配置所有单例bean。通常,这种预实例化是可取的,因为可以立即发现配置或周围环境中的错误,而不是在几小时甚至几天之后。当这种行为不可取时,您可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。一个延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时创建一个 bean 实例,而不是在启动时。
在 XML 中,此行为由 元素lazy-init
上的属性控制
,如以下示例所示:
当前面的配置被ApplicationContext使用时,lazybean 在ApplicationContext启动时不会被预先实例化,而not.lazybean 会被预先实例化。
但是,当延迟初始化 bean 是未延迟初始化的单例 bean 的依赖项时,ApplicationContext会在启动时创建延迟初始化 bean,因为它必须满足单例的依赖项。延迟初始化的 bean 被注入到其他地方没有延迟初始化的单例 bean 中。
您还可以通过使用元素default-lazy-init上的属性来控制容器级别的延迟初始化
1.4.5. 自动装配合作者
Spring 容器可以自动装配协作 bean 之间的关系。您可以让 Spring 通过检查ApplicationContext
. 自动装配具有以下优点:
自动装配可以显着减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,例如 bean 模板 ,在这方面也很有价值。)
自动装配可以随着对象的发展更新配置。例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,当代码库变得更稳定时,不会否定切换到显式装配的选项。
使用基于 XML 的配置元数据时(请参阅依赖注入),您可以使用元素的autowire
属性为 bean 定义指定自动装配模式
。自动装配功能有四种模式。您可以为每个 bean 指定自动装配,因此可以选择要自动装配的那些。下表描述了四种自动装配模式:
表 2. 自动装配模式
Mode | Explanation |
---|---|
no | (默认)没有自动装配。Bean 引用必须由ref元素定义。对于较大的部署,不建议更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 按属性名称自动装配。Spring 查找与需要自动装配的属性同名的 bean。例如,如果一个 bean 定义被设置为按名称自动装配并且它包含一个master属性(即它有一个 setMaster(..)方法),Spring 会查找一个名为的 bean 定义master并使用它来设置属性。 |
byType | 如果容器中只存在一个属性类型的 bean,则让属性自动装配。如果存在多个,则会引发致命异常,这表明您不能byType为该 bean使用自动装配。如果没有匹配的 bean,则不会发生任何事情(未设置属性)。 |
constructor | 类似于byType但适用于构造函数参数。如果容器中没有一个构造函数参数类型的 bean,则会引发致命错误。 |
使用byType或constructor自动装配模式,您可以连接数组和类型化集合。在这种情况下,提供容器内与预期类型匹配的所有自动装配候选者以满足依赖关系。Map如果预期的键类型是 ,您可以自动装配强类型实例String。自动装配Map 实例的值由与预期类型匹配的所有 bean 实例组成,并且 Map实例的键包含相应的 bean 名称。
自动装配的局限性和缺点
自动装配在整个项目中一致使用时效果最佳。如果通常不使用自动装配,开发人员可能会使用它来连接一两个 bean 定义,这可能会让人感到困惑。
考虑自动装配的局限性和缺点:
property和constructor-arg设置中的显式依赖项始终覆盖自动装配。您不能自动装配简单属性,例如Strings、 和Classes(以及此类简单属性的数组)。此限制是有意设计的。
自动装配不如显式装配精确。虽然,如前面的表中所述,Spring 小心避免在可能产生意外结果的歧义的情况下进行猜测。不再明确记录 Spring 管理的对象之间的关系。
可能无法从 Spring 容器生成文档的工具中使用接线信息。
容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型相匹配。对于数组、集合或 Map实例,这不一定是问题。但是,对于期望单个值的依赖项,这种歧义不会被任意解决。如果没有唯一的 bean 定义可用,则抛出异常。
在后一种情况下,您有多种选择:
- 放弃自动装配以支持显式装配。
- 如下一节所述,通过将其
autowire-candidate
属性设置为false
来避免对 bean 定义进行自动装配。 - 通过将
primary
其
元素的属性设置为 ,将单个 bean 定义指定为主要候选者true
。 - 使用基于注解的配置实现更细粒度的控制,如基于注解的容器配置 中所述。
从自动装配中排除 Bean
在每个 bean 的基础上,您可以从自动装配中排除一个 bean。在 Spring 的 XML 格式中,将
元素的autowire-candidate
属性设置为false
. 容器使该特定 bean 定义对自动装配基础设施不可用(包括注释样式配置,例如@Autowired
)。
该autowire-candidate属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的 bean 未标记为自动装配候选者,也会解析。因此,如果名称匹配,按名称自动装配仍然会注入一个 bean。
您还可以根据对 bean 名称的模式匹配来限制自动装配候选者。顶级
这些技术对于您永远不想通过自动装配注入其他 bean 的 bean 很有用。这并不意味着不能使用自动装配来配置被排除的 bean 本身。相反,bean 本身不是自动装配其他 bean 的候选者。
方法注入
在大多数应用场景中,容器中的大部分 bean 都是 单例的。当单例 bean 需要与另一个单例 bean 协作或非单例 bean 需要与另一个非单例 bean 协作时,您通常通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。当 bean 生命周期不同时就会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,可能在 A 上的每次方法调用上。容器只创建单例 bean A 一次,因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供 bean B 的新实例。
一个解决方案是放弃一些控制反转。您可以通过实现ApplicationContextAware接口,以及在bean A每次需要bean B实例时对容器进行getBean(“B”)调用,从而使bean A意识到容器。下面的例子展示了这种方法:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面是不可取的,因为业务代码知道并耦合到 Spring Framework。方法注入是 Spring IoC 容器的一个有点高级的特性,可以让你干净地处理这个用例。
查找方法注入
查找方法注入是容器覆盖容器管理 bean 上的方法并返回容器中另一个命名 bean 的查找结果的能力。查找通常涉及原型 bean,如上一节中描述的场景。Spring Framework 通过使用来自 CGLIB 库的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。
要使这种动态子类化工作,Spring bean 容器子类化的类不能是final,要覆盖的方法也不能是final。
对具有abstract方法的类进行单元测试需要您自己对类进行子类化并提供该abstract方法的存根实现。
组件扫描也需要具体的方法,这需要具体的类来获取。
另一个关键限制是查找方法不适用于工厂方法,尤其不适@Bean用于配置类中的方法,因为在这种情况下,容器不负责创建实例,因此不能在上创建运行时生成的子类苍蝇。
对于CommandManager前面代码片段中的类,Spring 容器动态覆盖了该createCommand() 方法的实现。该CommandManager没有任何Spring的依赖,修改后如下例所示:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法(CommandManager在本例中为 the)的客户端类中,要注入的方法需要以下形式的签名:
[abstract] theMethodName(no-arguments);
如果方法是abstract,则动态生成的子类实现该方法。否则,动态生成的子类会覆盖原始类中定义的具体方法。考虑以下示例:
标识为的 bean在需要bean的新实例时commandManager
调用它自己的createCommand()
方法myCommand
。myCommand
如果确实需要,您必须小心地将bean部署为原型。如果是单例,myCommand
则每次都返回相同的bean实例。
或者,在基于注解的组件模型中,您可以通过@Lookup注解声明一个查找方法,如下例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更惯用的是,您可以依靠目标 bean 根据查找方法的声明返回类型进行解析:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
请注意,您通常应该使用具体的存根实现声明此类带注释的查找方法,以便它们与 Spring 的组件扫描规则兼容,默认情况下抽象类将被忽略。此限制不适用于显式注册或显式导入的 bean 类。
访问不同范围的目标 bean 的另一种方法是ObjectFactory/ Provider注入点。参见Scoped Beans as Dependencies。
您可能还会发现ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config包中)很有用。
任意方法替换
与查找方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管 bean 中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能。
使用基于 XML 的配置元数据,您可以使用该replaced-method元素为已部署的 bean 将现有方法实现替换为另一个方法实现。考虑下面的类,它有一个computeValue我们想要覆盖的方法:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如以下示例所示:
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
用于部署原始类并指定方法覆盖的 bean 定义类似于以下示例:
String
您可以
java.lang.String
String
Str
由于参数的数量通常足以区分每个可能的选择,因此此快捷方式可以让您只键入与参数类型匹配的最短字符串,从而可以节省大量输入。
1.5. Bean 作用域
创建 bean 定义时,您创建了一个配方,用于创建由该 bean 定义定义的类的实际实例。bean 定义是一个配方的想法很重要,因为这意味着,与类一样,您可以从单个配方创建许多对象实例。
您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean 定义创建的对象的范围。这种方法功能强大且灵活,因为您可以通过配置选择您创建的对象的范围,而不必在 Java 类级别烘焙对象的范围。可以将 Bean 定义为部署在多个范围之一中。Spring Framework 支持六个范围,其中四个仅在您使用 web-aware ApplicationContext
时才可用。您还可以创建自定义范围。
下表描述了支持的范围:
表 3. Bean 范围
Scope | Description |
---|---|
single | (默认)将单个 bean 定义范围限定为每个 Spring IoC 容器的单个对象实例。 |
prototype | 将单个 bean 定义范围限定为任意数量的对象实例。 |
request | 将单个 bean 定义范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该 bean 实例是在单个 bean 定义的后面创建的。仅在 web-aware Spring 的上下文中有效ApplicationContext。 |
session | 将单个 bean 定义范围限定为 HTTP 的生命周期Session。仅在 web-aware Spring 的上下文中有效ApplicationContext。 |
application | 将单个 bean 定义范围限定为ServletContext. 仅在 web-aware Spring 的上下文中有效ApplicationContext。 |
websocket | 将单个 bean 定义范围限定为WebSocket. 仅在 web-aware Spring 的上下文中有效ApplicationContext。 |
从 Spring 3.0 开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参阅 的文档SimpleThreadScope
。有关如何注册此或任何其他自定义范围的说明,请参阅使用自定义范围。
1.5.1. 单例作用域
只有一个单例 bean 的共享实例被管理,并且所有对带有一个或多个 ID 与该 bean 定义匹配的 bean 的请求都会导致 Spring 容器返回一个特定的 bean 实例。
换句话说,当您定义一个 bean 定义并且它的作用域是一个单例时,Spring IoC 容器会创建该 bean 定义定义的对象的一个实例。该单个实例存储在此类单例 bean 的缓存中,并且对该命名 bean 的所有后续请求和引用都返回缓存对象。下图显示了单例范围的工作原理:
Spring 的单例 bean 概念不同于四人组 (GoF) 模式书中定义的单例模式。GoF 单例对对象的范围进行了硬编码,以便每个 ClassLoader 只创建一个特定类的一个实例。Spring 单例的范围最好描述为每个容器和每个 bean。这意味着,如果您在单个 Spring 容器中为特定类定义一个 bean,则 Spring 容器会创建该 bean 定义定义的类的一个且仅一个实例。单例作用域是 Spring 中的默认作用域。要将 bean 定义为 XML 中的单例,您可以定义一个 bean,如下例所示:
1.5.2. 原型作用域
bean 部署的非单一原型范围导致每次对特定 bean 发出请求时都会创建一个新 bean 实例。也就是说,bean 被注入到另一个 bean 中,或者您通过getBean()容器上的方法调用来请求它。通常,您应该对所有有状态 bean 使用原型作用域,对无状态 bean 使用单例作用域。
下图说明了 Spring 原型范围:
(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不保存任何会话状态。我们更容易重用单例图的核心。)
以下示例将 bean 定义为 XML 中的原型:
与其他作用域相比,Spring 不管理原型 bean 的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其交给客户端,没有该原型实例的进一步记录。因此,尽管在所有对象上调用初始化生命周期回调方法,而不管范围如何,但在原型的情况下,不会调用配置的销毁生命周期回调。客户端代码必须清理原型范围内的对象并释放原型 bean 持有的昂贵资源。要让 Spring 容器释放原型作用域 bean 持有的资源,请尝试使用自定义bean post-processor,它保存对需要清理的 bean 的引用。
在某些方面,Spring 容器在原型作用域 bean 方面的角色是 Javanew
运算符的替代品。超过该点的所有生命周期管理都必须由客户端处理。有关 Spring 容器中 bean 生命周期的详细信息,请参阅生命周期回调。
1.5.3. 具有 Prototype-bean 依赖关系的 Singleton Bean
当您使用具有对原型 bean 的依赖的单例作用域 bean 时,请注意在实例化时解析依赖关系。因此,如果您将原型范围的 bean 依赖注入到单例范围的 bean 中,则会实例化一个新的原型 bean,然后将依赖项注入到单例 bean 中。原型实例是唯一提供给单例作用域 bean 的实例。
但是,假设您希望单例范围的 bean 在运行时重复获取原型范围的 bean 的新实例。您不能将原型范围的 bean 依赖注入到您的单例 bean 中,因为该注入仅发生一次,当 Spring 容器实例化单例 bean 并解析并注入其依赖项时。如果您在运行时多次需要原型 bean 的新实例,请参阅方法注入。
精彩继续