当前位置: 首页 > 参考手册>阅读正文

Java框架-SpringFrameWork参考手册

2022.1.24 朱丰华 1994 次 留下评论 54506字

Spring框架,狭义上是指SprngFrameWork,更往细了说是指Spring-Core,官方资料传送门:Core Technologies (spring.io)

Spring,翻译为“春天”,当然你也可以理解为”给java开发带来春天“的框架,目前几乎所有主流的Java技术都与Spring有关。

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益:

  • JAVA EE应该更加容易使用。
  • 面向对象的设计比任何实现技术(比如JAVA EE)都重要。
  • 面向接口编程,而不是针对类编程。Spring将使用接口的复杂度降低到零。
  • 代码应该易于测试。Spring框架会帮助你,使代码的测试更加简单。
  • JavaBean提供了应用程序配置的最好方法。
  • 在Java中,已检查异常(Checked exception)被过度使用。框架不应该迫使你捕获不能恢复的异常。

Spring框架的雏形是发布于2002年的interface21框架,该框架经过重新设计后于2004年发布了Spring之1.0版本。

Spring是一个轻量级(实现代价很小)的、控制反转(IOC)、面向切面(AOP)的框架。

在很多情况下Spring框架也指的是Spring家族,Spring非常庞大,它的官网地址:Spring.io.

在Spring发展起来后,Spring由于整合了太多技术,使用起来配置比较复杂,也有人称之为“配置地狱”,对于Spring新手,官方更推荐使用SpringBoot进行开发。

IOC容器

本章介绍控制反转 (IoC) 原则的 Spring 框架实现。IoC 也称为依赖注入 (DI)。在此过程中,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回对象实例后在对象实例上设置的属性来定义其依赖项(即它们使用的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。这个过程基本上是Bean本身的反转(因此名称为Inversion of Control)(控制反转一般是指由原来的主动创建bean的属性,到bean被动的接受属性依赖,比如自动注入属性,而在单例模式中可以理解为:从原来的方式中bean通过参数被传递过来,现在可以主动从容器中获取),通过使用类的直接构造或服务定位器模式等机制来控制其依赖关系的实例化或位置。

org.springframework.beans 和 org.springframework.context 包是Spring Framework的IoC容器的基础。 BeanFactory接口提供了一种能够管理任何类型的对象的高级配置机制。 ApplicationContext是 的子接口。它补充说:

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

简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多特定于企业的功能。ApplicationContext是BeanFactory的完整超集,在本章中专门用于描述Spring的IoC容器。有关使用BeanFactory而不是ApplicationContext的更多信息,请参阅BeanFactory

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

容器概述

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

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

在大多数应用程序场景中,实例化Spring IoC容器的一个或多个实例不需要显式用户代码。例如,在 Web 应用程序方案中,应用程序web.xml文件中的八行左右的简单样板 Web 描述符 XML 通常就足够了(请参见方便的 Web 应用程序上下文实例化)。如果您使用Spring Tools for Eclipse(一个基于 Eclipse 的开发环境),则只需单击几下鼠标或击键即可轻松创建此样板配置。

下图显示了Spring工作原理的高级视图。ApplicationContext应用程序类与配置元数据相结合,以便在创建和初始化 后,您拥有一个完全配置且可执行的系统或应用程序。

图1:Spring IOC容器

配置元数据

如上图所示,Spring IoC 容器使用某种形式的配置元数据。此配置元数据表示作为应用程序开发人员,您如何告诉 Spring 容器在应用程序中实例化、配置和组装对象。

配置元数据传统上以简单直观的 XML 格式提供,本章的大部分内容都用于传达 Spring IoC 容器的关键概念和功能。

基于 XML 的元数据不是配置元数据的唯一允许形式。Spring IoC 容器本身与实际写入此配置元数据的格式完全分离。如今,许多开发人员为他们的Spring应用程序选择基于Java的配置

有关将其他形式的元数据与 Spring 容器配合使用的信息,请参阅:

Spring配置由容器必须管理的至少一个且通常不止一个bean定义组成。基于XML的配置元数据将这些bean配置为顶级<bean>元素中的</bean>元素。Java配置通常在@configuration类中使用@Bean注释的方法。

这些bean定义对应于构成应用程序的实际对象。通常,您定义服务层对象、数据访问对象(DAO)、表示对象(如Struts操作实例)、基础结构对象(如Hibernate 会话工厂)、JMS队列等。通常,不在容器中配置细粒度域对象,因为创建和加载域对象通常是DAO和业务逻辑的责任。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象。请参阅使用 AspectJ 将域对象与 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>
  1. id属性是标识单个 Bean 定义的字符串。
  2. class属性定义 Bean 的类型,并使用完全限定的类名。

id属性的值是指协作对象。此示例中未显示用于引用协作对象的 XML。有关详细信息,请参阅依赖项

实例化容器

提供给ApplicationContext构造函数的位置路径是资源字符串,允许容器从各种外部资源(如本地文件系统、Java类路径等)加载配置元数据。

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

以下示例显示了服务层对象配置文件:(services.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

下面的示例演示数据访问对象文件:daos.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

在前面的示例中,服务层由PetStoreServiceImpl类和两个类型为JpaAccountDaoJpaItemDao(基于JPA对象关系映射标准)的数据访问对象组成。property name元素引用JavaBean属性的名称,ref元素引用另一个bean定义的名称。idref元素之间的链接表示协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参见依赖项

编写基于 XML 的配置元数据

让 Bean 定义跨越多个 XML 文件会很有用。通常,每个单独的 XML 配置文件都表示体系结构中的一个逻辑层或模块。

您可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。此构造函数获取多个资源位置,如前一节所示。或者,使用一个或多个<import/>元素从另一个或多个文件加载bean定义。以下示例显示了如何执行此操作:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

在前面的示例中,外部bean定义从三个文件加载:services.xmlmessageSource.xmlthemeSource.xml。所有位置路径都是相对于执行导入的定义文件的,所以是相对于服务的。 services.xml 必须与执行导入的文件位于同一目录或类路径位置,而 messageSource.xmlthemeSource.xml 必须位于导入文件位置下方的resources目录。如您所见,前导斜杠被忽略。然而,考虑到这些路径是相对的,最好不要使用斜杠。根据Spring模式,要导入的文件的内容,包括顶级元素,必须是有效的xmlbean定义。

可以使用相对的”..”/” 路径。这样做会创建对当前应用程序外部的文件的依赖关系。特别是,不建议将此引用用于 classpath: URL(例如 ,classpath:../services.xml),其中运行时解析进程选择”最近”的类路径根目录,然后查看其父目录。类路径配置更改可能会导致选择不同的、不正确的目录。

您始终可以使用完全限定的资源位置而不是相对路径:例如,file:C:/config/services.xmlclasspath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常,最好为此类绝对位置保留间接寻址 — 例如,通过”${…}”占位符,这些占位符在运行时针对 JVM 系统属性进行解析。

名称空间本身提供导入指令功能。Spring提供的一系列XML名称空间中提供了普通bean定义之外的更多配置特性 — 例如,contextutil名称空间。

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

这种配置风格在很大程度上等同于XMLBean定义,甚至支持Spring的XML配置名称空间。它还允许通过importBeans指令导入xmlbean定义文件。

使用容器

ApplicationContext是高级工厂的接口,能够维护不同bean及其依赖项的注册表。通过使用方法T getBean(字符串名称,Class<T> requiredType),您可以检索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();

ApplicationContext允许您读取 Bean 定义并访问它们,如以下示例所示:

使用Groovy配置,引导看起来非常相似。它有一个不同的上下文实现类,它是Groovy感知的(但也理解XML Bean定义)。以下示例显示了 Groovy 配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最灵活的变体是GenericApplicationContext与读者委托的组合 — 例如,使用XmlBeanDefinitionReader for 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()方法的调用,因此完全不依赖于SpringAPI。例如,Spring与web框架的集成为各种web框架组件(如控制器和JSF托管bean)提供了依赖项注入,允许您通过元数据(如自动连接注释)声明对特定bean的依赖项。

bean 概述

SpringIOC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML<bean/>定义的形式)。

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

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

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

属性解释在…
Class实例化bean
Name命名bean
Scopebean范围
Constructor arguments依赖注入
Properties依赖注入
Autowiring mode自动装配模式
Lazy initialization mode惰性初始化的 Bean
Initialization method初始化回调
Destruction method销毁回调

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

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

命名bean

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

在基于XML的配置元数据中,可以使用id属性、name属性或两者来指定bean标识符。id属性允许您只指定一个id。通常,这些名称是字母数字的(’myBean’,’someService’,等等),但它们也可以包含特殊字符。如果要为bean引入其他别名,还可以在name属性中指定它们,并用逗号(,),分号(;)分隔,或空白。作为历史记录,在Spring3.1之前的版本中,id属性被定义为xsd:id类型,它约束可能的字符。从3.1开始,它被定义为xsd:string类型。注意,bean id唯一性仍然由容器强制执行,尽管不再由XML解析器强制执行。

您不需要为bean提供名称或id。如果不显式提供名称或id,容器将为该bean生成唯一的名称。但是,如果希望通过使用ref元素或服务定位器样式的查找按名称引用该bean,则必须提供名称。不提供名称的动机与使用内部bean自动连接协作器有关。

bean命名约定

约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,然后用驼峰大小写。此类名称的示例包括accountManager、accountService、userDao、loginController等。

一致地命名 Bean 可使您的配置更易于阅读和理解。此外,如果您使用Spring AOP,则在将建议应用于一组与名称相关的豆类时,它会有很大帮助。

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

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

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

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

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

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

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

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

Java 配置

如果使用 Java 配置,那么@Bean注解可用于提供别名。有关详细信息,请参阅使用@Bean批注

 实例化bean

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

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

  • 通常,在容器本身通过反射式调用其构造函数直接创建bean的情况下,指定要构造的bean类,这在某种程度上相当于使用新操作符的Java代码。
  • 指定包含被调用以创建对象的静态工厂方法的实际类,在不太常见的情况下,容器调用类上的静态工厂方法以创建bean。调用静态工厂方法返回的对象类型可以是同一个类,也可以是另一个类。

嵌套类名

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

例如,如果在com中有一个名为某物的类。这个SomeThing类有一个名为OtherThing的静态嵌套类,它们可以用美元符号($)或点(.)分隔。因此,bean定义中class属性的值应该是com。实例某物$OtherThing或com。实例某物另一件事。

使用构造函数进行实例化

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

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

使用基于 XML 的配置元数据,您可以按如下方式指定 Bean 类:

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

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

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

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

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

下面的bean定义指定通过调用工厂方法来创建bean。该定义不指定返回对象的类型(类),只指定包含工厂方法的类。在本例中,createInstance()方法必须是静态方法。以下示例显示如何指定工厂方法:

<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属性保留为空,并在factorybean属性中指定当前(或父级或祖先级)容器中的bean名称,该容器包含要调用以创建对象的实例方法。使用factory方法属性设置factory方法本身的名称。下面的示例演示如何配置这样的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"/>

一个工厂类还可以包含多个工厂方法,如下面的示例所示:

<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文档中,“factory bean”是指在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相比之下,FactoryBean(注意大写)指的是特定于Spring的FactoryBean实现类。
确定 Bean 的运行时类型

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

了解特定bean的实际运行时类型的推荐方法是BeanFactory。指定bean名称的getType调用。这将考虑上述所有情况,并返回BeanFactory需要的对象类型。getBean调用将返回相同的bean名称。

依赖关系

依赖关系注入 (DI) 是一个过程,在该过程中,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回对象实例后在对象实例上设置的属性来定义其依赖关系(即,它们与之一起工作的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。从根本上说,此过程是 Bean 本身的反转(因此称为”控制反转”),通过使用类或服务定位器模式的直接构造来控制其依赖项的实例化或位置。

依赖注入

使用 DI 原则,代码更清晰,当为对象提供其依赖项时,解耦更有效。对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

DI存在两个主要变体:基于构造函数的依赖注入基于Setter的依赖注入

基于构造函数的依赖关系注入

基于构造函数的DI是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造bean几乎是等效的,本讨论类似地处理构造函数和静态工厂方法的参数。以下示例显示了一个类,该类只能通过构造函数注入进行依赖项注入:

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和ThingTree类没有继承关系,则不存在潜在的歧义。因此,以下配置工作正常,您不需要在<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可以从构造函数中查找参数名称。如果不能或不想使用 debug 标志编译代码,则可以使用@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是由容器在调用无参数构造函数或无参数静态工厂方法实例化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用户并不直接使用这些类(即编程),而是使用基于Java的@Configuration类中的XMLBean定义、带注释的组件(即用@Component、@Controller等注释的类)或@bean方法。然后,这些源在内部转换为BeanDefinition的实例,并用于加载整个SpringIOC容器实例。

基于构造函数还是基于 setter 的 DI?

由于您可以混合使用基于构造函数和基于 setter 的 DI,因此将构造函数用于强制依赖项,将构造函数方法或配置方法用于可选依赖项是一个很好的经验法则。请注意,在 setter 方法上使用@Required注释可用于使属性成为必需的依赖项;但是,最好使用参数的编程验证进行构造函数注入。

Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为null。此外,构造函数注入的组件总是以完全初始化的状态返回给客户机(调用)代码。作为旁注,大量构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的责任,应该进行重构以更好地解决问题的适当分离。

Setter 注入应主要用于可选依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的所有位置执行非空检查。setter 注入的一个好处是,setter 方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBeans进行管理是 setter 注入的一个引人注目的用例。

使用对特定类最有意义的 DI 样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类不公开任何 setter 方法,则构造函数注入可能是 DI 的唯一可用形式。

依赖关系解析过程

容器执行 Bean 依赖关系解析,如下所示:

  • ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以由XML、Java代码或注释指定。
  • 对于每个 Bean,其依赖项以属性、构造函数参数或静态工厂方法的参数的形式表示(如果使用它而不是普通的构造函数)。当实际创建 Bean 时,这些依赖项将提供给 Bean。
  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 Bean 的引用。
  • 作为值的每个属性或构造函数参数都将从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,例如int、long、string、boolean等。

Spring 容器在创建容器时验证每个 Bean 的配置。但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。创建容器时,将创建单一实例范围并设置为预实例化(默认值)的 Bean。作用域在Bean 作用域中定义。否则,仅当请求 Bean 时才会创建该 Bean。创建 Bean 可能会导致创建 Bean 图,因为创建并分配了 Bean 的依赖项及其依赖项的依赖关系(依此类推)。请注意,这些依赖项之间的分辨率不匹配可能会延迟出现,即在首次创建受影响的 Bean 时。

循环依赖关系

如果主要使用构造函数注入,则可以创建无法解析的循环依赖关系方案。

例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入需要类A的实例。如果为类A和类B配置bean以相互注入,Spring IoC容器将在运行时检测此循环引用,并抛出BeanCurrentlyIncrementationException。

一种可能的解决方案是编辑某些类的源代码,由 setter 而不是构造函数配置。或者,避免构造函数注入,仅使用 setter 注入。换句话说,尽管不建议这样做,但您可以使用 setter 注入来配置循环依赖关系。

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


您通常可以相信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 定义,如下所示:

<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被告知调用静态工厂方法来返回对象的实例,而不是使用构造函数。

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

静态工厂方法的参数由<constructor-arg/>元素提供,与实际使用的构造函数完全相同。factory方法返回的类的类型不必与包含静态factory方法的类的类型相同(尽管在本例中是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用工厂bean属性而不是类属性之外),因此我们这里不讨论这些细节。

依赖关系和配置详情

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

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

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

<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 命名空间进行更简洁的 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>

TheSpring容器将元素中的文本转换为java。util。使用JavaBeans PropertyEditor机制创建属性实例。这是一个很好的快捷方式,也是Spring团队喜欢使用嵌套的元素而不是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是否确实存在。在第二个变体中,没有对传递给客户机bean的targetName属性的值执行任何验证。只有在实际实例化客户机bean时才会发现输入错误(很可能是致命的结果)。如果客户机bean是一个原型bean,那么只有在部署容器后很长一段时间才能发现这个输入错误和由此产生的异常。

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

<idref/>元素带来价值的一个常见位置(至少在Spring2.0之前的版本中)是ProxyFactoryBean定义中的AOP拦截器配置。在指定拦截器名称时使用元素可以防止拼写错误拦截器ID。

对其他bean类的引用(合作者)

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

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

<ref bean="someBean"/>

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

<!-- 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>
ref元素上的local属性在4.0bean XSD中不再受支持,因为它不再提供常规bean引用上的值。升级到4.0模式时,将现有的ref本地引用更改为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或名称。如果指定,则容器不使用此类值作为标识符。容器在创建时也会忽略作用域标志,因为内部bean总是匿名的,并且总是使用外部bean创建的。不可能单独访问内部bean,也不可能将它们注入到协作bean中,而不是封闭bean中。

作为极端情况,可以从自定义作用域接收销毁回调 — 例如,对于单例 Bean 中包含的请求范围的内 Bean。内 Bean 实例的创建与其包含 Bean 相关联,但销毁回调允许它参与请求范围的生命周期。这不是一种常见的情况。内豆通常只是简单地共享其包含豆的范围。

集合

<list/>、<set/>、<map/>和<props/>元素分别设置Java集合类型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>

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

bean | ref | idref | list | set | map | props | value | null

集合合并

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

这部分关于合并的内容将讨论父子 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定义的adminEmails属性的<props/>元素上使用了merge=true属性。当容器解析并实例化子bean时,生成的实例具有一个adminEmails属性集合,该集合包含将子bean的adminEmails集合与父bean的adminEmails集合合并的结果。下面的列表显示了结果:

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

子属性集合的值集继承父级的所有属性元素,并且支持值的子级值覆盖父级集合中的值。

此合并行为类似于<list/>、<map/>和<set/>集合类型。在元素的特定情况下,与列表集合类型(即值的有序集合的概念)相关联的语义将得到维护。父列表的值位于子列表的所有值之前。对于Map、Set和Properties集合类型,不存在排序。因此,对于容器内部使用的关联映射、集合和属性实现类型的基础集合类型,没有有效的排序语义。

集合合并的限制

不能合并不同的集合类型(例如映射和列表)。如果确实尝试这样做,则会引发相应的异常。必须在较低的继承子定义上指定合并属性。在父集合定义上指定“合并”属性是多余的,不会导致所需的合并。

强类型集合

随着Java5中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明集合类型,使其只能包含(例如)字符串元素。如果使用Spring将强类型集合注入bean中,则可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。以下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>

当somethingbean的accounts属性准备好注入时,关于强类型映射的元素类型的泛型信息可以通过反射获得。因此,Spring的类型转换基础设施将各种值元素识别为Float类型,字符串值(9.99、2.75和3.99)被转换为实际的Float类型。

Null值和空字符串值

Spring将属性等的空参数视为空字符串。以下基于XML的配置元数据片段将电子邮件属性设置为空字符串值(“”)。

<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-namespace允许您使用bean元素的属性(而不是嵌套的<property/>元素)来描述您的属性值。

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

下面的示例演示解析为相同结果的两个 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
        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定义中名为email的p名称空间中的一个属性。这告诉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=“party”ref=“jane”/>来创建从bean john到bean jane的引用,而第二个bean定义使用p:party ref=“jane”作为属性来执行完全相同的操作。在本例中,party是属性名,而-ref部分表示这不是一个直接值,而是对另一个bean的引用。

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

与具有p名称空间的XML快捷方式类似,Spring3.1中引入的c名称空间允许内联属性来配置构造函数参数,而不是嵌套的构造函数arg元素。

以下示例使用c:命名空间执行与基于from构造函数的依赖项注入相同的操作:

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

命名空间使用与p:one(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语法的原因,索引表示法要求出现前导ux,因为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。为了使其工作,在构造bean之后,something的fred属性和fred的bob属性不能为null。否则,将抛出NullPointerException。

使用depends-on

如果一个bean是另一个bean的依赖项,这通常意味着一个bean被设置为另一个bean的属性。通常,您可以通过基于XML的配置元数据中的元素来实现这一点。然而,有时候bean之间的依赖关系不那么直接。例如,需要触发类中的静态初始值设定项,例如数据库驱动程序注册。dependens属性可以显式地强制一个或多个bean在使用该元素的bean初始化之前进行初始化。以下示例使用depends-depen属性表示对单个bean的依赖关系:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

要表示对多个bean的依赖关系,请提供一个bean名称列表作为dependens-depen属性的值(逗号、空格和分号是有效的分隔符):

<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-depen属性既可以指定初始化时间依赖,也可以指定相应的销毁时间依赖(仅在单例bean的情况下)。定义与给定bean的dependens-depend关系的依赖bean首先被销毁,然后再销毁给定bean本身。因此,取决于是否还可以控制停机顺序。

惰性初始化的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使用时,当ApplicationContext启动时,惰性bean不会被急切地预实例化,而延迟bean则不会。lazybean急切地被预先实例化。

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

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

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

自动装配合作者

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

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

使用基于XML的配置元数据时(请参见依赖项注入),可以使用<bean/>元素的autowire属性为bean定义指定autowire模式。自动布线功能有四种模式。您可以为每个bean指定自动连线,从而可以选择要自动连线的bean。下表介绍了四种自动布线模式:

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

使用byType或构造函数自动连接模式,可以连接数组和类型化集合。在这种情况下,将提供容器中与预期类型匹配的所有autowire候选项以满足依赖关系。如果所需的键类型为字符串,则可以自动关联强类型映射实例。自动连线映射实例的值由与预期类型匹配的所有bean实例组成,映射实例的键包含相应的bean名称。

自动装配的局限性和缺点

自动装配在项目中一致使用时效果最佳。如果通常不使用自动布线,则开发人员可能会混淆仅使用它来连接一个或两个 Bean 定义。

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

  • 属性和构造函数参数设置中的显式依赖项始终覆盖自动关联。不能自动关联简单属性,例如基本体、字符串和类(以及此类简单属性的数组)。这一限制是故意造成的。
  • 自动布线不如显式布线精确。尽管如此,如前表所述,Spring小心避免在可能产生意外结果的歧义情况下进行猜测。Spring托管对象之间的关系不再明确记录。
  • 布线信息可能不适用于可能从Spring容器生成文档的工具。
  • 容器中的多个bean定义可能与要自动连接的setter方法或构造函数参数指定的类型匹配。对于数组、集合或映射实例,这不一定是问题。然而,对于期望单个值的依赖项,这种模糊性并不是任意解决的。如果没有唯一的bean定义可用,将引发异常。

在后一种情况下,您有几个选项:

  • 放弃自动布线,转而采用显式布线。
  • 通过将bean定义的autowire候选属性设置为false,避免bean定义的自动关联,如下一节所述。
  • 通过将<bean/>其元素的primary属性设置为true,将单个bean定义指定为主要候选项。
  • 如基于注释的容器配置中所述,使用基于注释的配置实现更细粒度的控制。
从自动装配中排除 Bean

在每个bean的基础上,您可以将bean从自动连接中排除。在Spring的XML格式中,将元素的autowire-candidate属性设置为false。该容器使该特定bean定义对自动布线基础结构(包括注释样式配置,如@Autowired)不可用。

“autowire-candidate”属性旨在仅影响基于类型的自动关联。它不会按名称影响显式引用,即使指定的bean未标记为autowire候选,也会解析显式引用。因此,如果名称匹配,按名称自动连接将注入bean。

您还可以基于对bean名称的模式匹配来限制autowire候选项。顶级<bean/>元素在其默认的autowire候选属性中接受一个或多个模式。例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供值*Repository。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的autowire候选属性的显式值true或false始终优先。对于这样的bean,模式匹配规则不适用。

这些技术对于您永远不希望通过自动连接注入其他bean的bean非常有用。这并不意味着排除的bean本身不能通过使用自动连接进行配置。相反,bean本身不是自动连接其他bean的候选对象。

方法注入

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

一个解决办法是放弃一些控制反转。您可以通过实现ApplicationContextAware接口,并在每次bean A需要时对容器发出getBean(“B”)调用以请求(通常是新的)bean 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框架并与之耦合。方法注入是SpringIoC容器的一个稍微高级的特性,它允许您干净地处理这个用例。

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

查找方法注入

查找方法注入是容器重写容器管理的 Bean 上的方法并返回容器中另一个命名 Bean 的查找结果的能力。查找通常涉及原型 Bean,如上一节中描述的方案所示。Spring Framework 通过使用 CGLIB 库中的字节码生成来动态生成重写该方法的子类来实现此方法注入。

为了使这个动态子类能够工作,springbean容器子类的类不能是final,要重写的方法也不能是final。

对具有抽象方法的类进行单元测试需要您自己对该类进行子类化,并提供抽象方法的存根实现。

组件扫描还需要具体的方法,这需要具体的类来拾取。

另一个关键限制是,查找方法不适用于工厂方法,特别是配置类中的@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);

如果方法是抽象的,则动态生成的子类将实现该方法。否则,动态生成的子类将重写在原始类中定义的具体方法。考虑下面的例子:

<!-- 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()方法。如果需要的话,您必须小心地将mycommandbean部署为原型。如果是单例,则每次都返回相同的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/提供者注入点。请参 阅作用域bean视为依赖项

您还可能发现ServiceLocatoryFactoryBean(在org.springframework.beans.factory.config包中)很有用。
任意方法替换

与查找方法注入相比,方法注入的一种不太有用的形式是能够将托管 Bean 中的任意方法替换为另一种方法实现。您可以安全地跳过本节的其余部分,直到您实际需要此功能。

With XM对于基于XML的配置元数据,对于已部署的bean,可以使用替换的method元素将现有的方法实现替换为另一个方法实现。考虑下面的类,它有一个我们想重写的叫做CopyTealValk的方法:

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

您可以在<replaced method/>元素中使用一个或多个元素来指示要重写的方法的方法签名。只有当方法重载且类中存在多个变量时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有选项都与java匹配。字符串:

java.lang.String
String
Str

由于参数数通常足以区分每个可能的选项,因此此快捷方式可以节省大量键入,方法是允许您仅键入与参数类型匹配的最短字符串。

Bean 作用域

当您创建一个bean定义时,您创建了一个菜谱,用于创建由该bean定义定义的类的实际实例。bean定义是一个菜谱的想法很重要,因为这意味着,与类一样,可以从一个菜谱创建多个对象实例。

您不仅可以控制要插入到从特定bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别烘焙对象的范围。可以将bean定义为部署在多个作用域中的一个。Spring框架支持六个作用域,其中四个只有在使用web感知的ApplicationContext时才可用。您还可以创建自定义范围。
下表描述了支持的作用域:

范围描述
singleton(默认)为每个SpringIOC容器将单个bean定义的范围限定为单个对象实例。
prototype将单个bean定义的范围限定为任意数量的对象实例。
request将单个bean定义限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的后面创建的。仅在支持web的Spring应用程序上下文的上下文中有效。
session将单个bean定义限定为HTTP会话的生命周期。仅在支持web的Spring应用程序上下文的上下文中有效。
application将单个bean定义限定到ServletContext的生命周期。仅在支持web的Spring应用程序上下文的上下文中有效。
websocket将单个bean定义的范围限定到WebSocket的生命周期。仅在支持web的Spring应用程序上下文的上下文中有效。

从 Spring 3.0 开始,thread 作用域可用,但默认情况下未注册。有关详细信息,请参阅SimpleThreadScope的文档。有关如何注册此范围或任何其他自定义范围的说明,请参阅使用自定义范围

单例作用域

仅管理单例 Bean 的一个共享实例,并且对 ID 与该 Bean 定义匹配的 ID 的所有 Bean 请求都会导致 Spring 容器返回该特定 Bean 实例。

换句话说,当您定义 Bean 定义并将其范围限定为单例时,Spring IoC 容器将只创建由该 Bean 定义定义的对象的一个实例。此单个实例存储在此类单例 Bean 的缓存中,并且对该命名 Bean 的所有后续请求和引用都将返回缓存的对象。下图显示了单例作用域的工作原理:

Spring的单例豆的概念与《四人帮》(GoF)模式书中定义的单例模式不同。GoF 单例对对象的作用域进行硬编码,以便每个 ClassLoader 创建一个且只有一个特定类的实例。Spring单例的范围最好被描述为每个容器和每个豆子。这意味着,如果在单个 Spring 容器中为特定类定义一个 Bean,则 Spring 容器将创建该 Bean 定义所定义的类的一个且仅一个实例。单例作用域是 Spring 中的默认作用域。若要在 XML 中将 Bean 定义为单例,可以定义一个 Bean,如下面的示例所示:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

原型范围

只bean部署的非单例原型范围导致每次对特定bean发出请求时都创建一个新的bean实例。也就是说,bean被注入到另一个bean中,或者通过容器上的getBean()方法调用来请求它。通常,所有有状态bean都应该使用prototype作用域,无状态bean应该使用singleton作用域。

下图说明了Spring原型的范围:

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不保留任何会话状态。我们更容易重用单例图的核心。

下面的示例将 Bean 定义为 XML 中的原型:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

与其他示波器相比,Spring不管理原型Bean的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其传递给客户端,不再记录该原型实例。因此,尽管无论作用域如何,都会对所有对象调用初始化生命周期回调方法,但在原型的情况下,不会调用配置的销毁生命周期回调。客户端代码必须清理原型范围的对象,并释放原型 Bean 所持有的昂贵资源。要让 Spring 容器释放原型范围的 bean 所持有的资源,请尝试使用自定义Bean 后处理器,其中包含对需要清理的 Bean 的引用。

在某些方面,Spring容器在原型范围bean中的角色是Java新操作符的替代品。所有超过该点的生命周期管理都必须由客户机处理。(有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)

 具有原型bean依赖关系的单例bean

将单例范围的 Bean 与原型 Bean 的依赖关系一起使用时,请注意,依赖关系是在实例化时解析的。因此,如果将原型范围的 Bean 从依赖关系注入到单例范围的 Bean 中,则会实例化一个新的原型 Bean,然后将依赖关系注入到单例 Bean 中。原型实例是提供给单例范围 Bean 的唯一实例。

但是,假设您希望单例范围的 Bean 在运行时重复获取原型范围的 Bean 的新实例。您不能将原型范围的 Bean 从依赖关系注入到单例 Bean 中,因为当 Spring 容器实例化单例 Bean 并解析并注入其依赖项时,该注入仅发生一次。如果在运行时多次需要原型 Bean 的新实例,请参 阅方法注入

请求、会话、应用程序和 WebSocket 范围

请求、会话、应用程序和websocket作用域仅在使用web感知的Spring ApplicationContext实现(如XmlWebApplicationContext)时可用。如果将这些作用域与常规Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会引发一个IllegalStateException,该异常会抱怨未知的bean作用域。

初始 Web 配置

为了在请求、会话、应用程序和websocket级别(web范围的bean)支持bean的作用域,在定义bean之前需要进行一些较小的初始配置。(标准作用域:singleton和prototype不需要此初始设置。)

如何完成此初始设置取决于您的特定 Servlet 环境。

如果您在SpringWebMVC中访问作用域bean,实际上是在SpringDispatcherServlet处理的请求中,则不需要特殊设置。DispatcherServlet已公开所有相关状态。

如果使用Servlet2.5Web容器,请求在Spring的DispatcherServlet之外处理(例如,当使用JSF或Struts时),则需要注册该组织。springframework。网状物上下文要求RequestContextListener ServletRequestListener。对于Servlet 3.0+,这可以通过使用WebApplicationInitializer接口以编程方式完成。或者,对于旧容器,将以下声明添加到web应用程序的web中。xml文件:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

或者,如果您的侦听器设置存在问题,请考虑使用Spring的RealestCutExter过滤器。过滤器映射取决于周围的web应用程序配置,因此必须根据需要进行更改。以下列表显示了web应用程序的筛选器部分:

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServlet、RequestContextListener和RequestContextFilter都执行完全相同的操作,即将HTTP请求对象绑定到为该请求提供服务的线程。这使得请求和会话作用域的bean在调用链的更下游可用。

请求范围

请考虑 Bean 定义的以下 XML 配置:

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

Spring容器通过为每个HTTP请求使用LoginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的作用域是HTTP请求级别。您可以随意更改创建的实例的内部状态,因为从同一loginAction bean定义创建的其他实例在状态中看不到这些更改。它们是特定于个人请求的。当请求完成处理时,作用域为请求的bean将被丢弃。

使用注释驱动组件或Java配置时,@RequestScope注释可用于将组件分配给请求范围。以下示例显示了如何执行此操作:

@RequestScope
@Component
public class LoginAction {
    // ...
}
会话范围

请考虑 Bean 定义的以下 XML 配置:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Spring容器通过在单个HTTP会话的生存期内使用UserPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean有效地限定了HTTP会话级别的范围。与请求范围的bean一样,您可以根据需要更改创建的实例的内部状态,因为其他HTTP会话实例也在使用从相同的userPreferences bean定义创建的实例,它们在状态中看不到这些更改,因为它们是特定于单个HTTP会话的。当HTTP会话最终被丢弃时,作用于该特定HTTP会话的bean也被丢弃。

使用注释驱动组件或Java配置时,可以使用@SessionScope注释将组件分配给会话范围。

@SessionScope
@Component
public class UserPreferences {
    // ...
}
应用范围

请考虑 Bean 定义的以下 XML 配置:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Spring容器通过为整个web应用程序使用AppPreferences bean定义一次来创建AppPreferences bean的新实例。也就是说,appPreferences bean的作用域在ServletContext级别,并作为常规ServletContext属性存储。这有点类似于Spring单例bean,但在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring ApplicationContext(在任何给定的web应用程序中可能有多个),并且它实际上是公开的,因此作为ServletContext属性可见。

使用注释驱动组件或Java配置时,可以使用@ApplicationScope注释将组件分配给应用程序范围。以下示例显示了如何执行此操作:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
网络插座范围

WebSocket 作用域与 WebSocket 会话的生命周期相关联,并应用于 STOMP over WebSocket 应用程序,有关详细信息,请参阅WebSocket 作用域

作用域Bean作为依赖项

Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果要将 HTTP 请求范围的 Bean 注入(例如)另一个生存期较长范围的 Bean 中,则可以选择注入 AOP 代理来代替作用域内的 Bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(如 HTTP 请求)检索真正的目标对象,并将方法调用委托给实际对象。

您还可以在作用域为singleton的bean之间使用,然后引用将通过可序列化的中间代理,从而能够在反序列化时重新获取目标singleton bean。

当针对范围bean原型声明时,共享代理上的每个方法调用都会导致创建一个新的目标实例,然后将调用转发到该实例。

此外,作用域代理并不是以生命周期安全的方式从较短作用域访问bean的唯一方法。您还可以将注入点(即构造函数或setter参数或autowired字段)声明为ObjectFactory,允许每次需要时调用getObject()来按需检索当前实例 — 无需保留实例或单独存储它。

作为一个扩展变量,您可以声明ObjectProvider,它提供了几个额外的访问变量,包括getIfAvailable和getIfUnique。

它的JSR-330变体称为Provider,它与Provider声明以及每次检索尝试的相应get()调用一起使用。有关JSR-330整体的更多详细信息,请参见此处。

以下示例中的配置仅为一行,但了解其背后的“为什么”和“如何”很重要:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/>  ①
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>


①定义代理的行。

要创建这样一个代理,需要将一个子元素插入到作用域bean定义中(请参阅选择要创建的代理类型和基于XML模式的配置)。为什么在请求、会话和自定义作用域级别定义作用域的bean需要元素?考虑下面的单点bean定义,并将它与前面所定义的范围进行比较(注意,下面的USERAPIONE bean定义不完整):

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在前面的示例中,向singleton bean(userManager)注入了对HTTP会话范围bean(userPreferences)的引用。这里最突出的一点是usermanagerbean是一个单例:每个容器只实例化一次,其依赖项(在本例中只有一个,userPreferences bean)也只注入一次。这意味着usermanagerbean只对完全相同的userPreferences对象(即最初注入它的对象)进行操作。

当将一个寿命较短的作用域bean注入一个寿命较长的作用域bean(例如,将一个HTTP会话作用域的协作bean作为依赖项注入到单例bean中)时,这不是您想要的行为。相反,您需要一个userManager对象,并且在HTTP会话的生存期内,您需要一个特定于HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想情况下是一个UserPreferences实例的对象),该对象可以从作用域机制(HTTP请求、会话等)获取真实的UserPreferences对象。容器将这个代理对象注入userManager bean,而userManager bean不知道这个UserPreferences引用是一个代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法。然后代理从HTTP会话(在本例中)获取real UserPreferences对象,并将方法调用委托给检索到的real UserPreferences对象。

因此,在将请求和会话范围的bean注入协作对象时,需要以下(正确且完整的)配置,如下例所示:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

自定义范围

bean作用域机制是可扩展的。您可以定义自己的作用域,甚至可以重新定义现有的作用域,尽管后者被认为是不好的做法,并且您不能覆盖内置的单例和原型作用域。

创建自定义作用域

要将自定义范围集成到Spring容器中,您需要实现org。springframework。豆。工厂配置。作用域接口,本节将对此进行描述。要了解如何实现自己的作用域,请参阅Spring框架本身和Scope javadoc提供的作用域实现,其中更详细地解释了需要实现的方法。

Scope接口有四种方法可以从作用域中获取对象、从作用域中删除对象以及销毁对象。

例如,会话作用域实现返回会话作用域bean(如果它不存在,则在将bean绑定到会话以供将来引用后,该方法返回bean的新实例)。以下方法从基础作用域返回对象:

Object get(String name, ObjectFactory<?> objectFactory)

例如,会话作用域实现从底层会话中删除会话作用域bean。应该返回该对象,但如果找不到具有指定名称的对象,则可以返回null。以下方法从基础作用域中删除对象:

Object remove(String name)

以下方法注册一个回调,当作用域被销毁或作用域中的指定对象被销毁时,该回调应被调用:

void registerDestructionCallback(String name, Runnable destructionCallback)

请参阅javadoc或 Spring 范围实现,了解有关销毁回调的更多信息。

以下方法获取基础作用域的会话标识符:

String getConversationId()

每个作用域的此标识符都不同。对于会话范围的实现,此标识符可以是会话标识符。

使用自定义范围

在编写和测试一个或多个自定义作用域实现之后,您需要让Spring容器知道您的新作用域。以下方法是向Spring容器注册新作用域的中心方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上声明,该接口可通过Spring附带的大多数具体ApplicationContext实现的BeanFactory属性获得。

注册表范围(..)的第一个参数方法是与作用域关联的唯一名称。Spring容器本身中此类名称的示例有singleton和prototype。注册表范围(..)的第二个参数方法是要注册和使用的自定义范围实现的实际实例。

假设编写自定义范围实现,然后注册它,如下一个示例所示。

下一个示例使用SimpleThreadScope,它包含在Spring中,但默认情况下未注册。对于您自己的自定义范围实现,说明将是相同的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

然后,您可以创建符合自定义范围的作用域规则的bean定义,如下所示:

<bean id="..." class="..." scope="thread">

使用自定义范围实现,您不限于范围的编程注册。您还可以使用CustomScopeConfigurer类以声明方式进行范围注册,如下例所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>
当您将放在FactoryBean实现的声明中时,作用域是工厂bean本身,而不是从getObject()返回的对象。

自定义 Bean 的性质

Spring Framework提供了许多界面,您可以使用这些接口来自定义Bean的性质。本节按如下方式对它们进行分组:

生命周期回调

为了与容器对bean生命周期的管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用AfterPropertieSet(),为后者调用destroy(),以便bean在初始化和销毁bean时执行某些操作。

JSR-250@PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于Spring的接口。有关详细信息,请参见使用@PostConstruct和@PreDestroy。

如果不想使用JSR-250注释,但仍想删除耦合,请考虑init方法和销毁方法bean定义元数据。

在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要定制功能或Spring默认不提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关详细信息,请参见容器扩展点。

除了初始化和销毁回调之外,Spring托管对象还可以实现生命周期接口,以便这些对象可以参与启动和关闭过程,这是由容器自身的生命周期驱动的。

本节介绍了生命周期回调接口。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean在容器对bean设置了所有必要的属性之后执行初始化工作。InitializingBean接口指定一个方法:

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init method属性指定具有void no-argument签名的方法的名称。通过Java配置,您可以使用@Bean的initMethod属性。请参阅接收生命周期回调。考虑下面的例子:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
公共类ExampleBean{
公共void init(){
//做一些初始化工作
}
}

前面的示例与以下示例的效果几乎完全相同(由两个列表组成):

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}

但是,前面两个示例中的第一个示例不会将代码耦合到 Spring。

销毁回调

实施组织。springframework。豆。工厂DisposableBean接口允许bean在包含它的容器被销毁时获得回调。DisposableBean接口指定一个方法:

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PreDestroy注释或指定bean定义支持的通用方法。使用基于XML的配置元数据,您可以在<bean/>上使用destroy method属性。通过Java配置,您可以使用@Bean的destroyMethod属性。请参阅接收生命周期回调。考虑下面的定义:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

前面的定义与以下定义几乎具有完全相同的效果:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

但是,前面两个定义中的第一个定义不会将代码耦合到Spring。

您可以为<bean/>元素的destroy method属性指定一个特殊(推断)值,该值指示Spring自动检测特定bean类上的公共关闭或关闭方法。(因此,实现java.lang.AutoCloseable或java.io.Closeable的任何类都将匹配。)您还可以在元素的default destroy method属性上设置此特殊(推断)值,以将此行为应用于整个bean集(请参见default Initialization and destroy Methods)。请注意,这是Java配置的默认行为。
默认初始化和销毁方法

编写初始化和销毁方法回调时,不使用Spring特定的初始化bean和销毁bean回调接口,您通常会编写名称为 init()、initialize()、dispose() 等的方法。理想情况下,这种生命周期回调方法的名称在整个项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

您可以将Spring容器配置为“look”命名的,并初始化、销毁每个bean上的回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()的初始化回调,而不必为每个bean定义配置init method=“init”属性。springioc容器在创建bean时调用该方法(并根据前面描述的标准生命周期回调契约)。此功能还强制执行初始化和销毁方法回调的一致命名约定。

假设初始化回调方法名为init(),销毁回调方法名为destroy()。然后,您的类与以下示例中的类相似:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

然后,您可以在类似于以下内容的 bean 中使用该类:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

本篇完,还有疑问?留下评论吧

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注