Part III. 核心技术-7. IOC容器-7.4 依赖关系
典型的企业应用程序不包含单个对象(或Spring说明中的bean)。 即使是最简单的应用程序,也有几个对象共同合作,展示最终用户看到的一致性应用程序。 下一节将介绍如何从定义一些独立于完全实现的应用程序的bean定义(对象协作实现目标)。 7.4.1 依赖注入依赖注入(DI)是一个过程,对象定义它们的依赖关系,也就是与它们配合使用的其他对象,只有通过构造函数参数,工厂方法的参数或构造或返回对象实例后设置的属性 从工厂方法。 然后,容器在创建bean时注入这些依赖项。 这个过程基本上是逆向的,因此,控制的控制(IoC)名称本身就是通过使用直接构造类或者服务定位器模式来控制自己的依赖关系的实例化或位置。 使用DI原理,代码更清晰,当对象具有依赖关系时,解耦更有效。 对象不查找其依赖关系,并且不知道依赖关系的位置或类。 因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,允许在单元测试中使用存根或模拟实现。 DI存在两种主要的变体:基于构造函数的依赖注入和基于Setter的依赖关系注入。 基于构造函数的依赖注入基于构造函数的DI通过容器调用具有多个参数的构造函数完成,每个参数表示一个依赖关系。 调用一个具有特定参数的静态工厂方法来构造bean几乎是等效的,这个讨论类似地将参数视为一个构造函数和一个静态的工厂方法。 以下示例显示一个只能使用构造函数注入注入的依赖关系的类。 请注意,这个类没有什么特别之处,它是一个POJO,它不依赖于容器特定的接口,基类或注释。 public class SimpleMovieLister {
构造函数解析使用参数的类型进行构造函数参数解析匹配。 如果在bean定义的构造函数参数中不存在潜在的歧义,那么构造函数参数在bean定义中定义的顺序就是在bean被实例化时将这些参数提供给适当的构造函数的顺序。 考虑下列课程: package x.y;
Foo { Foo(Bar bar,Baz baz) { // ... } } 没有潜在的模糊性存在,假设 <beans>
<bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> </beans> 当引用另一个bean时,类型是已知的,并且可以进行匹配(如前面的例子所示)。 当使用简单的类型,例如 package examples;
ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life,the Universe,and Everything private String ultimateAnswer; ExampleBean(int years,String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } } 在上述情况下,如果使用type属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的type。 例如: <bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean> 使用index属性来明确指定构造函数参数的索引。 例如: "examples.ExampleBean">
<constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean> 除了解决多个简单值的歧义之外,指定索引可以解析构造函数具有两个相同类型参数的歧义。 请注意,索引为0。 您还可以使用构造函数参数名称进行值消除歧义: "examples.ExampleBean">
<constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean> 请记住,为了使这个工作开箱即用,您的代码必须在启用调试标志的情况下进行编译,以便Spring可以从构造函数中查找参数名称。 如果您无法使用调试标志(或不想)编译代码,则可以使用 // Fields omitted
({"years","ultimateAnswer"}) this.ultimateAnswer = ultimateAnswer; } 基于Setter的依赖注入 基于Setter的DI通过在调用无参数构造函数或无参数 以下示例显示一个只能使用纯setter注入进行依赖注入的类。 这个类是常规Java。 它是一个POJO,它不依赖容器特定的接口,基类或注释。 public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder // a setter method so that the Spring container can inject a MovieFinder public void setMovieFinder(MovieFinder movieFinder) { // business logic that actually uses the injected MovieFinder is omitted... }
依赖性解决过程容器执行如下的bean依赖解析:
创建容器时,Spring容器会验证每个bean的配置。 但是,bean实际创建之前不会设置bean属性。 单个复制并设置为预实例化(默认)的Bean将在创建容器时创建。 范围在7.5节“Bean范围”中定义。 否则,只有在请求时才创建该bean。 创建bean可能会导致创建bean的图形,因为创建和分配了bean的依赖关系及其依赖关系(等等)。 请注意,这些依赖关系中的解决方案不匹配可能会显示较晚,即首次创建受影响的bean。
你一般可以信任Spring做正确的事情。它在容器加载时检测到配置问题,例如对不存在的bean和循环依赖项的引用。当bean实际创建时,Spring会尽可能早地设置属性并解析依赖关系。这意味着如果在创建该对象或其依赖关系时出现问题,则在请求对象时可以正确加载的Spring容器可以生成异常。例如,bean由于缺少或无效的属性而抛出异常。某些配置问题的这种潜在的延迟可见性是为什么默认情况下, 如果不存在循环依赖关系,当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入依赖bean之前被完全配置。 这意味着如果bean A对bean B有依赖关系,则Spring IoC容器在调用bean A的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是一个预先实例化的单例),它 设置依赖关系,并调用相关的生命周期方法(如配置的init方法或InitializingBean回调方法)。 依赖注入的例子以下示例使用基于设置器的DI的基于XML的配置元数据。 Spring XML配置文件的一小部分指定了一些bean定义: "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 {
private AnotherBean beanOne; private YetAnotherBean beanTwo; int i; setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } setIntegerPropertyint i) { this.i = i; } } 在前面的示例中,setter被声明为与XML文件中指定的属性相匹配。以下示例使用基于构造函数的DI: <!-- 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="examples.YetAnotherBean"/> ExampleBean {
private AnotherBean beanOne; private YetAnotherBean beanTwo; (AnotherBean anotherBean,YetAnotherBean yetAnotherBean,int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } } 在bean定义中指定的构造函数参数将被用作构造函数的参数的例子。 现在考虑一个这个例子的变体,而不是使用一个构造函数,Spring被要求调用一个 "examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="examples.AnotherBean"/> <bean id="examples.YetAnotherBean"/> ExampleBean {
// a private constructor private (...) { ... } // 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. static ExampleBean createInstance ( AnotherBean anotherBean,239)">int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } } 7.4.2 依赖和配置详细 如上一节所述,您可以将bean属性和构造函数参数定义为对其他受管Bean(协作者)的引用,也可以定义为inline。 Spring的基于XML的配置元数据支持其 直接值(基本类型,字符串等) "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="masterkaoli"/> </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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="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="masterkaoli"/> </beans> 前面的XML更简洁; 但是,在运行时而不是设计时发现错字,除非您在创建bean定义时使用支持自动属性完成的IDE(如IntelliJ IDEA或Spring Tool Suite(STS))。 强烈建议您使用此类IDE协助。 您还可以将 "mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties --> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property> </bean> Spring容器通过使用JavaBeans PropertyEditor机制将 idref元素 <bean id="theTargetBean" class="..."/>
<bean "theClientBean" "..."> <property name="targetName"> <idref bean="theTargetBean"/> </property> </bean> 上述bean定义片段与以下代码片段完全相同(在运行时): "..." />
<bean "client" "targetName" value="theTargetBean"/> </bean> 第一种形式优于第二种形式,因为使用
一个常见的地方(至少在Spring 2.0之前的版本中),其中 引用其他bean(协作者) ref元素是 通过 <ref bean="someBean"/>
通过父属性指定目标bean创建对当前容器的父容器中的bean的引用。 父属性的值可能与目标bean的id属性或目标bean的name属性中的值之一相同,目标bean必须位于当前bean的父容器中。 您使用此bean参考变体主要是在具有容器层次结构时,并且想要使用与父bean具有相同名称的代理将现有bean包装在父容器中。 <!-- in the parent context -->
<bean id="accountService" class="com.foo.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 "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的范围。 集合 在 "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的键或值或设置值的值也可以是以下任何元素: bean | ref | idref | list | set | map | props | value | null
集合的合并 Spring容器还支持集合的合并。 应用程序开发人员可以定义父样式的 关于合并的这个部分讨论了父子bean的机制。 不熟悉父和子bean定义的读者可能希望在继续之前阅读相关章节。 以下示例演示集合合并: "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定义的 administrator=administrator@example.com
sales=sales@example.com support=support@example.co.uk 子属性集合的值集继承父项 此合并行为类似于 集合合并的限制您不能合并不同的集合类型(如Map和List),如果您尝试这样做,则会抛出适当的异常。 合并属性必须在较低的,继承的子定义上指定; 在父集合定义上指定合并属性是多余的,不会导致所需的合并。 强类型集合 通过在Java 5中引入泛型,可以使用强类型集合。 也就是说,可以声明一个 Foo {
private Map<String,Float> accounts; setAccounts(Map<String,Float> accounts) { this.accounts = accounts; } } "x.y.Foo">
<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> 当 空和空字符串值 Spring将空参数作为空 <bean class="ExampleBean">
<property name="email" value=""/> </bean> 上述示例相当于以下Java代码: exampleBean.setEmail("")
"email">
<null/> </property> </bean> 上述配置相当于以下Java代码: exampleBean.setEmail(null)
p-namespace Spring支持具有命名空间的可扩展配置格式,它们基于XML模式定义。 本章讨论的 以下示例显示了解决相同结果的两个XML片段:第一个使用标准XML格式,第二个使用p-namespace。 "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="foo@bar.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/> </beans> 该示例显示了在bean定义中称为电子邮件的p命名空间中的属性。 这告诉Spring包含一个属性声明。 如前所述, 下一个示例包括两个对另一个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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean "john-classic" "com.example.Person"> <"name" value="John Doe"/> <"spouse" ref="jane"/> </bean> <bean "john-modern" "com.example.Person" p:"John Doe" p:spouse-"jane"/> <bean "jane" "Jane Doe"/> </bean> </beans> 您可以看到,此示例不仅包括使用p-namespace的属性值,还使用特殊格式来声明属性引用。 而第一个bean定义使用
c-namespace 类似于称为“具有 我们来回顾一下名为“基于构造器的依赖注入”一节中与 "http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="x.y.Bar"/> <bean id="x.y.Baz"/> <!-- traditional declaration --> <bean id="baz"/> <constructor-arg value="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> </beans> 对于构造函数参数名称不可用的罕见情况(通常如果字节码在没有调试信息的情况下编译),可以对参数索引使用回退: <!-- c-namespace index declaration -->
<bean id="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
在实践中,构造函数解析机制在匹配参数方面非常有效,除非真的需要,我们建议您使用名称符号通过配置。 复合属性名称当您设置bean属性时,可以使用复合或嵌套属性名称,只要除了最终属性名称之外的路径的所有组件都不为null。 考虑下面的bean定义。 <bean id="foo" class="foo.Bar">
7.4.3 使用依赖 如果一个bean是另一个的依赖,通常意味着一个bean被设置为另一个bean的属性。 通常,您可以使用基于XML的配置元数据中的 "beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" /> 要表示对多个bean的依赖关系,请使用逗号,空格和分号作为有效分隔符提供一个bean名称列表作为 "manager,accountDao">
<property name="manager" ref="manager" /> </bean> <bean id="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
7.4.4 延迟初始化bean 默认情况下, 在XML中,此行为由元素上的lazy-init属性控制; 例如: "lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/> 当上述配置被 然而,当一个惰性初始化的bean是不被延迟初始化的单例bean的依赖项时, 您还可以通过使用 <beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... --> </beans> 7.4.5 自动装配的合作者 Spring容器可以自动关联协作bean。 您可以通过检查
当使用基于XML的配置元数据10时,您可以使用元素的autowire属性为bean定义指定
使用byType或构造函数自动装配模式,可以对数组和类型集合进行装配。 在这种情况下,提供符合预期类型的容器内的所有自动装配候选者以满足依赖性。 如果预期的键类型为 您可以将自动装配行为与依赖关系检查相结合,这是自动连线完成后执行的。 自动装配的局限性和缺点当项目一贯使用时,自动装配效果最好。 如果一般不使用自动装配,开发人员可能会混淆使用它来连接一个或两个bean定义。 考虑自动装配的局限性和缺点:
容器中的多个bean定义可能与由setter方法指定的类型或构造函数参数匹配以进行自动连线。 对于数组,集合或地图,这不一定是问题。 然而,对于期望单个值的依赖性,这种模糊性不是任意解决的。 如果没有唯一的bean定义可用,则抛出异常。 在后一种情况下,您有几个选项:
从自动装配中排除一个bean 在每个bean的基础上,您可以将bean从自动装配中排除。 在Spring的XML格式中,将
您还可以基于与bean名称的模式匹配来限制自动应答。 toplevel 这些技术对于您不想通过自动连线注入其他bean的bean很有用。 这并不意味着排除的bean本身不能使用自动布线来配置。 相反,bean本身不是自动连线其他bean的候选者。 7.4.6 方法注入在大多数应用场景中,容器中的大多数bean都是单例。 当一个单例bean需要与另一个singleton bean协作时,或者一个非singleton bean需要与另一个非singleton 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; 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); } setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } 上述是不可取的,因为业务代码知道并耦合到Spring框架。 方法注入是Spring IoC容器的一个先进的功能,可以以干净的方式处理这种用例。 可以在此博客条目中阅读更多关于方法注入的动机。
<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> 标识为 或者,在基于注释的组件模型中,您可以通过 abstract CommandManager {
(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } ("myCommand") protected abstract Command (); } 或者,更为惯用的是,您可能依赖于目标bean针对声明的返回类型的 (Object commandState) {
MyCommand command = createCommand(); command.setState(commandState); return command.execute(); } abstract MyCommand (); } 请注意,您通常会使用具体的stub实现声明这种带注释的查找方法,以使它们与Spring的组件扫描规则兼容,其中抽象类默认情况下被忽略。 此限制不适用于明确注册或明确导入的bean类的情况。
任意方法更换方法注入比查找方法注入的一种不太有用的形式是使用另一种方法实现来替代托管bean中的任意方法。 用户可以安全地跳过本节的其余部分,直到实际需要该功能。 使用基于XML的配置元数据,可以使用replacement-method元素将现有的方法实现替换为已部署的bean。 考虑下面的类,使用一个方法computeValue,我们要覆盖: MyValueCalculator {
String computeValue(String input) { // some real code... } // some other methods... } 实现 /**
* meant to be used to override the existing computeValue(String) * implementation in MyValueCalculator */ public class ReplacementComputeValue 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定义如下所示: "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"/> 您可以在 java.lang.String String Str 由于参数的数量通常足以区分每个可能的选择,所以此快捷方式可以节省大量的打字,只需键入与参数类型匹配的最短字符串即可。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |