欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

【译文】spring framework核心功能-IOC【一】
(last modified Nov 28, 2023, 9:33 PM )
by
侧边栏壁纸
  • 累计撰写 178 篇文章
  • 累计创建 62 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

【译文】spring framework核心功能-IOC【一】

橙序员
2022-08-20 / 0 评论 / 0 点赞 / 418 阅读 / 12 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

1.1. Spring IoC 容器和 Bean 简介本章介绍了控制反转 (IoC) 原则的 Spring Framework 实现。IoC 也称为依赖注入 (DI)。这是一个过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖关系(即与它们一起

1.1. Spring IoC 容器和 Bean 简介

本章介绍了控制反转 (IoC) 原则的 Spring Framework 实现。IoC 也称为依赖注入 (DI)。这是一个过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖关系(即与它们一起工作的其他对象) . 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖关系的实例化或位置的逆过程(因此称为控制反转)。

org.springframework.beansorg.springframework.context包是 Spring Framework 的 IoC 容器的基础。 BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。 ApplicationContextBeanFactory的子接口。它增加了如下拓展:

  • 更容易与 Spring 的 AOP 功能集成
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层特定上下文,例如用于 Web 应用程序的上下文WebApplicationContext

简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext增加了更多的企业特定功能。ApplicationContextBeanFactory的完整超集,并且在本章中专门用于描述 Spring 的 IoC 容器。有关使用BeanFactory代替ApplicationContext更多信息,请参阅涵盖 BeanFactoryAPI的部分。

在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。否则,bean 只是应用程序中的众多对象之一。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。

1.2. 容器概述

org.springframework.context.ApplicationContext接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取关于要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

Spring 提供了ApplicationContext接口的几个实现。在独立应用程序中,通常会创建 ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例。虽然 XML 一直是定义配置元数据的传统格式,但您可以通过提供少量 XML 配置来以声明方式支持这些附加元数据格式,从而指示容器使用 Java 注解或代码作为元数据格式。

在大多数应用场景中,不需要显式用户代码来实例化 Spring IoC 容器的一个或多个实例。例如,在 Web 应用程序场景中,应用程序web.xml文件中简单的八(大约)行样板 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 的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参阅 @Configuration@Bean@Import@DependsOn注解。

Spring 配置包含容器必须管理的至少一个,通常是多个 bean 定义。基于 XML 的配置元数据将这些 bean 配置为<bean/>顶级元素内的<beans/>元素。Java 配置通常在@Configuration类中使用@Bean 注解的方法。

这些 bean 定义对应于构成应用程序的实际对象。通常,您定义服务层对象、数据访问对象 (DAO)、表示对象(如 StrutsAction实例)、基础设施对象(如 Hibernate SessionFactories、JMSQueues等)。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是 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
        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 CLASSPATH等)加载配置元数据。

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

在了解了 Spring 的 IoC 容器之后,您可能想了解更多关于 Spring 的 Resource抽象(如参考资料中所述),它提供了一种方便的机制来从 URI 语法中定义的位置读取 InputStream。特别是, Resource路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。

以下示例显示了服务层对象(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类和两个类型的数据访问对象JpaAccountDaoJpaItemDao(基于 JPA 对象-关系映射标准)组成。property name元素引用JavaBean 属性的名称,元素ref引用另一个bean 定义的名称。元素id之间的这种ref联系表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅 依赖项。

编写基于 XML 的配置元数据

让 bean 定义跨越多个 XML 文件会很有用。通常,每个单独的 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 定义是从三个文件加载的: services.xmlmessageSource.xmlthemeSource.xml. 所有位置路径都相对于执行导入的定义文件,因此services.xml必须与执行导入的文件位于同一目录或类路径位置,而 messageSource.xmlthemeSource.xml必须位于导入文件位置下方的resources位置。如您所见,前导斜杠被忽略。但是,鉴于这些路径是相对的,最好不要使用斜线。根据 Spring Schema ,被导入文件的内容,包括顶级<beans/>元素,必须是有效的 XML bean 定义。

可以但不推荐使用相对“…/”路径来引用父目录中的文件。这样做会创建对当前应用程序之外的文件的依赖。特别是,不建议将此引用用于classpath:URL(例如,classpath:../services.xml),其中运行时解析过程选择“最近的”类路径根,然后查看其父目录。类路径配置更改可能会导致选择不同的、不正确的目录。您始终可以使用完全限定的资源位置而不是相对路径:例如,file:C:/config/services.xmlclasspath:/config/services.xml. 但是,请注意您将应用程序的配置耦合到特定的绝对位置。通常最好为此类绝对位置保留间接性——例如,通过在运行时针对 JVM 系统属性解析的“${…}”占位符。

命名空间本身提供了导入指令功能。除了普通的 bean 定义之外,更多的配置特性可以在 Spring 提供的 XML 命名空间的选择中使用——例如,contextutil命名空间。

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与阅读器委托结合使用——例如,XmlBeanDefinitionReader与XML 文件结合使用,如以下示例所示:

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/>定义的形式)。

在容器本身中,这些 bean 定义表示为BeanDefinition 对象,其中包含(以及其他信息)以下元数据:

  • 一个包限定的类名:通常是被定义的 bean 的实际实现类。
  • Bean 行为配置元素,它说明 bean 在容器中的行为方式(范围、生命周期回调等)。
  • 对 bean 完成工作所需的其他 bean 的引用。这些引用也称为协作者或依赖项。
  • 要在新创建的对象中设置的其他配置设置——例如,池的大小限制或在管理连接池的 bean 中使用的连接数。

此元数据转换为组成每个 bean 定义的一组属性。下表描述了这些属性:

Property 解释…
Class 实例化 Bean
Name 命名 Bean
Scope Bean范围
Constructor arguments 依赖注入
Properties 依赖注入
Autowiring mode 自动装配协作者
Lazy initialization mode 延迟初始化的 Bean
Initialization method 初始化回调
Destruction method 销毁回调

除了包含有关如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext的实现还允许注册在容器外部(由用户)创建的现有对象。这是通过 getBeanFactory() 方法访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 DefaultListableBeanFactory 实现。DefaultListableBeanFactory 通过 registerSingleton(..) registerBeanDefinition(..) 方法支持这种注册。然而,典型的应用程序仅使用通过常规 bean 定义元数据定义的 bean。

需要尽早注册 Bean 元数据和手动提供的单例实例,以便容器在自动装配和其他自省步骤中正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时注册新 bean(同时对工厂进行实时访问),并可能导致并发访问异常、bean 容器中的状态不一致或两个都。

1.3.1. Bean命名

每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。但是,如果它需要多个,则可以将多余的视为别名。

在基于 XML 的配置元数据中,您可以使用id属性、name属性或两者来指定 bean 标识符。该id属性可让您准确指定一个 id。按照惯例,这些名称是字母数字的(“myBean”、“someService”等),但它们也可以包含特殊字符。如果要为 bean 引入其他别名,也可以在name 属性中指定,用逗号 ( ,)、分号 ( ;) 或空格分隔。作为历史记录,在 Spring 3.1 之前的版本中,id属性被定义为一种xsd:ID类型,它限制了可能的字符。从 3.1 开始,它被定义为一种xsd:string类型。请注意,id容器仍然强制执行 bean 唯一性,但不再由 XML 解析器强制执行。

您不需要为 bean 提供nameid。如果您不显式提供 nameid,则容器会为该 bean 生成一个唯一名称。但是,如果您想通过名称引用该 bean,通过使用ref元素或服务定位器样式查找,您必须提供名称。不提供名称的动机与使用内部 bean自动装配合作者有关。

Bean 命名约定

约定是在命名 bean 时对实例字段名称使用标准 Java 约定。也就是说,bean 名称以小写字母开头,并且从那里开始是驼峰式的。此类名称的示例包括accountManageraccountServiceuserDaologinController等。

命名 bean 的一致性使您的配置更易于阅读和理解。此外,如果您使用 Spring AOP,则在将建议应用于一组按名称相关的 bean 时会很有帮助。

通过类路径中的组件扫描,Spring 为未命名的组件生成 bean 名称,遵循前面描述的规则:本质上,采用简单的类名称并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,当有多个字符并且第一个和第二个字符都是大写时,会保留原始大小写。这些与java.beans.Introspector.decapitalize(Spring 在此处使用的) 定义的规则相同。

在 Bean 定义之外为 Bean 起别名

在 bean 定义本身中,您可以为 bean 提供多个名称,方法是使用属性指定的最多一个名称id和属性name中任意数量的其他名称的组合。这些名称可以是同一个 bean 的等效别名,并且在某些情况下很有用,例如让应用程序中的每个组件通过使用特定于该组件本身的 bean 名称来引用公共依赖项。

但是,指定实际定义 bean 的所有别名并不总是足够的。有时需要为在别处定义的 bean 引入别名。这在大型系统中很常见,其中配置在每个子系统之间进行拆分,每个子系统都有自己的一组对象定义。在基于 XML 的配置元数据中,您可以使用<alias/>元素来完成此操作。以下示例显示了如何执行此操作:

<alias name="fromName" alias="toName"/>

在这种情况下,命名fromName的 bean(在同一容器中)也可以在使用此别名定义后称为toName.

例如,子系统 A 的配置元数据可能引用名为subsystemA-dataSource 的 DataSource 。子系统 B 的配置元数据可能会引用名称为 subsystemB-dataSource的 DataSource。在编写同时使用这两个子系统的主应用程序时,主应用程序通过名称 myApp-dataSource来引用 DataSource。要让所有三个名称都引用同一个对象,您可以将以下别名定义添加到配置元数据中:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

现在每个组件和主应用程序都可以通过一个唯一的名称来引用数据源,并保证不会与任何其他定义冲突(有效地创建一个命名空间),但它们引用的是同一个 bean。

Java 配置

如果您使用 Java配置,则可以使用注解@Bean来提供别名。有关详细信息,请参阅使用@Bean注解

1.3.2. 实例化 Bean

bean 定义本质上是创建一个或多个对象的方法。当被访问时,容器会查看命名 bean 的定义,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。

如果您使用基于 XML 的配置元数据,您可以在元素<bean/>class属性中指定要实例化的对象的类型(或类) 。 class属性(在内部是实例BeanDefinitionClass属性 )通常是必需的。(有关例外情况,请参阅 使用实例工厂方法Bean 定义继承进行实例化。)您可以通过以下两种方式之一使用Class属性:

  • 通常,在容器本身通过反射调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,有点等价于 Java 代码中的new操作符。
  • 指定包含被调用以创建对象的static工厂方法的实际类,在不太常见的情况下,容器调用类上的static工厂方法来创建 bean。调用static工厂方法返回的对象类型可能是同一个类,也可能完全是另一个类。

嵌套类名

如果要为嵌套类配置 bean 定义,可以使用嵌套类的二进制名称或源名称。

例如,如果您在com.example包中调用了一个SomeThing类,并且SomeThing类有一个static名为OtherThing 的嵌套类,则它们可以用美元符号 ( $) 或点 ( .) 分隔。所以bean 定义中属性class的值是com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing

使用构造函数进行实例化

当您通过构造方法创建 bean 时,所有普通类都可以被 Spring 使用并兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 bean 类就足够了。但是,根据您用于该特定 bean 的 IoC 类型,您可能需要一个默认(空)构造函数。

Spring IoC 容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBean。大多数 Spring 用户更喜欢只有一个默认(无参数)构造函数以及根据容器中的属性建模的适当的 setter 和 getter 的实际 JavaBeans。您还可以在容器中拥有更多奇特的非 bean 样式类。例如,如果您需要使用绝对不符合 JavaBean 规范的遗留连接池,那么 Spring 也可以管理它。

使用基于 XML 的配置元数据,您可以指定 bean 类,如下所示:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

有关在构造对象后向构造函数提供参数(如果需要)和设置对象实例属性的机制的详细信息,请参阅 注入依赖项

使用静态工厂方法进行实例化

在定义使用静态工厂方法创建的 bean 时,使用class 属性指定包含static工厂方法的类,使用命名属性factory-method指定工厂方法本身的名称。您应该能够调用此方法(使用可选参数,如后所述)并返回一个活动对象,该对象随后被视为是通过构造函数创建的。这种 bean 定义的一种用途是在遗留代码中调用static工厂。

以下 bean 定义指定将通过调用工厂方法创建 bean。定义没有指定返回对象的类型(类),而是包含工厂方法的类。在此示例中, createInstance()方法必须是static方法。以下示例显示了如何指定工厂方法:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="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:

<!-- 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;
    }
}

这种方法表明,工厂 bean 本身可以通过依赖注入 (DI) 进行管理和配置。请参阅依赖项和配置详细信息

在 Spring 文档中,“工厂 bean”是指在 Spring 容器中配置并通过 实例静态工厂方法创建对象的 bean。相比之下, FactoryBean(注意大写)指的是特定于 Spring 的 FactoryBean实现类。

确定 Bean 的运行时类型

确定特定 bean 的运行时类型并非易事。bean 元数据定义中的指定类只是一个初始类引用,可能与声明的工厂方法结合,或者是可能导致 bean 的不同运行时类型的FactoryBean类,或者在实例的情况下根本没有设置实例级别工厂方法(而是通过指定的factory-bean名称解析)。此外,AOP 代理可以用基于接口的代理包装一个 bean 实例,并限制目标 bean 的实际类型(仅其实现的接口)的暴露。

查找特定 bean 的实际运行时类型的推荐方法是BeanFactory.getType调用指定的 bean 名称。这会考虑上述所有情况, 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) {
        // ...
    }
}

假设ThingTwoThingThree类不通过继承相关,则不存在潜在的歧义。因此,以下配置工作正常,您无需在 <constructor-arg/>元素中显式指定构造函数参数索引或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

当引用另一个 bean 时,类型是已知的,并且可以发生匹配(就像前面的示例一样)。当使用简单类型时,如 <value>true</value>,Spring 无法确定值的类型,因此无法在没有帮助的情况下按类型匹配。例如以下类:

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显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配,如以下示例所示:

<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 可以从构造函数中查找参数名称。如果您不能或不想使用调试标志编译代码,则可以使用 JDK 注解@ConstructorProperties 显式命名构造函数参数。示例类必须如下所示:

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管理的bean 支持基于构造函数和基于 setter 的 DI。在已经通过构造方法注入了一些依赖项之后,它还支持基于 setter 的 DI。您以 BeanDefinition 的形式配置依赖项,您可以将其与PropertyEditor实例结合使用以将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户并不直接使用这些类(即以编程方式),而是使用 XMLbean 定义、带注解的组件(即,使用@Component@Controller等注解的类)或基于 Java 的@Configuration类中的@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,它的依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用它而不是普通的构造函数)。这些依赖项在实际创建 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注入来配置循环依赖。

与典型情况(没有循环依赖关系)不同,bean A 和 bean B 之间的循环依赖关系强制其中一个 bean 在完全初始化之前注入另一个 bean(典型的先有鸡还是先有蛋的场景)。

您通常可以相信 Spring 会做正确的事情。它在容器加载时检测配置问题,例如对不存在的 bean 和循环依赖项的引用。在实际创建 bean 时,Spring 会尽可能晚地设置属性并解析依赖关系。这意味着,如果在创建该对象或其依赖项之一时出现问题,则正确加载的 Spring 容器稍后可以在您请求对象时生成异常——例如,bean 由于某个丢失或无效的属性而引发异常。某些配置问题的这种潜在延迟可见性就是为什么ApplicationContext默认情况下,实现预实例化单例 bean。以在实际需要之前创建这些 bean 的一些前期时间和内存为代价,您会在ApplicationContext创建这些 bean 时发现配置问题,而不是之后。您仍然可以覆盖此默认行为,以便单例 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"/>

以下示例显示了相应的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:

<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"/>

以下示例显示了相应的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工厂方法来返回对象的实例,而不是使用构造函数:

<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"/>

以下示例显示了相应的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<constructor-arg/>元素提供,就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含static工厂方法的类的类型相同(尽管在本例中是这样)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用factory-bean属性而不是class属性),因此我们不在这里讨论这些细节。

1.4.2. 详细的依赖关系和配置

如上一节所述,您可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用或内联定义的值。为此, Spring 的基于 XML 的配置元数据支持其<property/>和元素<constructor-arg/>中的子元素类型。

直接值(基元、字符串等)

元素<property/>value属性将属性或构造函数参数指定为人类可读的字符串表示。Spring 的 转换服务用于将这些值从 String 转换为属性或参数的实际类型。以下示例显示了正在设置的各种值:

<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="misterkaoli"/>
</bean>

以下示例使用p-namespace进行更简洁的 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
    https://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="misterkaoli"/>

</beans>

前面的 XML 更简洁。但是,拼写错误是在运行时而不是设计时发现的,除非您在创建 bean 定义时使用支持自动完善属性功能的 IDE(例如IntelliJ IDEASpring Tools for Eclipse )。强烈推荐这种 IDE 帮助。

您还可以配置java.util.Properties实例,如下所示:

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- 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机制将<value/>元素内的文本转换为java.util.Properties 实例。这是一个不错的捷径,也是 Spring 团队支持使用嵌套<value/>元素而不是value属性样式的少数几个地方之一。

idref元素

idref元素只是将容器中另一个 bean 的id(字符串值 - 不是引用)传递给<constructor-arg/>或元素<property/>的一种防错方法 。以下示例显示了如何使用它:

<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 确实存在。在第二个变体中,不对传递给clientbean属性targetName的值执行验证。只有在实际实例化client bean时才会发现拼写错误(很可能是致命的结果) 。如果client bean 是一个原型bean,那么这个拼写错误和产生的异常可能只有在容器部署很久之后才会被发现。

4.0 bean XSD 不再支持元素上的local属性idref,因为它不再提供常规bean引用的值。在升级到 4.0 架构时将现有idref local引用更改为idref bean

<idref/>元素带来价值的常见地方(至少在 Spring 2.0 之前的版本中)是在 bean 定义中的AOP 拦截器配置中。ProxyFactoryBean在指定拦截器名称时使用<idref/>元素可以防止您拼错拦截器 ID。

对其他 Bean 的引用(协作者)

ref元素是<constructor-arg/><property/> 定义元素中的最后一个元素。在这里,您将 bean 的指定属性的值设置为对容器管理的另一个 bean(协作者)的引用。引用的bean是要设置属性的bean的依赖,在设置属性前根据需要进行初始化。(如果协作者是单例 bean,它可能已经被容器初始化。)所有引用最终都是对另一个对象的引用。范围和验证取决于您是否通过beanparent属性指定其他对象的 ID 或名称。

通过<ref/>标签的bean属性指定目标bean是最通用的形式,它允许创建对同一容器或父容器中的任何bean的引用,而且不用管它是否在同一个XML文件中。属性 bean的值可以与目标bean 的属性id相同,或者与目标bean 的属性name中的值之一相同。以下示例显示了如何使用ref元素:

<ref bean="someBean"/>

通过属性parent指定目标 bean会创建对当前容器的父容器中的 bean 的引用。parent属性的值可以与目标 bean 的id属性或目标 bean 的属性name中的值之一相同。目标 bean 必须在当前 bean 的父容器中。您应该使用此 bean 引用变体,主要是当您具有容器层次结构并且希望使用与父 bean 同名的代理将现有 bean 包装在父容器中时。以下一对清单显示了如何使用parent属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required 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 bean XSD 不再支持元素上的local属性ref,因为它不再提供常规bean引用的值。在升级到 4.0 架构时需要更改现有ref local引用到ref bean

内部Bean

<property/><constructor-arg/>元素中的<bean/>元素定义了一个内部 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 或名称。如果指定,容器不会使用这样的值作为标识符。容器在创建时也会忽略该scope标志,因为内部 bean 始终是匿名的,并且始终使用外部 bean 创建。不可能独立访问内部 bean 或将它们注入到协作 bean 中,而不是注入封闭 bean。

作为一个极端情况,可以从自定义范围接收销毁回调 - 例如,对于包含在单例 bean 中的请求范围内的内部 bean。内部 bean 实例的创建与其包含的 bean 相关联,但销毁回调让它参与请求范围的生命周期。这不是常见的情况。内部 bean 通常只是共享其包含 bean 的范围。

集合

<list/><set/><map/><props/>元素分别设置 Java Collection类型中ListSetMap的属性和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>

映射键或值或设置值的值也可以是以下任何元素:

bean | ref | idref | list | set | map | props | value | null
集合合并

Spring 容器还支持合并集合。应用程序开发人员可以定义父<list/><map/><set/><props/>元素,并让子<list/> <map/> <set/><props/>元素继承并覆盖父集合中的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,其中子集合元素覆盖父集合中指定的值。

本节关于合并讨论了父子 bean 机制。不熟悉 parent 和 child 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>

请注意在child bean 定义的merge=true属性的<props/>元素上使用 adminEmails属性。当child bean 被容器解析和实例化时,生成的实例有一个adminEmails Properties集合,其中包含将子集合 adminEmails与父adminEmails集合合并的结果。以下清单显示了结果:

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

子集合Properties的值集从父集合继承所有<props/>属性元素,并且子集合的值support会覆盖父集合中的值。

这种合并行为同样适用于<list/><map/><set/> 集合类型。在<list/>元素的特定情况下,与List集合类型相关联的语义(即ordered 值集合的概念)得到维护。父级的值在所有子级列表的值之前。对于MapSetProperties集合类型,不存在排序。因此,对于容器内部使用的关联MapSetProperties实现类型下的集合类型,没有任何排序语义有效。

集合合并的限制

您不能合并不同的集合类型(例如 Map和 a List)。如果您确实尝试这样做,则会抛出适当的Exceptionmerge属性必须在较低的继承的子定义中指定。在父集合定义上指定merge属性是多余的,不会导致所需的合并。

强类型集合

由于 Java 对泛型类型的支持,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。如果使用 Spring 将Collection强类型依赖注入到 bean 中,则可以利用 Spring 的类型转换支持,以便强类型实例的Collection 元素在添加到Collection. 以下 Java 类和 bean 定义显示了如何执行此操作:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <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>

something bean 的accounts属性准备好注入时,强类型Map<String, Float>的元素类型的泛型信息可以通过反射获得。因此,Spring 的类型转换基础结构将各种 value 元素识别为Float type ,并将字符串值(9.992.753.99)转换为实际Float类型。

Null 和空字符串值

Spring 将属性等的空参数视为空Strings参数。以下基于 XML 的配置元数据片段将该email属性设置为空 String值 (“”)。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

前面的示例等效于以下 Java 代码:

exampleBean.setEmail("");

<null/>元素处理null值。以下清单显示了一个示例:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上述配置等价于以下 Java 代码:

exampleBean.setEmail(null);
带有 p 命名空间的 XML 快捷方式

p-namespace 允许您使用bean元素的属性(而不是嵌套 <property/>元素)来描述协作 bean 或两者的属性值。

Spring 支持带有命名空间的可扩展配置格式,它基于 XML 模式定义。本章讨论的beans配置格式在 XML Schema 文档中定义。但是,p-namespace 没有在 XSD 文件中定义,仅存在于 Spring 的核心中。

以下示例显示了解析为相同结果的两个 XML 片段(第一个使用标准 XML 格式,第二个使用 p-namespace):

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

该示例显示了在 bean 定义中调用的 p-namespace 中的一个email属性。这告诉 Spring 包含一个属性声明。如前所述,p-namespace 没有架构定义,因此您可以将属性的名称设置为属性名称。

下一个示例包括另外两个 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
        https://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 定义<property name="spouse" ref="jane"/>用于创建从 bean john到 bean jane的引用,而第二个 bean 定义p:spouse-ref="jane"用作属性来执行完全相同的操作。在本例中,spouse是属性名称,而该-ref部分表示这不是直接值,而是对另一个 bean 的引用。

p 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与以 结尾的属性冲突Ref,而标准 XML 格式则不会。我们建议您仔细选择您的方法并将其传达给您的团队成员,以避免生成同时使用所有三种方法的 XML 文档。

带有 c 命名空间的 XML 快捷方式

与带有 p-namespace 的 XML Shortcut类似,在 Spring 3.1 中引入的 c-namespace 允许内联属性来配置构造函数参数,而不是嵌套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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

命名空间使用与通过名称设置构造函数参数c:相同的约定p:(bean 引用的-ref后缀)。同样,它需要在 XML 文件中声明,即使它没有在 XSD 模式中定义(它存在于 Spring 核心中)。

对于构造函数参数名称不可用的极少数情况(通常是在编译字节码时没有调试信息),您可以使用回退到参数索引,如下所示:

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>

由于 XML 语法,索引表示法需要存在前导_,因为 XML 属性名称不能以数字开头(即使某些 IDE 允许这样做)。相应的索引符号也可用于<constructor-arg>元素,但不常用,因为声明的简单顺序通常在那里就足够了。

实际上,构造函数解析 机制在匹配参数方面非常有效,因此除非您确实需要,否则我们建议在整个配置中使用名称表示法。

复合属性名称

您可以在设置 bean 属性时使用复合或嵌套属性名称,只要路径的所有组件(最终属性名称除外)都不是null. 考虑以下 bean 定义:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

Something bean 有一个fred属性,该属性有一个 bob 属性,该属性有一个 sammy 属性,并且最终的 sammy 属性被设置为值 123。为了使其正常工作,something fred 属性和 bob 属性bean 构造完成后,fred 的值不能为 null。否则,将引发 NullPointerException

1.4.3. 使用depends-on

如果一个 bean 是另一个 bean 的依赖项,那通常意味着一个 bean 被设置为另一个 bean 的属性。通常,您使用基于 XML 的配置元数据中的<ref/> 元素来完成此操作。但是,有时 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-on属性既可以指定初始化时间依赖项,也可以指定对应的销毁时间依赖项(仅在单例bean 的情况下)。定义与给定 bean 的关系的从属depends-on bean首先被销毁,然后给定 bean 本身被销毁。这样,depends-on也可以控制关机顺序。

1.4.4. 延迟初始化的 Bean

默认情况下,作为初始化过程的一部分,ApplicationContext实现会急切地创建和配置所有 单例bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即发现,而不是几小时甚至几天之后。当这种行为不可取时,您可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在第一次被请求时创建一个 bean 实例,而不是在启动时。

在 XML 中,此行为由<bean/> 元素上的属性lazy-init控制,如以下示例所示:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当前面的配置被 ApplicationContext使用时,lazybean 不会在ApplicationContext启动时急切地预实例化,而not.lazy bean 是急切地预实例化的。

但是,当延迟初始化的 bean 是未延迟初始化的单例 bean 的依赖项时,ApplicationContext将在启动时创建延迟初始化的 bean,因为它必须满足单例的依赖项。延迟初始化的 bean 被注入到没有延迟初始化的其他地方的单例 bean 中。

您还可以通过使用元素上的 default-lazy-init属性在容器级别控制延迟初始化<beans/>,如以下示例所示:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

1.4.5. 自动装配协作者

Spring 容器可以自动装配协作 bean 之间的关系。您可以让 Spring 通过检查 ApplicationContext 的内容自动为您的 bean 解析协作者(其他 bean)。自动装配具有以下优点:

  • 自动装配可以显着减少指定属性或构造函数参数的需要。(本章其他地方讨论的 bean 模板等其他机制 在这方面也很有价值。)
  • 随着对象的发展,自动装配可以更新配置。例如,如果您需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,自动装配在开发过程中特别有用,而不会在代码库变得更稳定时否定切换到显式装配的选项。

当使用基于 XML 的配置元数据时(请参阅依赖注入),您可以使用<bean/>元素的autowire属性为 bean 定义指定自动装配模式。自动装配功能有四种模式。您指定每个 bean 的自动装配,因此可以选择要自动装配的那些。下表描述了四种自动装配模式:

模式 解释
no (默认)不自动装配。Bean 引用必须由ref元素定义。对于较大的部署,不建议更改默认设置,因为明确指定协作者可以提供更大的控制力和清晰度。在某种程度上,它记录了系统的结构。
byName 按属性名称自动装配。Spring 寻找与需要自动装配的属性同名的 bean。例如,如果一个 bean 定义被设置为按名称自动装配并且它包含一个master属性(即它有一个 setMaster(..)方法),那么 Spring 会查找一个命名的 bean 定义master并使用它来设置该属性。
byType 如果容器中恰好存在一个属性类型的 bean,则让属性自动装配。如果存在多个,则会抛出一个致命异常,这表明您可能不会byType对该 bean 使用自动装配。如果没有匹配的 bean,则不会发生任何事情(未设置属性)。
constructor 类似于byType但适用于构造函数参数。如果容器中没有一个构造函数参数类型的 bean,则会引发致命错误。

使用byTypeconstructor自动装配模式,您可以封装数组和类型化集合。在这种情况下,将提供容器中与预期类型匹配的所有自动装配候选者来满足依赖关系。如果预期的键类型是String ,您可以自动装配强类型Map实例。自动装配Map 实例的值包含与预期类型匹配的所有 bean 实例,并且 Map实例的键包含相应的 bean 名称。

自动装配的限制和缺点

自动装配在项目中一致使用时效果最佳。如果一般不使用自动装配,开发人员可能会混淆使用它来只装配一个或两个 bean 定义。

考虑自动装配的限制和缺点:

  • propertyconstructor-arg设置中的显式依赖项总是覆盖自动装配。您不能自动装配简单属性,例如基元、 StringsClasses(以及此类简单属性的数组)。此限制是设计使然。
  • 自动装配不如显式装配精确。尽管如前表中所述,Spring 会小心避免猜测可能会产生意想不到的结果的歧义。Spring 管理的对象之间的关系不再明确记录。
  • 从 Spring 容器生成文档的工具可能无法使用接线信息。
  • 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型匹配。对于数组、集合或 Map实例,这不一定是问题。但是,对于期望单个值的依赖项,这种歧义不会被任意解决。如果没有唯一的 bean 定义可用,则会引发异常。

在后一种情况下,您有多种选择:

  • 放弃自动装配以支持显式装配。
  • 通过将属性autowire-candidate设置为false 来避免对 bean 定义进行自动装配,如下一节所述
  • 通过将其<bean/>元素的primary属性设置为true ,将单个 bean 定义指定为主要候选者 。
  • 使用基于注解的配置实现更细粒度的控制,如基于注解的容器配置中所述。
从自动装配中排除 Bean

在每个 bean 的基础上,您可以从自动装配中排除 bean。在 Spring 的 XML 格式中,将<bean/>元素的autowire-candidate属性设置为false. 容器使自动装配基础设施无法使用特定的 bean 定义(包括注解样式配置,例如@Autowired)。

autowire-candidate属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的 bean 未标记为自动装配候选者,也会得到解析。因此,如果名称匹配,按名称自动装配仍然会注入一个 bean。

您还可以根据与 bean 名称的模式匹配来限制自动装配候选者。顶级<beans/>元素在其 default-autowire-candidates属性中接受一个或多个模式。例如,要将自动装配候选状态限制为名称*Repository 结尾的任何 bean ,请提供Repository. 要提供多种模式,请在逗号分隔的列表中定义它们。bean 定义属性autowire-candidate的显式值 truefalse始终优先。对于此类 bean,模式匹配规则不适用。

这些技术对于您永远不想通过自动装配注入其他 bean 的 bean 很有用。这并不意味着排除的 bean 本身不能通过使用自动装配来配置。相反,bean 本身不是自动装配其他 bean 的候选对象。

1.4.6. 方法注入

在大多数应用场景中,容器中的大多数 bean 都是 单例的。当一个单例 bean 需要与另一个单例 bean 协作或非单例 bean 需要与另一个非单例 bean 协作时,您通常通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。当 bean 生命周期不同时,就会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,可能在 A 上的每个方法调用上。容器只创建一次单例 bean A,因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供一个新的 bean B 实例。

一个解决方案是放弃一些控制反转。您可以通过实现ApplicationContextAware接口将 bean A 织入到容器,并在每次bean A 需要时通过getBean("B")方法调用容器来请求(通常是新的)bean B 实例。以下示例显示了这种方法:

// 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 框架。方法注入是 Spring IoC 容器的一项高级功能,可让您干净地处理此用例。

您可以在此博客条目中阅读有关方法注入动机的更多信息 。

查找方法注入

查找方法注入是容器覆盖容器管理的 bean 上的方法并返回容器中另一个命名 bean 的查找结果的能力。查找通常涉及一个原型 bean,如上一节中描述的场景。Spring 框架通过使用 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 ),要注入的方法需要以下形式的签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是abstract,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

每当需要 myCommand bean 的新实例时,标识为 commandManager 的 bean 都会调用自己的createCommand()方法。如果实际需要的话,您必须小心地将 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 定义类似于以下示例:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

您可以在<arg-type/>元素中使用一个或多个<replaced-method/>元素 来指示被覆盖方法的方法签名。只有当方法被重载并且类中存在多个变体时,参数的签名才是必需的。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配 java.lang.String

java.lang.String
String
Str

因为参数的数量通常足以区分每个可能的选择,所以这个快捷方式可以节省大量的输入,让您只输入与参数类型匹配的最短字符串。

0

评论区