即使是最简单的应用程序也有一些对象,它们协同工作以呈现最终用户所看到的连贯应用程序。
依赖注入
依赖性注入(DI)是一个过程,通过在对象上设置属性来定义它们之间的依赖关系,然后容器在创建bean时注入这些依赖。
采用DI原则,代码会更干净,当对象被提供其依赖时,解耦会更有效。对象不会查找其依赖,也不知道依赖的位置或类别。因此,类变得更容易测试,特别是当依赖是在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI有两个主要的变体。基于构造器的依赖性注入和基于setter的依赖性注入。
基于构造器的依赖性注入
基于构造方法的 DI 是容器通过调用有参数构造方法来完成的,每个参数代表一个依赖。下面的例子显示了一个只能用构造方法注入的依赖性注入的类。
1 | public class SimpleMovieLister { |
请注意,这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注解的 POJO。
参数解析
参数解析使用参数的类型进行匹配。如果参数中不存在歧义,那么在 bean 定义中定义构造方法参数的顺序就是在实例化 bean 时将这些参数提供给适当的构造方法的顺序。考虑以下类:
1 | package x.y; |
假设 ThingTwo 和 ThingThree 类没有通过继承关联,则不存在潜在的歧义。因此,以下配置工作正常,不需要在<constructor-arg/>元素中显式指定构造方法参数索引或类型。
1 | <beans> |
当另一个类型已知的 bean 被引用时,可以发生匹配(就像前面的例子一样)。当使用简单类型时,例如 <value>true</value>,Spring 无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。考虑以下类:
1 | package examples; |
参数类型匹配
在上述场景中,如果您使用 type 属性显式指定构造方法参数的类型,则容器可以使用简单类型的类型匹配,如下例所示:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
参数索引
可以使用index属性来明确指定构造方法参数的索引,如下例所示。
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
除了解决多个简单值的歧义外,指定一个索引还可以解决构造方法有两个相同类型的参数的歧义。
索引从0开始。
参数名称
可以使用构造方法的参数名称来进行数值消歧,如下例所示。
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
请记住,要使这项工作开箱即用,代码必须在启用调试标志的情况下进行编译,以便 Spring 可以从构造方法中查找参数名称。如果您不能或不想启用调试编译代码,则可以使用 @ConstructorProperties JDK 注解显式命名构造方法参数。示例类必须如下所示:
1 | package examples; |
基于 Setter 的依赖注入
基于 Setter 的 DI 是容器通过调用无参数构造方法或无参静态工厂方法来实例化 bean 后,再调用 setter 方法来完成的。
以下示例显示了一个只能使用纯 setter 注入进行依赖注入的类。
1 | public class SimpleMovieLister { |
ApplicationContext 为支持基于构造方法和基于 setter 的 DI。在已经通过构造方法方法注入了一些依赖项之后,它还支持基于 setter 的 DI。
根据经验,对强制依赖项使用构造方法,对可选依赖项使用 setter 方法或配置方法。请注意,在 setter 方法上使用 @Required 注释可用于使属性成为必需的依赖项;但最好使用带有参数编程验证的构造方法注入。
Spring 团队通常提倡构造方法注入,因为它可以让应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造方法注入的组件总是以完全初始化的状态返回给客户端(调用)代码。
大量的构造方法参数是一种糟糕的代码味道,这意味着该类可能有太多的责任,应该重构以更好地解决适当的关注点分离问题。
Setter 注入应该主要仅用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。
依赖解析的过程
容器执行bean依赖解析如下:
ApplicationContext是使用描述所有 bean 的配置元数据创建和初始化的。配置元数据可以由XML、Java 代码或注解指定。- 对于每个 bean,它的依赖关系以属性、构造方法参数或静态工厂方法的参数的形式表示。在实际创建 bean 时,将这些依赖关系提供给 bean。
- 每个属性或构造方法参数都要设置值的实际定义,或者是对容器中另一个 bean 的引用。
- 作为值的每个属性或构造方法参数都从其指定格式转换为该属性或构造方法参数的实际类型。默认情况下,Spring 可以将字符串格式提供的值转换为所有内置类型,例如 int、long、String、boolean 等。
Spring 容器在创建容器时验证每个 bean 的配置。但在实际创建 bean 之前不会设置 bean 属性本身。创建容器时会创建单例范围并设置为预实例化(默认)的 Bean。范围在 Bean 范围中定义;否则,仅在请求时才创建 bean。
循环依赖
如果主要使用构造方法注入,可能会创建无法解决的循环依赖场景。
例如:A类通过构造方法注入需要B类的实例,B类通过构造方法注入需要A类的实例。如果您将类 A 和 B 的 bean 配置为相互注入,则 Spring IoC 容器在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException。
一种解决方案是避免构造方法注入并仅使用 setter 注入。也就是说(虽然不推荐),可以通过setter注入来配置循环依赖。
与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖迫使其中一个 bean 在完全初始化之前被注入另一个 bean(经典的鸡和蛋场景)。
如果不存在循环依赖,当一个或多个协作 bean 被注入依赖 bean 时,每个协作 bean 在注入依赖 bean 之前都已完全配置。这意味着,如果 bean A 依赖 bean B,则 Spring IoC 容器在调用 bean A 上的 setter 方法之前完全配置 bean B。换句话说,bean 被实例化(如果它不是预实例化的单例),相关依赖已设置完毕,相关生命周期方法(如配置的init方法或InitializingBean回调方法)已被调用。
依赖注入的例子
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。 Spring XML 配置文件的一小部分指定了一些 bean 定义,如下所示:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
下面的例子显示了相应的ExampleBean类。
1 | public class ExampleBean { |
在前面的示例中,setter 被声明为与 XML 文件中指定的属性匹配。
以下示例使用基于构造方法的 DI:
1 | <bean id="exampleBean" class="examples.ExampleBean"> |
以下示例显示了相应的 ExampleBean 类:
1 | public class ExampleBean { |
bean 定义中指定的构造方法参数用作 ExampleBean 的构造方法的参数。
现在考虑这个例子的一个变体,其中不使用构造方法,而是告诉 Spring 调用静态工厂方法来返回对象的实例:
1 | <bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> |
以下示例显示了相应的 ExampleBean 类:
1 | public class ExampleBean { |
静态工厂方法的参数由 <constructor-arg/> 元素提供,与实际使用构造方法完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本示例中是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用 factory-bean 属性而不是 class 属性)。
依赖和配置详解
如上一节所述,将 bean 属性和构造方法参数定义为对其他托管 bean(协作者)的引用或作为内联定义的值。为此,Spring 的基于 XML 的配置元数据支持其 <property/> 和 <constructor-arg/> 元素中的子元素类型。
直接值(Primitives、字符串等)
<property/> 标签的 value 属性将属性或构造方法参数指定为人类可读的字符串表示形式。 Spring 的转换服务用于将这些值从 String 转换为属性或参数的实际类型。以下示例显示了正在设置的各种值:
1 | <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> |
以下示例使用 p-namespace 进行更简洁的 XML 配置:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
前面的 XML 更简洁,但拼写错误是在运行时而不是设计时发现的,除非在创建 bean 定义时使用支持自动属性完成的 IDE(例如 IntelliJ IDEA 或 Spring Tools for Eclipse)。强烈建议使用此类 IDE 帮助。
还可以配置一个 java.util.Properties 实例,如下所示:
1 | <bean id="mappings" |
Spring 容器使用 JavaBeans PropertyEditor 机制将 <value/> 元素内的文本转换为 java.util.Properties 实例。这是一个很好的捷径,并且是 Spring 团队支持使用嵌套 <value/> 元素而不是 value 属性样式的少数几个地方之一。
idref 元素
idref 元素只是一种将容器中另一个 bean 的 id(字符串值 - 而不是引用)传递给<constructor-arg/>或 <property/> 元素的防错方式。以下示例显示了如何使用它:
1 | <bean id="theTargetBean" class="..."/> |
前面的 bean 定义片段与以下片段完全等效(在运行时):
1 | <bean id="theTargetBean" class="..." /> |
第一种形式比第二种形式更可取,因为使用 idref 标记让容器在部署时验证引用的命名 bean 实际存在。在第二个变体中,不对传递给客户端 bean 的 targetName 属性的值执行验证。只有在实际实例化客户端 bean 时才会发现拼写错误(最有可能是致命的结果)。如果客户端 bean 是原型 bean,则可能只有在部署容器很久之后才能发现此错误和由此产生的异常。
4.0 beans XSD 不再支持 idref 元素上的 local 属性。升级到 4.0 模式时,将现有的
idref local引用更改为idref bean。
<idref/> 元素带来价值的一个常见地方(至少在 Spring 2.0 之前的版本中)是在 ProxyFactoryBean bean 定义中的 AOP 拦截器的配置中。在指定拦截器名称时使用 <idref/> 元素可防止您拼错拦截器 ID。
对其他 Bean 的引用(合作者)
ref 元素是 <constructor-arg/> 或 <property/> 元素定义中的最后一个元素。
通过<ref/>标记的 bean 属性指定目标 bean 是最通用的形式,它允许创建对同一容器或父容器中的任何 bean 的引用,无论它是否在同一 XML 文件中。 bean 属性的值可能与目标bean 的id 属性相同,也可能与目标bean 的name 属性中的值之一相同。以下示例显示了如何使用 ref 元素:
1 | <ref bean="someBean"/> |
通过 parent 属性指定目标 bean 会创建对当前容器的父容器中的 bean 的引用。 parent 属性的值可能与目标 bean 的 id 属性或目标 bean 的 name 属性中的值之一相同。目标 bean 必须在当前容器的父容器中。您应该主要在具有容器层次结构并且希望使用与父 bean 同名的代理将现有 bean 包装在父容器中时使用此 bean 引用变体。以下清单显示了如何使用 parent 属性:
1 | <!-- in the parent context --> |
1 | <!-- in the child (descendant) context --> |
4.0 beans XSD 不再支持 ref 元素上的 local 属性,因为它不再提供常规 bean 引用的值。升级到 4.0 模式时,将现有的 ref 本地引用更改为 ref bean。
内部bean
1 | <bean id="outer" class="..."> |
内部 bean 定义不需要定义的 ID 或名称。如果指定,容器不会使用这样的值作为标识符。容器在创建时也会忽略范围标志,因为内部 bean 始终是匿名的,并且始终与外部 bean 一起创建。不可能独立访问内部 bean 或将它们注入除封闭 bean 之外的协作 bean 中。
作为一种极端情况,可以从自定义范围 接收销毁回调— 例如,对于包含在单例 bean 中的请求范围内的 bean。内部 bean 实例的创建与其包含的 bean 相关联,但销毁回调让它参与请求范围的生命周期。这不是一个常见的场景。内部 bean 通常只是共享它们包含的 bean 的作用域。
集合
<list/>、<set/>、<map/> 和 <props/> 元素分别设置 Java 集合类型 List、Set、Map 和 Properties 的属性和参数。以下示例显示了如何使用它们:
1 | <bean id="moreComplexObject" class="example.ComplexObject"> |
映射键或值或set的值也可以是以下任何元素:
1 | bean | ref | idref | list | set | map | props | value | null |
集合的合并
Spring 容器还支持合并集合。开发人员可以定义父 <list/>、<map/>、<set/> 或 <props/> 元素并拥有子 <list/>、<map/>、<set/> 或 <props/> 元素从父集合继承和覆盖值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合元素覆盖父集合中指定的值。
关于合并的这一节讨论了父子 bean 机制。不熟悉父和子 bean 定义的读者可能希望在继续之前阅读相关部分。
以下示例演示了集合合并:
1 | <beans> |
请注意在子 bean 定义的 adminEmails 属性的 <props/> 元素上使用了 merge=true 属性。当容器解析并实例化子 bean 时,生成的实例具有一个 adminEmails Properties 集合,该集合包含将子 bean 的 adminEmails 集合与父级的 adminEmails 集合合并的结果。以下清单显示了结果:
1 | administrator=administrator@example.com |
子 Properties 集合的值集继承了父
这种合并行为同样适用于 <list/>、<map/> 和 <set/> 集合类型。在 <list/> 元素的特定情况下,与 List 集合类型(即值的有序集合的概念)相关联的语义得到维护。父列表的值先于所有子列表的值。对于 Map、Set 和 Properties 集合类型,不存在排序。因此,对于作为容器内部使用的关联 Map、Set 和 Properties 实现类型基础的集合类型,没有任何排序语义有效。
集合合并的限制
不能合并不同的集合类型(例如 Map 和 List)。如果您确实尝试这样做,则会抛出适当的异常。
必须在较低的继承子定义上指定合并属性。在父集合定义上指定合并属性是多余的,不会导致所需的合并。
强类型集合
随着 Java 5 中泛型的引入,可以使用强类型集合。也就是说,可以声明一个 Collection 类型,使其只能包含(例如)String 元素。如果你使用Spring将强类型的Collection依赖性注入到Bean中,你可以利用Spring的类型转换支持,这样你的强类型Collection实例的元素在被添加到Collection中之前就被转换为适当的类型。以下 Java 类和 bean 定义显示了如何执行此操作:
1 | public class SomeClass { |
1 | <beans> |
当something bean 的accounts 属性准备注入时,关于强类型Map<String, Float> 元素类型的泛型信息可通过反射获得。因此,Spring 的类型转换基础结构将各种值元素识别为 Float 类型,并将字符串值(9.99、2.75 和 3.99)转换为实际的 Float 类型。
Null 和空字符串值
Spring 将属性等的空参数视为空字符串。以下基于 XML 的配置元数据片段将电子邮件属性设置为空字符串值 (“”)。
1 | <bean class="ExampleBean"> |
前面的示例等效于以下 Java 代码:
1 | exampleBean.setEmail(""); |
1 | <bean class="ExampleBean"> |
上面的配置相当于下面的Java代码:
1 | exampleBean.setEmail(null); |
p 命名空间
p-namespace 允许使用 bean 标签的属性(而不是嵌套的 <property/> 标签)来描述协作 bean 的属性值,或两者兼而有之。
p 命名空间并未在 XSD 文件中定义,仅存在于 Spring 的核心中。
以下示例显示了两个解析为相同结果的 XML 片段(第一个使用标准 XML 格式,第二个使用 p 命名空间):
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
下一个示例包括另外两个 bean 定义,它们都引用了另一个 bean:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
此示例不仅包括使用 p 命名空间的属性值,而且还使用特殊格式来声明属性引用。第一个 bean 定义使用 <property name="spouse" ref="jane"/> 创建从 bean john 到 bean jane 的引用,而第二个 bean 定义使用 p:spouse-ref="jane" 作为属性来做完全一样的东西。在这种情况下,配偶是属性名称,而 -ref 部分表示这不是一个直接值,而是对另一个 bean 的引用。
p 命名空间不如标准 XML 格式灵活。例如,声明属性引用的格式与以 Ref 结尾的属性冲突,而标准 XML 格式则不然。我们建议您谨慎选择您的方法并将其传达给您的团队成员,以避免同时使用所有三种方法生成 XML 文档。
c 命名空间
Spring 3.1 中引入的 c-namespace 允许内联属性来配置构造方法参数,而不是嵌套的 constructor-arg 元素。
下面的例子使用 c: 命名空间来做与 from Constructor-based Dependency Injection 相同的事情:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
c: 命名空间使用与 p: one(用于 bean 引用的尾随 -ref)相同的约定,用于通过名称设置构造方法参数。同样,它需要在 XML 文件中声明,即使它没有在 XSD 模式中定义(它存在于 Spring 核心中)。
对于构造方法参数名称不可用的极少数情况(通常如果字节码是在没有调试信息的情况下编译的),您可以使用参数索引的回退,如下所示:
1 | <!-- c-namespace index declaration --> |
由于 XML 语法的原因,索引符号需要存在前导 _,因为 XML 属性名称不能以数字开头(即使某些 IDE 允许)。相应的索引符号也可用于<constructor-arg>元素,但不常用,因为声明的简单顺序通常就足够了。
实际上,构造方法解析机制在匹配参数方面非常有效,因此除非您确实需要,否则我们建议在整个配置中使用名称表示法。
复合属性名称
您可以在设置 bean 属性时使用复合或嵌套属性名称,只要路径中除最终属性名称之外的所有组件都不为空即可。考虑以下 bean 定义:
1 | <bean id="something" class="things.ThingOne"> |
something bean 有一个 fred 属性,它有一个 bob 属性,它有一个 sammy 属性,并且最终的 sammy 属性被设置为 123。为了使其工作,something 的 fred 属性和 bob 属性构造bean 后,fred 不能为空。否则,抛出 NullPointerException。
使用 depen-on
depends-on 参数可以显式地强制初始化一个或多个 bean。
1 | <bean id="beanOne" class="ExampleBean" depends-on="manager"/> |
要表达对多个 bean 的依赖,需要提供 bean 名称列表作为参数的值(逗号、空格和分号):
1 | <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> |
延迟初始化的 Bean
默认情况下,ApplicationContext 实现会在初始化过程中创建和配置所有单例 bean。一般来说,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后。当这种行为不可取时,你可以通过将Bean定义标记为lazy-init来防止单子Bean的预实例化。
lazy-initialized bean告诉IoC容器在第一次被请求时创建一个bean实例,而不是在启动时。
在 XML 中,此行为由 <bean/> 元素上的 lazy-init 属性控制,如以下示例所示:
1 | <bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> |
当 ApplicationContext 启动时,lazy-initialized bean不会被预先实例化,而 not.lazy bean 会被预先实例化。
但是,当 lazy-initialized bean 是未延迟初始化的单例 bean 的依赖项时,ApplicationContext 在启动时创建延迟初始化的 bean,因为它必须满足单例的依赖项。
可以通过使用 <beans/> 元素上的 default-lazy-init 属性在容器级别控制延迟初始化,如以下示例所示:
1 | <beans default-lazy-init="true"> |
自动装配
Spring 容器可以自动装配协作 bean 。通过检查 ApplicationContext 的内容,可以让 Spring 自动为 bean 解析协作者(其他 bean)。自动装配具有以下优点:
- 自动装配可以显着减少指定属性或构造方法参数的需要。
- 自动装配可以随着对象的发展更新配置。例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,当代码库变得更稳定时,不会否定切换到显式装配的选项。
可以使用 <bean/> 元素的 autowire 属性为 bean 定义指定自动装配模式。自动装配功能有四种模式,可以为每个 bean 指定自动装配。下表描述了四种自动装配模式:
| Mode | Explanation |
|---|---|
no |
(Default) No autowiring. Bean references must be defined by ref elements. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system. |
byName |
Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master and uses it to set the property. |
byType |
Lets a property be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens (the property is not set). |
constructor |
Analogous to byType but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised. |
自动装配的局限性和缺点
自动装配在整个项目中一致使用时效果最佳。如果通常不使用自动装配,开发人员使用它来连接一两个 bean 定义,这可能会让人感到困惑。
考虑自动装配的局限性和缺点:
- 属性和构造方法参数设置中的显式依赖项始终覆盖自动装配。不能自动装配简单的属性,例如基元、字符串和类(以及此类简单属性的数组)。此限制是有意设计的。
- 自动装配不如显式装配精确。
- 可能无法从 Spring 容器生成文档的工具中使用装配信息。
- 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造方法参数指定的类型相匹配。如果没有唯一的 bean 定义可用,则抛出异常。
在最后一种情况下,有多种选择:
- 放弃自动装配以支持显式装配。
- 通过将 bean 定义的
autowire-candidate属性设置为false来避免自动装配 bean 定义。 - 通过将其
<bean/>元素的主要属性设置为true,将单个 bean 定义指定为主要候选者。 - 使用基于注解的配置实现更细粒度的控制。
从自动装配中排除 Bean
在每个 bean 的基础上,可以从自动装配中排除一个 bean。在 Spring 的 XML 格式中,将 <bean/> 元素的 autowire-candidate 属性设置为 false。容器使该特定 bean 定义对自动装配不可用(包括注解配置,例如 @Autowired)。
autowire-candidate 属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的 bean 未标记为自动装配候选者,也会解析。因此,如果名称匹配,按名称自动装配仍然会注入一个 bean。
还可以根据对 bean 名称的模式匹配来限制自动装配候选者。顶级 <beans/> 元素在其 default-autowire-candidates 属性中接受一个或多个模式。例如,要将自动装配候选状态限制为名称以 Repository 结尾的任何 bean,请提供值 *Repository。要提供多个模式,请在逗号分隔的列表中定义它们。 bean 定义的 autowire-candidate 属性的显式 true 或 false 值始终优先。对于此类 bean,模式匹配规则不适用。
方法注入
在大多数应用场景中,容器中的大多数bean都是单例的。当单例 bean 需要与另一个单例 bean 协作或非单例 bean 需要与另一个非单例 bean 协作时,一般通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。当 bean 生命周期不同时就会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,可能在 A 上的每次方法调用上。容器只创建单例 bean A 一次,因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供 bean B 的新实例。
一个解决方案是放弃一些控制反转。可以通过实现 ApplicationContextAware 接口使 bean A 察觉容器,并在每次 bean A 需要时通过对容器进行 getBean(“B”) 调用来请求(通常是新的)bean B 实例 。以下示例显示了这种方法:
1 | // a class that uses a stateful Command-style class to perform some processing |
前面的情况是不可取的,因为业务代码知道并耦合到Spring框架。方法注入(Method Injection)是Spring IoC容器的一个高级功能,可以让你干净地处理这种用例。
查询方法注入
查询方法注入是指容器能够覆盖 bean 上的方法并返回容器中另一个 bean 的查询结果。这种查找通常涉及到一个原型Bean,就像上一节中描述的情景。Spring框架通过使用CGLIB库的字节码生成来实现这种方法注入,动态地生成一个覆盖该方法的子类。
要使这种动态子类化工作,Spring bean 容器子类化的类不能是 final,要覆盖的方法也不能是 final。
对具有抽象方法的类进行单元测试需要对类进行子类化并提供抽象方法的存根实现。
组件扫描也需要具体的方法,这需要具体的类来获取。
另一个关键限制是查询方法不适用于工厂方法,尤其不适用于配置类中的 @Bean 方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的动态子类。
对于前面代码片段中的 CommandManager 类,Spring 容器动态覆盖了 createCommand() 方法的实现。 CommandManager 类没有任何 Spring 依赖项,如重新设计的示例所示:
1 | package fiona.apple; |
在包含要注入的方法(在本例中为 CommandManager)的客户端类中,要注入的方法需要以下形式的签名:
1 | <public|protected> [abstract] <return-type> theMethodName(no-arguments); |
如果该方法是抽象的,则动态生成的子类将实现该方法。否则,动态生成的子类会覆盖原始类中定义的具体方法。考虑以下示例:
1 | <!-- a stateful bean deployed as a prototype (non-singleton) --> |
标识为 commandManager 的 bean 在需要 myCommand bean 的新实例时调用它自己的 createCommand() 方法。如果实际上需要,您必须小心地将 myCommand bean 部署为原型。如果是单例,则每次都返回相同的 myCommand bean 实例。
或者,在基于注解的组件模型中,您可以通过@Lookup 注解声明一个查找方法,如下例所示:
1 | public abstract class CommandManager { |
或者,更惯用的是,您可以依靠目标 bean 根据查找方法的声明返回类型进行解析:
1 | public abstract class CommandManager { |
请注意,您通常应该使用具体的存根实现声明此类带注释的查找方法,以便它们与 Spring 的组件扫描规则兼容,默认情况下抽象类将被忽略。此限制不适用于显式注册或显式导入的 bean 类。
任意方法替换
与查询方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管 bean 中的任意方法。
使用基于 XML 的配置元数据,您可以使用replaced-method 元素将现有的方法实现替换为另一个,用于部署的bean。考虑下面的类,它有一个我们想要覆盖的名为 computeValue 的方法:
1 | public class MyValueCalculator { |
实现 org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如以下示例所示:
1 | /** |
用于部署原始类并指定方法覆盖的 bean 定义类似于以下示例:
1 | <bean id="myValueCalculator" class="x.y.z.MyValueCalculator"> |
您可以在
1 | java.lang.String |
由于参数的数量通常足以区分每个可能的选择,因此该快捷方式可以让您只键入与参数类型匹配的最短字符串,从而可以节省大量输入。