Spring IOC之依赖
一个标准的企业级应用不只有一个对象组成。即使是最简单的引用也会有个相互作用的对象以使最终呈现
在用户面前的是个连贯一致的引用。
1依赖注入
依赖注入(DI)是一个对象定义他们依赖的过程,也就是说他们一起处理的对象,只是通过构造器参数、
工厂方法参数或者是通过工厂饭饭返回或者是通过构造的设置在实例上的属性。当容器创建这些bean的
时候会注入他们的依赖关系。这个过程实现了饭庄,因此被有了控制反转的名字(IOC),也就是通过构造器的器或者是
Service Locator模式,bean自己可以控制实例化和依赖的位置。
遵循DI原则的代码更加整洁,当对象提供了他们的依赖的时候会更加的有效。这些对象不会寻找他们的依赖
而且也不知道依赖类的位置。正式这样,你的类更加容易去测试,特别是当依赖是接口或者是抽象类的时候,
它将允许在在单元测试中使用。
DI主要有两个形式,基于构造函数的依赖注入和基于setter方法的依赖注入。
1.1基于构造函数的依赖注入
基于构造函数的DI的完成时通过容器调用含有参数的的构造函数。调用一个有明确参数static工厂方法去构造bean是完全一样的,这个方式处理
传递给构造器和静态工厂方法是相似的。下面的例子将会展示一个类智能通过构造器注入。注意这个类没有其他特别地方,
这是一个POJO,它没有依赖容器规范接口,基础类或者是注解。
public classSimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
privateMovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
publicSimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
1.1.1构造器参数解决方案
构造器参数解决方案通过匹配参数来使用的。在一个Bean被实例化的时候,如果在一个Bean定义的构造器参数中没有潜在的模糊存在的话,那么在Bean定义的构造器参数的顺序就是就就是会被应用到合适的构造器参数的顺序。看下下面的类
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
没有潜在的模糊存在,加入Bar 和Barz类没有继承关系。所以下面的配置文件时没有问题的,而且你不需要去通过
<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;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private 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>
需要注意的是让这个功能起作用的话你需要在debug模式开启下编译你的代码以便Spring可以从构造器中寻找参数。如果你想使用debug来编译你的嗲吗,你可以使用JDK的注解 @ConstructorProperties来显示的命名你的构造器参数。下面有一个样例:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
1.2基于Setter方法的依赖注入
基于Setter方法的依赖注入是通过容器调用一个无参的构造函数或者无参的静态工厂方法去实例化你的bean之后调用在你的bean中setter方法来实现的:
下面的例子展示了只是通过单纯的setter方法注入来实现DI的。这个类是常规的类。它是一个没有依赖容器具体接口基础类或者注解的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支持基于构造函数和基于setter方法的DI。它也支持在一些依赖已经通过构造器途径来注入后的setter方法注入。你注册这些依赖以BeanDefinition的形式,你会使用它来和PropertyEditor来关联起来将属性从一个格式转为另外一个格式。但是,大部分的是Spring用户不直接和这些类打交道,而是通过XML格式的bean定义、注解组件(@Component、@Controller)或者在基于Java的@Configuration类中的@Bean方法。这些最后在内部会转为为BeanDefinition实例,用于去加载一个整的Spring IOC容器实例。
1.3依赖方案过程
容器处理bean依赖通过下面途径:
- ApplicationContext创建后然后使用描述所有bean的配置数据来进行初始化。配置数据是通过XML、java代码或者注解的方式的指明的。
- 对于每一个bean,如果你使用IOC容器来实例化而不是通过普通的构造函数,它的依赖是通过属性、构造器参数、静态工厂方法的参数的形式来表现的。当bean被创建的时候,这些依赖就会提供给bean。
- 在容器中,每一个属性或者构造器参数就是要赋值给实际的定义或者其他bean的引用。
- 作为值的每一个属性或者构造器参数从显示的格式转换成实际的属性或者构造器参数类型。默认情况下,Spring能够将一个String格式的值转成所有的内置类型,比如int.long.String.boolean等等。
Spring容器在每个bean被创建的是时候会校验他们的配置。但是,只有当bean被创建的时候bean自己的属性才会被设置。在容器创建的时候,单例的bean会被提前实例化创建。否则的话,只有在被请求的时候才会被创建。一个bean的创建会潜在的引起多个bean的创建,因为bean的依赖和他们依赖的依赖会被创建和分配。
1.3.1循环依赖
如果你主要使用构造器注入,这样可能产生一个无法解决的循环依赖情况。
例如:A类需要通过构造器注入得到类B的实例,B类需要通过构造器注入得到类A的实例。如果你配置类A和类B注入给彼此,Spring 的IOC 容器在运行时会发现这个循环引用,就会抛出BeanCurrentlyInCreationException异常。
一个可能的解决方法就是编辑类的源码使用setter方法注入而不是通过构造器。两者选其一,只用setter注入而不是使用构造器注入。换句话说,金库这不是被建议,但是你应该使用setter方法配置循环依赖。
不像一个标准的例子,在Bean A 和Bean B之间的一个循环依赖强迫其中一个bean在自己卑微完全实例化之前被注入。
你会想回Spring在做正确的事情。它发现配置问题,例如在容器加载的时候引用不存在的bean和循环嵌套。当bean实际被创建的时候,Spring设置属性和解析依赖会尽可能的晚。这意味着如果创建的对象或者它的依赖有问题时,一个已经正确加载的Spring容器在你请求一个对象的时候会产生异常。例如,bean因为缺失或者不合法的属性时会抛出异常。这种潜在的延迟发现
一些配置问题是为什么ApplicationContext的实现在缺省的情况下回提前实例化单例bean的原因。在他们真正需要之前或消耗写前置事假内核内存作为代价,你可以发现一些配置问题在ApplicationContext的时候而不是后来。你仍然可以重写这个缺省的行为一百年单实例bean可是懒加载而不是提前实例化。
如果没有循环依赖存在的话,当一个或者多个协作的bean被注入到一个依赖的bean的时候,每一个协作的bean会被配置在注入到依赖的bean之前。这意味着如果bean A 有一个依赖bean,Spring Ioc容器在调用bean A的setter方法之前就会完成B的配置。换句话说,Bean被实例化,它的依赖被设置,而且相关的方法(如何一个配置初始化方法或者是懒加载bean的回调方法)被调用。
1.3.2依赖注入的例子
下面的例子使用基于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"/>
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"/>
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"/>
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;
}
}
传递给静态工厂方法的参数是通过
2.详细的依赖和配置
正如上面部分提到的,你可以定义bean属性和构造器参数引用给其他管理的bean,或者在内部被定义的值。SPring的基于XML配置的元数据支持子元素类型在
2.1直接值(原始类型、字符串等等)
元素
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroymethod="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>
下面的例子是通过p-namespace 来作为更加简明的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
http://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="masterkaoli"/>
</beans>
上面的XML更加简明了;但是类型是在运行时发现的而不是在设计的时候,出发你室友像Intellij IDEA或者是 SpringSource Tool Suit 这样在你创建bean定义的时候支持属性补全功能的IDE。这样的IDE帮助是很提倡的。
你可以配置一个java.util.Properities实例像下面:
<bean id="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容器通过使用JavaBeanPropertyEditor机制将
2.2 idref元素
idref元素是一个简单误差检验校验方式将在容器中的其他bean的id传给
<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是否实际上存在。在第二个变化版本中,没有校验传递给client Bean 的targetName属性。类型只有在client bean被正在实例化的时候才会被发现。如果client bean 是一个原型bean,这种形式和结果异常可能会在容器部署后才会发现。
注意:在idref中的 local 属性不在4.0的beans xsd中支持了因为它不在提供bean引用的值了。当升级到4.0 schema的时候 修改 idref local 到idref bean.
在
2.3其他bean的引用
ref元素是
<ref bean="someBean"/>
通过parent属性指明目标bean创建了一个在当前容器的父容器的bean的引用。parent属性的值可以说既可以目标bean的id属性,目标bean的name属性的一个值,而且目标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>
2.4内部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或者name;容器会忽略这些值的,它也会忽略scope标记,内部bean通常是异步的而且他们在外部bean创建的时候创建的。它不可能注入协作的bean而不是封闭的bean中。
2.5.1集合
在,
<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的key的值,value,集合set的值可以分配下面元素的类型:
bean | ref | idref | list | set | map | props | value | null
2.5.2集合的合并
Spring容器也支持集合的合并。一个应用的开发中能够定义一个父风格的,
下面的例子演示了集合合并:
<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>
注意在child bean定义中的adminEmails属性的
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子Properties集合值的继承了所有的属性元素从父
这个合并也适用于,
例子中,语义是和List集合有关的,也就是说,ordered的集合类型的概念是可保持的;父的值在所有子list值的前面。在Map、Set和Properties集合类型中,没有顺序存在。因此没有排序的集合类型的语义实际上构成相关的Map,Set,和Properties实现类型容器内部使用。
2.5.3集合合并的局限性
你不能合并不同类型的集合,而且如果你这样做的话就会抛出异常。merge属性必须要在child定义中指明;声明在父集合定义的merge属性是多余的也不会得到想要的合并结果。
2.5.4强类型的集合
在Java5中的泛型类型介绍中,你可以使用强类型的集合,也即是说,可以去声明集合类型比如只包含String类型的元素。如果使用Spring 去DI一个强类型的集合到一个bean中,你可以李世勇Spring的类型转换支持,你强类型的集合实例能够转换成合适的类型在被加入到集合之前。
public classFoo {
privateMap<String, Float> accounts;
public voidsetAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="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>
当foo bean的accounts属性准备好注入的时候,关于强类型 Map<String, Float>的元素的泛型信息时能够通过反射访问的。因为Spring的类型转换机制识别出了不同的值作为Float类型,而且String的值9.99, 2.75, 和 3.99被转换成实际的Float类型。
2.5.5NULL 和 empty的字符串的值
Spring对待属性参数的empty值,就像对象空的字符串一样。下面基于XML的配置信息片段设置Email的值是空的字符串"".
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的例子和下面的Java代码是等价的:
exampleBean.setEmail("")
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面的代码和下面的Java代码等价:
exampleBean.setEmail(null)
2.6 p-namespace的XML简写
p-namespace可以让你使用bean元素的属性,而不是嵌套的
Spring支持使用基于XML模式定义的命名空间的扩展的配置。在这章节里讨论的bean配置格式都是定义在XML模式文档中的。但是 p-namespace并不是以一个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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
这个例子表示了在bean中定义的叫做Email的p-namespace的属性。这告诉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
http://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使用
2.7 c-namespace的XML简写
和称为"p-namespace的XML简写"部分类似, c-namespace在Spring3.1中被新引进来的,它允许使用内置的属性在配置构造器参数的时候而不是使用嵌套的
让我们看个基于构造器注入的例子,还用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
http: //www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
c: namespace和 p: one 一样使用相同的惯例在通过他们的名字来设置构造器参数的是偶。但是尽管他需要在一个XSD模式中声明但是还需要声明的(它存在Spring核心的内部)。
对于极少的情况下构造器参数名字无法使用的时候(一般情况下是字节码变异的时候没有debug信息),我们可以使用参数索引。
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
在实际中,构造器解决机制在真正需要的时候在参数匹配上能够非常有效的,我们建议使用在你配偶之中使用名字的方式。
2.8 复合的属性名
你可以使用复合或者嵌套的属性名在你设置bean属性的时候,只要素有的路径的组件期望的最总属性名字不是null的。看下面的bean定义。
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
foo bean 有一个fred属性,而fred有一个bob属性,bob属性有一个sammy属性,而且最总的sammy属性值被设置为123.为了让这个起作用,fred的属性bob,bob的属性fred一定不能为null,在bean被构建以后,或者NullPointerException被抛出。
3.使用依赖
如果一个bean是其他bean的依赖,这一般意味着一个bean被设置为其他的属性。习惯上讲,你完成这个用在基于XML配置的元素。但是,有时候bean之间的依赖不是那么直接,例如,在一个类中的静态初始化器需要被触发,比如数据库驱动注册的时候。depends-on属性能够明确强迫一个多个多个bean在使用这个元素的bean被初始化的时候被初始化。下面的例子使用depends-on属性去表示在单例中的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
为了表达在多个bean中的依赖,在 dependson属性中使用用逗号空格冒号连接的列表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" />
4.懒加载bean
在默认情况下,ApplicationContext的实现偏好于创建和配置所有的bean作为初始化过程的一部分。一般来讲,这个提前实例化是需要的,因为在配置或者周遭环境中的错误能够被立即发现,而不是几小时或者几天之后。当这个行为不被需要的时候,你可以通过是bean的定义是懒加载的的方式来阻止单例bean的提前实例化。一个懒加载的bean告诉Ioc容器在它第一次被需要的时候进行实例化而不是在启动的时候。
在XML中,这种行为是通过
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当上面的配置被ApplicationContext加载的时候,名为lazy 的bean 在ApplicationContext启动的时候不会被实例化,而not.lazy的bean则会被提前实例化。
但是,当一个懒加载的bean 是一个不是懒加载的单例的依赖,那么ApplicationContext 也会在启动的时候创建懒加载的bean,因为它必须满足单例的依赖关系。懒加载被注入到一个单例bean中,不是懒加载的。
你可以使用在
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
5.自动注入协作者
Spring容器科室在协作的bean之间自动注入依赖关系。你可以允许Spring去自动的去解析其他bean通过通过检查ApplicationContext的内容。自动注入有下面的优势:
- 自动注入有效的减少了需要指明属性或者构造器参数的需求。
- 自动注入能够在你的bean改变的时候更新配置信息。例如,如果你需啊哟添加一个依赖给一个类,囊额依赖可以自动的满足需求而你不需要去修改配置。因为自动注入在开发中是特别有用的,而不用在基础代码变得更加稳定的时候改下选择区先明确的修改。
当使用基于XML的配置时,你指明可以在
模式 | 解释 |
没有(空) | (缺省情况下)没有自动注入,Bean的引用必须通过ref元素定义。改变缺省的设置在大型部署中是不建议的,因为显示的指明协作类会保证更好的控制和透明。在一定的范围程度上,它表示了一个系统的结构 |
通过名字 | 通过属性名来自动注入。Spring寻找一个bean和需要自动注入的属性名字相同的bean.例如,如果一个bean定义是通过名字自动注入,而且它包含以一个master属性(也就是说他有一个setMaster()方法),Spring寻找一个定义为名字是master的bean,并且用它来作为属性名字。 |
通过类型 | 如果只有一个属性类型的bean存在容器中,允许属性值自动注入。如果有多个的话,就会抛出致命异常,表示你可能不能够通过byType类型来注入实现。如果没有匹配的bean,什么事情也不会发生;属性值不会被设置。 |
构造器 | 和通过类型方式类似,但是需要引用到构造器参数。如果在容器中没有对应的构造器参数的bean,就会有指明的异常错误出现。 |
使用byType或者 constructor类型注入模式,你可以应用在数组和集合类型上。在这种情况下,所有在容器中的候选的bean只要匹配上期望的类型就会提供给相应的依赖。你可以注入强类型的Maps如果期望的key类型是String。一个自动注入的Maps的值由匹配所有期望类型的bean实例组成,Maps keys将包含相配的bean 名字。
你可以讲自动类型注入和依赖检查联系在一起,这个将在注入完成后进行。
5.1自动注入的限制和不利处
当自动注入在整个项目中都被使用的时候,自动注入就会起很好地作用。如果自动注入没有被使用总是,那么这个可能会使开发者疑惑去使用它去配置只是一个或是两个bean。
来看看自动注入受限和不利处:
- 在property和constructor-arg 设置的显示依赖可以覆盖自动注入。你不可以自动注入所谓的简单属性想原生类型,字符串,类,(包括数组)。这种受限是故意这样设计的。
- 自动注入没有显示的嵌套更加的明了。尽管这样,正如上面表格中写的那样,Spring会小心避免那些模糊的事情,比如无法预料的结果,你的Spring管理的对象之间的关系没有显示的给出文档等等。
- 封装信息可能对于那些聪明给你一个Spring容器中生成文档的工具是不可用的。
- 在容器里面多个Bean的定义通过setter方法构造器参数来匹配指明的类型来实现自动注入。对于数组、集合、或者maps,这些可能不会有什么问题。但是对于那些期望有一个值得依赖,这种模糊性无法得到直接的解析。如果没有唯一的Bean定义,就会抛出异常。
对于后面的场景,你可以有这些选择:
- 使用显示的封装而不是用自动注入。
- 通过设置属性autowire-candidata 属性为FALSE,而不适用自动注入对一个Bean的定义。
- 通过设置
的primary属性为TRUE来指定一个单独的Bean定义作为primary candidate。 - 通过基于注解的配置的方式实现更细粒度的控制。
5.2将一个Bean排除在自动注入之外
在一个Bean的基础之上,你可以将一个Bean排除在自动注入以外。在Spring的XML格式中,将
你可以限制自动注入的Bean通过模式匹配而不是Bean的名字。顶级元素
只需要提供一个 *Repository的值就可以了。为了提供多一个模式,在一个逗号分隔的列表中指出来就可以了。对于一个Bean定义的utowire-candidate
属性的明确的值TRUE和FALSE常常提前生效,对于这些Bean,模式匹配就不在起作用了。
这些技术在你永远都想通过自动注入的方式来注入到其他的Bean的Bean是很用用处的。这个不意味着一个被排除的Bean自己不能通过自动注入的方式了当然了,Bean自己不再是自动注入其他Bean的候选者了。
6.方法注入
在绝大部分的引用场景中,在容器中的大部分的Bean是单例的。当一个单例的Bean需要和其他的单例的Bean产生写作关系时,或者一个非单例的Bean需要和其他非单例Bean产生写作关系时,你以通过定义把这个Bean定义为其他Bean的属性的方式来定义依赖。
当Bean的生命周期不同的时候就会出现问题了。假设单例Bean A 需要去使用一个非单例Bean B,或许使用在A中的每一个方法调用。容器只会创建单例A 一次,而且只会有一次的机会去设置属性。容器不会给Bean A在每一次需要的时候提供给一个新的实例Bean B.
一种解决方案就是放弃以前的一些控制反转原则。你可以通过实现ApplicationContextAware的方式来让Bean A来通过容器提醒,而且通过一个getBean("B")的方法告诉容器去请求Bean B的实例在A需要的时候。下面就是这种方式的一个例子:
// a class that uses a stateful Command-style class to perform some processing
packagefiona.apple;
// Spring-API imports
importorg.springframework.beans.BeansException;
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.ApplicationContextAware;
public classCommandManager implementsApplicationContextAware {
privateApplicationContext applicationContext;
publicObject 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);
returncommand.execute();
}
protectedCommand createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public voidsetApplicationContext(
ApplicationContext applicationContext) throwsBeansException {
this.applicationContext = applicationContext;
}
}
前面的是不需要的,因为业务代码对于Spring框架来说是已知的。方法注入,多少算得上SpringIOC容器的高级特性了,允许以一种简洁的方式来处理了。
6.1查看方法注入
查看方法注入是容器重写容器管理的Bean的方法的功能,它可以返回在容器中其他命名的Bean的擦看结果。这个查看一般会涉及到一个原型Bean,正如在前面的场景中描述到的一样。
Spring框架通过使用CGLIGB库的字节码生成来动态的产生一个子类来重写这个方法来实现方法注入的。
注意:为了让这个动态的子类能够起作用,Spring容器中将产生子类的类不能够是final的,而且被复写的方法也不能是final的。
而且测试一个含有抽象方法的类需要你自己产生一个子类,并且实现了这个抽象方法。最后,成为方法注入的目标的对象不能够被序列化的。
因为Spring3.2中不在需要去添加CGLIB到你的classpath中了,因为CGLIB已经在org.springframework中被重写打包并且在spring-core jar包中发布了。
这样做的目的即是方便使用也是为了避免其他项目中使用不同版本的CGLIB造成潜在的冲突。
看下在前面的代码中的CommandManager类,你可以看到Spring容器动态覆盖了 createCommand()方法的实现。你的CommandManager
中不会有任何Spring的依赖,这个可以通过下面的例子看到:
packagefiona.apple;
// no more Spring imports!
public abstract classCommandManager {
publicObject 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);
returncommand.execute();
}
// okay... but where is the implementation of this method?
protected abstractCommand createCommand();
}
客户端中需啊哟含有注入的方法,这个需要被注入的方法需要像下面形式的签名:
<public|protected>[abstract] <return-type>theMethodName(no-arguments);
如果方法是抽象的,鼎泰的产生的子类需要实现那个方法。否则,动态产生的子类就会覆盖在原始类中蒂尼的具体的方法。例如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" 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="command"/>
</bean>
定义为commandManager的Bean调用它自己的createCommand()方法,在任何时候它需要一个新的实例的command Bean。你必须小心去将
command bean作为一个原型。如果部署是单例的话,那么command bean 每次返回的就是同一个实例了。
6.2 武断的方式替换
一个不太有用的方法注入形式比查看方法注入是替换在管理的bean中用其他方法实现的方法的能力。
通过基于XML配置的数据,对于一个已经部署的bean,你可以使用replaced-method元素去替换一个存在的方法用其他方法。
看下下面的有computeValue方法的类,我们准备用去复写这种方法:
public classMyValueCalculator {
publicString 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 classReplacementComputeValue implementsMethodReplacer {
publicObject reimplement(Object o, Method m, Object[] args) throwsThrowable {
// 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"/>
你可以在
只有一个方法被重载或者在一个类里面有多个变体的时候方法的参数才是需要的。为了方便,String类型的参数可能是一个完整的指明
类型名称的子串。例如,下面的都匹配java.lang.String:
java.lang.String
String
St
因为通常来讲参数的数量就足够来区分不同的选择,通过允许你去指出最短的字符串来匹配参数类型的这种简写可以节省很多的类型书写。