目录
依赖
依赖注入
DI 存在两个主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数代表一个依赖项。
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...
}
基于 Setter 的依赖注入
基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数static
工厂方法来实例化 bean 后调用 bean 上的 setter 方法来完成的。
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...
}
基于构造函数还是基于 setter 的 DI?
由于您可以混合使用基于构造函数和基于 setter 的 DI,因此将构造函数用于强制依赖项并将 setter 方法或配置方法用于可选依赖项是一个很好的经验法则。请注意,在 setter 方法上使用@Required 注释可用于使属性成为必需的依赖项;然而,带有参数的编程验证的构造函数注入是更可取的。
Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不是null
. 此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量的构造函数参数是一种不好的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决适当的关注点分离问题。
Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。因此,通过JMX MBean进行管理是 setter 注入的一个引人注目的用例。
使用对特定类最有意义的 DI 样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是 DI 的唯一可用形式。
依赖解析过程
容器执行 bean 依赖解析如下:
- 使用
ApplicationContext
描述所有 bean 的配置元数据创建和初始化。配置元数据可以由 XML、Java 代码或注释指定。 - 对于每个 bean,它的依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用它而不是普通的构造函数)。这些依赖项在实际创建 bean 时提供给 bean。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 bean 的引用。
- 作为值的每个属性或构造函数参数都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如
int
,long
,String
,boolean
等。
在创建容器时会创建单例范围并设置为预实例化(默认)的 Bean。但是,在实际创建 bean 之前,不会设置 bean 属性本身。在创建容器时会创建单例范围并设置为预实例化(默认)的 Bean。否则,只有在请求时才会创建 bean。创建 bean 可能会导致创建 bean 图,因为创建和分配 bean 的依赖项及其依赖项的依赖项(等等)。请注意,这些依赖项之间的解析不匹配可能会出现较晚 - 即在第一次创建受影响的 bean 时。
您通常可以相信 Spring 会做正确的事情。它在容器加载时检测配置问题。
依赖注入的例子
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI
<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"/>
依赖关系和配置
值
元素的value
属性将<property/>
属性或构造函数参数指定为人类可读的字符串表示。
<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>
对其他 Bean 的引用
bean
通过标签的属性指定目标bean<ref/>
是最通用的形式,它允许创建对同一容器或父容器中的任何bean的引用,而不管它是否在同一个XML文件中。属性的值 bean
可以与目标bean 的属性相同,id
或者与目标bean 的属性中的值之一相同name
。以下示例显示了如何使用ref
元素:
<ref bean="someBean"/>
集合
以下示例显示了如何使用它们:<map/>``<props/>``Collection``List``Set``Map
<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>
Null 和空字符串值
Spring 将属性等的空参数视为空参数Strings
。以下基于 XML 的配置元数据片段将该email
属性设置为空 String
值 ("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
该<null/>
元素处理null
值。以下清单显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等价于以下 Java 代码:
exampleBean.setEmail(null);
延迟初始化的 Bean
默认情况下,作为初始化过程的一部分,ApplicationContext
实现会急切地创建和配置所有 单例bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即发现,而不是几小时甚至几天之后。当这种行为不可取时,您可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在第一次被请求时创建一个 bean 实例,而不是在启动时。
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
您还可以通过使用元素上的 default-lazy-init
属性在容器级别控制延迟初始化<beans/>
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动装配
Spring 容器可以自动装配协作 bean 之间的关系。
您可以使用元素的autowire
属性为 bean 定义指定自动装配模式。
模式 | 解释 |
---|---|
no |
(默认)不自动装配。Bean 引用必须由ref 元素定义。对于较大的部署,不建议更改默认设置,因为明确指定协作者可以提供更大的控制力和清晰度。在某种程度上,它记录了系统的结构。 |
byName |
按属性名称自动装配。Spring 寻找与需要自动装配的属性同名的 bean。例如,如果一个 bean 定义被设置为按名称自动装配并且它包含一个master 属性(即它有一个 setMaster(..) 方法),那么 Spring 会查找一个命名的 bean 定义master 并使用它来设置该属性。 |
byType |
如果容器中恰好存在一个属性类型的 bean,则让属性自动装配。如果存在多个,则会抛出一个致命异常,这表明您可能不会byType 对该 bean使用自动装配。如果没有匹配的 bean,则不会发生任何事情(未设置属性)。 |
constructor |
类似于byType 但适用于构造函数参数。如果容器中没有一个构造函数参数类型的 bean,则会引发致命错误。 |
方法注入
在大多数应用场景中,容器中的大多数 bean 都是单例的。当一个bean 需要与另一个bean 协作。当 bean 生命周期不同时,就会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,可能在 A 上的每个方法调用上。容器只创建一次单例 bean A,因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供一个新的 bean B 实例。
一个解决方案是放弃一些控制反转。
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面是不可取的,因为业务代码知道并耦合到 Spring 框架。方法注入是 Spring IoC 容器的一项高级功能,可让您干净地处理此用例。
查找方法注入
Spring 框架通过使用 CGLIB 库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。
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();
}
如果方法是abstract
,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为的 bean在需要 bean 的新实例时commandManager
调用它自己的方法。
或者,在基于注解的组件模型中,您可以通过注解声明查找方法@Lookup
,如以下示例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
任意方法替换
使用基于 XML 的配置元数据,您可以使用该replaced-method
元素将现有方法实现替换为另一个,用于已部署的 bean。