官方文档链接:https://docs.spring.io/spring-framework/docs/current/reference/html/index.html

源码下载(5.3.8版本):https://github.com/spring-projects/spring-framework/releases/tag/v5.3.8

编译前先安装gradle:https://gradle.org/releases/

配置环境变量

编译:将下载好的代码压缩包解压后,进入主目录,打开cmd运行:gradlew :spring-oxm:compileTestJava

Welcome to Gradle 6.8.3!

Here are the highlights of this release:

 - Faster Kotlin DSL script compilation
 - Vendor selection for Java toolchains
 - Convenient execution of tasks in composite builds
 - Consistent dependency resolution

For more details see https://docs.gradle.org/6.8.3/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
fatal: not a git repository (or any of the parent directories): .git

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.8.3/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 4m 50s
44 actionable tasks: 25 executed, 19 from cache
Build scan background action failed.
org.gradle.process.internal.ExecException: Process 'command 'git'' finished with non-zero exit value 128
        at org.gradle.process.internal.DefaultExecHandle$ExecResultImpl.assertNormalExitValue(DefaultExecHandle.java:414)
        at org.gradle.process.internal.DefaultExecAction.execute(DefaultExecAction.java:38)
        at org.gradle.process.internal.DefaultExecActionFactory.exec(DefaultExecActionFactory.java:175)
        at io.spring.ge.conventions.gradle.WorkingDirectoryProcessOperations.exec(WorkingDirectoryProcessOperations.java:45)
        at io.spring.ge.conventions.gradle.ProcessOperationsProcessRunner.run(ProcessOperationsProcessRunner.java:41)
        at io.spring.ge.conventions.core.BuildScanConventions.run(BuildScanConventions.java:166)
        at io.spring.ge.conventions.core.BuildScanConventions.addGitMetadata(BuildScanConventions.java:113)
        at io.spring.ge.conventions.gradle.GradleConfigurableBuildScan.lambda$background$0(GradleConfigurableBuildScan.java:104)
        at com.gradle.enterprise.gradleplugin.internal.extension.b$3.run(SourceFile:101)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

1. IoC容器 -5.3.8

1.1 Spring IoC容器和bean的介绍

对象只能通过构造函数参数、工厂方法的参数或在对象实例被构造以及从工厂方法返回后设置的属性来定义它们的依赖关系(即它们使用的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程基本上是bean本身通过直接构造类或诸如Service Locator模式这样的机制来控制其依赖项的实例化或位置的逆过程(因此得名“控制反转”)。

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。BeanFactory接口提供了能够管理任何类型对象的高级配置机制。ApplicationContext是BeanFactory的一个子接口,添加了更多特定于企业的功能。

bean是由Spring IoC容器实例化、组装和管理的对象。bean及其之间的依赖关系反映在容器使用的配置元数据中。

1.2 容器概述

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

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

在大多数应用程序场景中,不需要显式的用户代码来实例化Spring IoC容器的一个或多个实例。例如,在一个web应用程序场景中,在应用程序的web. XML文件中简单的8行(大约)web描述符XML样本就足够了。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

下图显示了Spring如何工作的高级视图。应用程序类与配置元数据相结合,这样在ApplicationContext创建并初始化之后,您就拥有了一个配置完整且可执行的系统或应用程序。

image-20210620151320785

1.2.1 配置元数据

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

配置元数据传统上以一种简单而直观的XML格式提供,本章的大部分内容都使用这种格式来传达Spring IoC容器的关键概念和特性。

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

  • spring2.5引入了对基于注释的配置元数据的支持,具体使用在1.9节进行介绍。
  • Spring3.0引入了基于Java的配置,更多知识在1.12进行详细说明。

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

这些bean定义对应于组成应用程序的实际对象。通常,您需要定义服务层对象、数据访问对象(DAO)、表示对象(如Struts Action实例)、基础设施对象(如Hibernate SessionFactories、JMS队列)等等。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是 DAO 和业务逻辑的责任。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象【见5.10】。

下面的例子展示了基于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.4】。

1.2.2 容器实例化

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

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

在您了解 Spring 的 IoC 容器之后,您可能想了解更多关于 Spring 的resource概念【见2】,它提供了一种从 URI 语法中定义的位置读取 InputStream 的便捷机制。特别是,资源路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述【见2.8】。

以下示例显示了服务层对象(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>

下面的例子显示了数据访问对象dao .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类和JpaAccountDao和JpaItemDao(基于JPA对象关系映射标准)类型的两个数据访问对象组成。属性名元素引用JavaBean属性的名称,ref元素引用另一个bean定义的名称。id和ref元素之间的这种链接表示了协作对象之间的依赖关系。有关配置对象依赖关系的详细信息,请参见依赖关系【见1.4】。

编写基于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.xml、messageSource.xml和themeSource.xml。

所有位置路径都相对于执行导入的定义文件,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置,而messageSource.xml和themeSource.xml必须位于导入文件位置下面的资源位置。如您所见,前导斜杠被忽略。 但是,鉴于这些路径是相对的,最好根本不使用斜杠。 根据 Spring Schema,被导入文件的内容,包括顶级 <beans/> 元素,必须是有效的 XML bean 定义。

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

您可以始终使用完全限定的资源位置,而不是相对路径:例如,文件:C:/config/services.xml或类路径:/config/services.xml。但是,请注意,您是将应用程序的配置耦合到特定的绝对位置。

通常,最好为这些绝对位置保留一个间接位置——例如,通过在运行时根据JVM系统属性解析的“${…}”占位符。

命名空间本身提供了导入指令特性。除了普通bean定义之外,Spring提供的XML名称空间中还提供了更多的配置特性——例如,上下文和util名称空间。

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();

最灵活的变体是GenericApplicationContext与reader委托结合使用——例如,XML文件使用XmlBeanDefinitionReader,如下例所示:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
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定义的一组属性。下表描述了这些属性:

属性 详细介绍
Class [实例化Bean](#1.3.2 实例化Bean)
Name 命名Bean
Scope [Bean范围](#1.5 Bean范围)
Constructor arguments [依赖注入](#1.4.1 依赖注入)
Properties [依赖注入](#1.4.1 依赖注入)
Autowiring mode [自动装配协作者](#1.4.5 自动装配协作者)
Lazy initialization mode [延迟初始化的Bean](#1.4.4 延迟初始化的Bean)
Initialization method 初始化回调
Destruction method 破坏回调

除了包含有关如何创建特定 bean 信息的 bean 定义之外,ApplicationContext 的实现类还允许注册在容器外部(由用户创建)创建的现有对象。

这是通过 getBeanFactory() 方法访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 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 类型。 请注意,bean id 唯一性仍由容器强制执行,但不再由 XML 解析器强制执行。

您不需要为bean提供名称或id。如果您没有显式地提供名称或id,容器将为该bean生成唯一的名称。但是,如果希望通过使用ref元素或Service Locator样式查找按名称引用该bean,则必须提供名称。不提供名称的动机与使用内部bean和[自动装配协作者](#1.4.5 自动装配协作者)有关。

Bean命名约定:

约定是在命名Bean时对实例字段名使用标准Java约定。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰大小写。这些名称的示例包括accountManager、accountService、userDao、loginController等。

保持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的名称引用一个数据源。子系统B的配置元数据可以通过subsystemB-dataSource的名称来引用一个数据源。当组合使用这两个子系统的主应用程序时,主应用程序通过myApp-dataSource的名称引用数据源。要让这三个名称都引用同一个对象,可以在配置元数据中添加以下别名定义:

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

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

如果使用javaconconfiguration,可以使用@Bean注释提供别名。详细信息请参见使用@Bean注释

1.3.2 实例化Bean

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

如果使用基于xml的配置元数据,则需要指定要在<bean/>元素的class属性,这个class属性通常是强制性的。你可以用以下两种方式之一来使用Class属性:

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

嵌套类命名:

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

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

使用构造函数进行实例化

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

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

使用基于xml的配置元数据,你可以如下指定你的bean类:

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

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

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

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

定义使用静态工厂方法创建的 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 属性留空,并在 factory-bean 属性中指定当前(或父或祖先)容器中 bean 的名称,该容器包含要调用以创建对象的实例方法。 使用 factory-method 属性设置工厂方法本身的名称。 以下示例显示了如何配置此类 bean:

<!-- 工厂bean,它包含一个名为createInstance()的方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- 要通过工厂bean创建的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元数据定义中的指定类只是一个初始类引用,可能与声明的工厂方法或FactoryBean类相结合,这可能导致bean的不同运行时类型,或者在实例级工厂方法的情况下,根本没有设置。此外,AOP代理可以用一个基于接口的代理来包装一个bean实例,并有限地暴露目标bean的实际类型(仅仅是实现的接口)。

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

1.4 依赖

典型的企业应用程序不包含单个对象(或 Spring 用语中的 bean)。 即使是最简单的应用程序也有一些对象,它们协同工作以呈现最终用户所看到的连贯应用程序。下一节将解释如何从定义大量独立的bean定义过渡到一个完全实现的应用程序,其中对象通过协作实现目标。

1.4.1 依赖注入

依赖注入(DI)是一个过程,对象通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖项(即它们工作的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程基本上是bean本身通过直接构造类或Service Locator模式控制其依赖项的实例化或位置的逆过程(因此得名“控制反转”)。

DI 原则使代码更清晰,当对象提供依赖关系时,解耦更有效。 该对象不查找其依赖项,也不知道依赖项的位置或类。 因此,您的类变得更容易测试,尤其是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用stub或Mock实现。

依赖注入主要有两种变体:基于构造函数的依赖注入基于setter的依赖注入

基于构造函数的依赖注入

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

public class SimpleMovieLister {

    // SimpleMovieLister依赖于MovieFinder
    private final MovieFinder movieFinder;

    // 一个构造函数,这样Spring容器就可以注入一个MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

注意,这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注释的POJO。

构造函数参数解析

构造函数参数解析匹配是通过使用参数的类型来实现的。如果bean定义的构造函数参数中不存在潜在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当的构造函数的顺序。考虑以下类:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假设ThingTwo和ThingThree类没有继承关系,就不存在潜在的歧义。因此,以下配置可以正常工作,并且不需要在<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可以从构造函数中查找参数名。如果不能或不想使用调试标志编译代码,可以使用@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 {

    // SimpleMovieLister依赖于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用户并不是直接使用这些类(即通过编程方式),而是使用XML bean定义、带注释的组件(即使用@Component、@Controller等带注释的类),或者基于java的@Configuration类中的@Bean方法。然后,这些源在内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。

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

由于可以混合使用基于构造函数和基于setter的DI,因此对于强制依赖项使用构造函数,对于可选依赖项使用setter方法或配置方法是一条很好的经验法则。注意,在setter方法上使用@Required注释可以使属性成为必需的依赖项;然而,使用编程式验证参数的构造函数注入更可取。

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

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

使用对特定类最有意义的DI风格。有时,当您处理没有源代码的第三方类时,您可以自行选择。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

依赖解析过程

容器执行bean依赖关系解析如下:

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

Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。在创建容器时将创建单例作用域且设置为预实例化(默认)的bean。作用域在Bean作用域中定义。否则,仅在请求bean时才创建它。创建一个bean可能会导致创建一个bean图,因为创建并分配了bean的依赖项及其依赖项的依赖项(等等)。请注意,这些依赖项之间的解析不匹配可能会在后期出现——即在受影响bean的第一次创建时出现。

循环依赖

如果主要使用构造函数注入,可能会创建不可解析的循环依赖项场景。

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

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

与典型情况(没有循环依赖项)不同,bean a和bean B之间的循环依赖项迫使一个bean在完全初始化自己之前被注入到另一个bean中(典型的先有鸡还是先有蛋的场景)。

您通常可以相信 Spring 会做正确的事情。 它在容器加载时检测配置问题,例如对不存在的 bean 的引用和循环依赖。 Spring 在真正创建 bean 时尽可能晚地设置属性并解析依赖项。

这意味着,如果创建该对象或其某个依赖项出现问题时,则已正确加载的 Spring 容器稍后可以在您请求对象时生成异常,例如,bean会由于缺少或无效的属性而抛出异常。这可能会延迟一些配置问题的可见性,这就是为什么ApplicationContext实现默认情况下会预先实例化单例bean。

在实际需要这些bean之前创建这些bean需要花费一些前期时间和内存,因此在创建ApplicationContext时发现配置问题,而不是稍后。您仍然可以覆盖这个默认行为,以便单例bean可以延迟初始化,而不是急切地预先实例化。

如果不存在循环依赖项,那么当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入到依赖bean之前都会被完全配置。这意味着,如果bean A依赖于bean B,那么Spring IoC容器在调用bean A的setter方法之前将完全配置bean B。换句话说,bean被实例化(如果它不是一个预先实例化的单例),它的依赖关系被设置,相关的生命周期方法(例如配置的init方法InitializingBean回调方法)被调用。

依赖注入的例子

下面的示例为基于setter的DI使用基于xml的配置元数据。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-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名称空间来进行更简洁的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 IDEA或用于Eclipse的Spring Tools)。强烈推荐这样的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>

通过使用JavaBeans的PropertyEditor机制,Spring容器将<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实际存在。在第二个变体中,对传递给客户机bean的targetName属性的值不执行任何验证。只有在实际实例化客户端bean时才会发现打字错误(很可能导致致命的结果)。如果客户端bean是一个原型bean,那么这个输入错误和由此产生的异常可能要在容器部署很久之后才会被发现。

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

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

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

ref元素是<constructor-arg/>或<property/>定义元素中的最后一个元素。在这里,您将一个bean的指定属性的值设置为对容器管理的另一个bean(合作者)的引用。被引用的bean是要设置其属性的bean的依赖项,在设置属性之前,根据需要对其进行初始化。(如果合作者是一个单例bean,它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。范围和验证取决于您是通过bean还是父属性指定其他对象的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引用变体。以下清单显示了如何使用 parent 属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>

<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

内部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 相关联,但销毁回调让它参与请求范围的生命周期。 这不是一个常见的场景。 内部 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>

map键或值或set值的值也可以是以下元素中的任何一个:

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

集合合并

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

关于合并的这一节讨论父-子bean机制。不熟悉父bean和子bean定义的读者可能希望在继续之前阅读相关部分。

下面的例子演示了集合合并:

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

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

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

子属性集合的值集继承了父属性<props/>的所有属性元素,并且子属性的支持值覆盖了父集合中的值。

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

收集合并的限制

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

强类型集合

通过在Java 5中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一个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 的类型转换基础结构将各种值元素识别为 Float 类型,并将字符串值(9.99、2.75 和 3.99)转换为实际的 Float 类型。

Null和空字符串值

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

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

上述示例相当于以下Java代码:

exampleBean.setEmail("");

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

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

等价于:

exampleBean.setEmail(null);

使用p-名称空间的XML快捷方式

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

Spring 支持具有命名空间的可扩展配置格式,这些格式基于 XML 模式定义。 本章讨论的 bean 配置格式是在 XML Schema 文档中定义的。 但是,p 命名空间并未在 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 定义中名为 email 的 p 命名空间中的一个属性。 这告诉 Spring 包含一个属性声明。 如前所述,p 命名空间没有模式定义,因此您可以将属性的名称设置为属性名。

下一个示例包括另外两个bean定义,它们都有对另一个bean的引用:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        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-namespace的属性值,而且还使用一种特殊的格式来声明属性引用。第一个bean定义使用<property name=”spouse” ref=”jane”/>来创建从bean john到bean jane的引用,第二个bean定义使用p:spouse-ref=”jane”作为属性来完成完全相同的工作。在本例中,spouse是属性名,而-ref部分表示这不是一个直接值,而是对另一个bean的引用。

p-namespace不像标准XML格式那样灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式不会。我们建议您谨慎地选择您的方法,并与您的团队成员进行沟通,以避免生成同时使用所有三种方法的XML文档。

使用c-namespace的XML快捷方式

与带有p-namespace的XML快捷方式类似,Spring 3.1中引入的c-namespace允许内联属性来配置构造函数参数,而不是嵌套的构造函数-参数元素。

下面的例子使用c: namespace做了与基于构造函数的依赖注入相同的事情:

<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语法的原因,索引表示法要求出现前导_,因为XML属性名不能以数字开头(尽管有些ide允许)。对于<constructor-arg>元素也可以使用相应的索引表示法,但不常用,因为声明的简单顺序通常就足够了。

在实践中,构造函数解析机制在匹配参数方面非常有效,所以除非真的需要,否则我们建议在整个配置中使用名称表示法。

复合属性名

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

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

这个bean有一个fred属性,它有一个bob属性,它有一个sammy属性,最后一个sammy属性被设置为123。为了使其工作,在构造bean之后,something的fred属性和fred的bob属性不能为空。否则,抛出NullPointerException。

1.4.3 使用depends-on

如果一个bean是另一个bean的依赖项,这通常意味着一个bean被设置为另一个bean的属性。通常使用基于xml的配置元数据中的<ref/>元素来实现这一点。但是,有时bean之间的依赖关系不是那么直接。例如,当需要触发类中的静态初始化器时,例如数据库驱动程序注册时。depends-on可以显式地在使用此元素的bean初始化之前强制初始化一个或多个bean。下面的示例使用depends-on属性表示对单个对象的依赖关系:

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

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

<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 本身被销毁之前,首先销毁与给定 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 使用前面的配置时,当 ApplicationContext 启动时,lazy bean 不会被预先实例化,而 not.lazy bean 会被预先实例化。

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

你也可以通过在<beans/>元素上使用default-lazy-init属性来控制容器级的惰性初始化,如下面的例子所示:

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

1.4.5 自动装配协作者

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

  • 自动装配可以显著减少指定属性或构造函数参数的需要。
  • 自动装配可以随着对象的发展更新配置。例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,在开发过程中,自动装配尤其有用,当代码库变得更加稳定时,不会否定切换到显式装配的选项。

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

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

1.5 Bean范围

1.6.1 生命周期回调

初始化回调

破坏回调

1.12.3使用@Bean注释


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

IDEA常用快捷键 Previous
阿里巴巴Java开发手册 Next