当构造函数有2个相同类型的参数,指定次序可以解决此种情况。注意index
是从0开始
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
记住,若要使Spring能从构造函数查找参数名字,代码在编译时必须开启调试模式。若你没有开启调试模式(或者不想),可以使用@ConstructorProperties
JDK 注解明确指定构造参数的name
。样例程序:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
<h5 id='beans-setter-injection'>setter注入</h5>
Setter注入是容器调用bean上的setter方法,bean是使用无参构造函数返回的实例,或者无参静态工厂方法返回的实例。
下面样例中展示了只能使用Setter注入的类。这个类是传统java类,就是个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
对它所管理的bean支持构造注入和setter注入。也支持先构造注入再setter注入。定义依赖,会转换成某种形式的<code class="scode">BeanDefinition</code>类,<code class="scode">BeanDefinition</code>类与<code class="scode">PropertyEditor</code>实例配合,即可将属性从一种格式转换成其他格式。然而,大多数程序员不会直接使用这些类(也就是编程式),更多的是使用XML、注解(也就是<code class="scode">@Component</code><code class="scode">@Controller</code>等等),或者<code class="scode">@Configuration</code>注解的类中的方法上使用 <code class="scode">@Bean</code>。这些配置数据,都会在容器内部转换成BeanDefinition
,用于加载整个Spring Ioc 容器。
构造注入对比setter注入
何时使用构造注入,何时使用setter注入,经验法则是:强制依赖用构造,可选依赖用Setter。注意,在settter方法上使用<code class="scode">@Required</code>注解即可另属性强制依赖。
Spring 团队建议,构造注入的实例是不可变的,不为null的。此外,构造注入组件要将完全初始化后的实例返回给客户端代码。还有,大量参数的构造函数是非常烂的,它意味着该类有大量的职责,得重构。
setter注入主要用于可选依赖,类内部可以指定默认依赖。否则类内所有使用依赖的地方,都得进行非空校验。setter注入的有个好处就是,类可以重配置或者再注入。因此,使用JMX MBeans
进行管理的场景中,就非常适合setter注入。
使用何种依赖注入方式,对于某些类,非常有意义。有时协同第三方类处理,没有源码,由你来决定使用何种方式。比如,第三方类未暴露任何setter方法,那么构造注入也许就是唯一的可行的注入方式了。
<h5 id="beans-dependency-resolution">依赖处理过程</h5>
容器解析bean依赖如下:
-
ApplicationContext
创建后用配置元数据中描述的所有bean进行初始化。配置元数据格式可以是XML、Java Code,或者注解。 - 每个bean的依赖,都会以下列形式表达:属性、构造参数,静态工厂方法的参数。当bean真正的创建时,这些依赖会被提供给bean。
- 每个属性或者构造函数或者以value值形式在bean处直接设置,或者引用容器中其他bean。
- 每一个属性或者构造参数都是一个值,该值将会从指定的格式转换为属性、构造参数的真正类型。Spring默认会将一个
String
类value转换成内建类型,比如int
,long
,String
,boolean
等等
Spring容器在创建bean之前会验证bean的配置。在bean创建之前,bean的属性不会赋值。当容器创建之后,会创建被设置为预先初始化的sington-scope
单例作用域bean,非单例作用域bean,只有在请求时才会创建。作用域,在5.5章有定义,"Bean 作用域"。一个bean的创建,可能会引起许多bean的创建。因为bean的依赖以及依赖的依赖得先创建好用于引用。不涉及首先创建的bean及其依赖类bean,会稍后创建。
循环依赖
如果你主要使用构造注入,可能会创建一个循环依赖,该依赖不能解析。
举个栗子:类A需要类B的实例,使用了构造注入,类B需要一个类A的实例,也用了构造注入。若在配置文件中配置类A的bean和类B的bean互相注入,Spring IoC容器在运行时发现循环引用,抛出异常BeanCurrentlyInCreationException
。
一般使用Setter注入替代构造注入,这需要修改源码改配置,来解决循环依赖。避免使用构造注入或者只使用setter,都能避免循环依赖。 换句话说,虽然不推荐循环依赖,但是你可以使用setter注入来完成循环依赖。
和大多数场景(无循环引用)不一样的是,循环引用中的类A和类B中,得强制其中一个自己能完全初始化,然后注入给另一个(经典的先有鸡现有蛋的问题)。
循环依赖end
对于Spring,你经管放心,它非常智能。他能在容器加载期发现配置中的问题,比如:引用了一个不存在的bean、循环依赖。Spring在bean创建后,会尽可能迟的设置bean属性并处理依赖。这意味着,spring容器正确加载之后,当你请求一个对象而该对象的创建有问题或者是该对象的依赖有问题时,也能产生一个异常。举例来说,因为属性找不到,或者属性无效, 导致bean抛出异常。这可能会延迟发现配置问题,这就是为什么ApplicationContext
默认会预先实例化单例bean。在这些bean被实际请求之前就创建,会消耗一些时间和内存,但是在ApplicationContext
创建后你就能发现配置问题,而不是更迟。如果你愿意 ,也可以重写该行为,让单例bean延迟初始化。
如果没有循环依赖,当一个或者多个合作bean被注入到他们的依赖类时,每一个合作bean将会比依赖类更早的实例化。也就是说,如果bean A依赖bean B,Spring Ioc容器在调用A的setter方法之前,会先实例化B。换句话说,bean先实例化(非单例),然后设置依赖,然后调用相关声明周期方法(比如配置的init方法,或者是初始化回调函数)。
<h5 id='beans-some-examples'>注入依赖样例</h5>
The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions:
下面例子中使用了XML配置元数据,setter注入方式。XML 配置文件中的片段定义了bean:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 使用内嵌的ref元素完成setter注入 -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- 使用ref属性完成setter注入 -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
看java代码
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;
}
}
In the preceding example, setters are declared to match against the properties specified in the XML file. The following example uses constructor-based DI:
上例中,setter方法名要和XML文件中的property
元素的name
属性相匹配。下面演示使用构造注入 :
<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"/>
看java代码
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
类的参数。
现在考虑下这个样例的变种,将使用构造器改为静态工厂方法返回对象实例:
<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"/>
看java代码
public class ExampleBean {
//私有构造函数
private ExampleBean(...) {
...
}
// 静态工厂方法; 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
元素产生,就像是bean的构造函数一样.工厂方法返回的类的类型无需和工厂类类型相同,虽然本例中他们是相同的。实例工厂方法(非静态)和静态工厂方法本质相同(除了使用facory-bean
属性替代class
属性,其他都相同),因此细节就不讨论了。
<h4 id='beans-factory-properties-detailed'>依赖和配置详解</h4>
前面章节提到的,你可以定义的bean的属性和构造参数引用其他的Spring bean(合作者),或者是使用value属性设置其值。Spring XML格式配置元数据至此<property/>
和<constructor-arg/>
子元素,用以实现构造注入和属性注入。
<h5 id="beans-value-element">直接赋值(原始类型、String等等)</h5>
<property />
元素的value
属性为对象域属性或者构造参数设置了一个可读的字串。Spring的会将其转换为实际的与属性或者参数的数据类型。
<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="masterkaoli"/>
</bean>
下面的样例,是使用了XML配置中的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
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更简洁;然而,错别字,要在运行期才能发现而不能再开发期发现,除非你使用IDE支持自动补全。这样的的IDE的助手真心推荐。
也可以这样配java.unit.Properties
实例:
<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 容器通过JavaBean的PropertyEditor
机制将<value/>
元素内的值转换到java.util.Properties
实例。这是非常棒的,Spring团队最喜欢的几处好用之处之一:用内嵌<value/>
元素替代 值属性风格。
<h5 id='beans-idref-element'>元素<span class="scode">idref</span></h5>
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真是存在。在第二个中,对于client bean是属性 targetName
的值则没有校验执行 .client
bean真正的实例化时,错别字才会被发现(可能会导致致命错)。如果client
bean是一个原型bean,这个错字导致的异常也许会等到部署后才能被发现。
在4.0 beans xsd ,
idref
上的local
属性不在支持。因此它能提供正规bean引用 。当你升级到4.0的语法时,记得清除已经存在于idref
元素上的local
属性。
一个老生常谈的问题(至少是2.0以前了),<idref/>
带来的好处,在使用ProxyFactorybean
bean定义AOP拦截器时,当指定拦截器名字是使用<idref/>
元素将,容器会校验拦截目标是否存在。
<h5 id='beans-ref-element'>引用其他bean(协作类)</h5>
ref
元素是<constructor-arg/>
元素和<property/>
元素内决定性元素。用它设置bean的属性以引用另一个容器管理的bean。引用的bean就是要设置属性的bean的依赖,在设置属性值之前它就要被初始化。(如果协作类是单例bean,它会在容器初始化时首先完成初始化)。差不多所有的bean都会引用其他对象。指定id/name
的对象的作用域和依赖校验通过bean
,local
,parent
属性来配置。
指定引用bean通常使用<ref/>
标签,它允许引用本容器或者父容器中任意的bean,无需配置在同一个xml文件中 。<ref/>
标签中bean
的属性值,使用的被引用bean的id
或者name
。
<ref bean="someBean"/>
通过指定目标bean的parent
属性来引用当前容器的父容器中的bean。parent
属性的值可以和引用bean的id
或者name
(引用bean的name之一)相同,引用的bean必须存在于当前容器的父容器中。若容器存在继承的情况,并且需要封装现有父容器中的某个bean到一个代理中,就可以用此种引用机制,一个与parent
bean重名的bean。
<!-- 父容器中 -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- 依赖 -->
</bean>
子容器中
<bean id="accountService" <!-- 和parent bean重名 -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!--注意如何引用 parent bean -->
</property>
<!-- 其他配置和依赖 -->
</bean>
在4.0 beans xsd ,
ref
上的local
属性不在支持。因次它不再支持正规bean的引用 。当你升级到到4.0时,记得清除已经存在于ref
元素上的local
属性。
<h5 id='beans-inner-beans'>内部bean</h5>
在<property/>
元素或者constructor-arg/>
元素内定义<bean/>
元素,就是所谓的内部类。
<bean id="outer" class="...">
<!-- 不是引用而是定义一个bean -->
<property name="target">
<bean class="com.example.Person"> <!-- 这就是内部类 -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部bean的定义无需id
或name
;容器会忽略这些属性。也会忽略scope
标记。内部通常是匿名的,伴随着外部类(的创建)而创建 。不能引用内部bean(ref属性不能指向内部bean),除非使用闭合bean
标签。
译者注,内部bean更直观
fuck goods,上干活
public class Customer {
private Person person;
public Customer(Person person) {
this.person = person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "Customer [person=" + person + "]";
}
}
再来一段
public class Person {
private String name;
private String address;
private int age;
//getter and setter methods
@Override
public String toString() {
return "Person [address=" + address + ",
age=" + age + ", name=" + name + "]";
}
}
通常情况下,使用在CustomerBean
bean内设置ref
属性值为Person
bean的标示符,即完成注入。
<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
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="CustomerBean" class="com.example.common.Customer">
<property name="person" ref="PersonBean" />
</bean>
<bean id="PersonBean" class="com.example.common.Person">
<property name="name" value="MrChen" />
<property name="address" value="address1" />
<property name="age" value="28" />
</bean>
</beans>
In general, it’s fine to reference like this, but since the ‘MrChen’ person bean is only used for Customer bean only, it’s better to declare this ‘MrChen’ person as an inner bean as following :
一般情况下,这样的引用很好用。但是如果'MrChen'这个person bean只用于Customer
。最好是使用内部bean来声明Person
,看起来更加直观,更具有可读性.
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="CustomerBean" class="com.mkyong.common.Customer">
<property name="person">
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyong" />
<property name="address" value="address1" />
<property name="age" value="28" />
</bean>
</property>
</bean>
</beans>
This inner bean also supported in constructor injection as following :
内部bean也支持构造注入
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="CustomerBean" class="com.mkyong.common.Customer">
<constructor-arg>
<bean class="com.mkyong.common.Person">
<property name="name" value="mkyong" />
<property name="address" value="address1" />
<property name="age" value="28" />
</bean>
</constructor-arg>
</bean>
</beans>
<h5 id='beans-collection-elements'>集合</h5>
<list/>
,<set/>
,<map/>
,<props/>
元素,用来设置Java Collection
属性和参数,分别对应List
,Set
,Map
,Properties
<bean id="moreComplexObject" class="example.ComplexObject">
<!--调用setAdminEmails(java.util.Properties) -->
<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>
<!-- 调用setSomeList(java.util.List) -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- 代用setSomeMap(java.util.Map) -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- 调用 setSomeSet(java.util.Set) -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map.key,map.value,或者set.value,以可以是以下元素
bean | ref | idref | list | set | map | props | value | null
<h5 id="beans-collection-elements-merging">集合合并</h5>
集合合并
Spring容器也支持集合合并。应用开发者可以定义父集合<list/>
,<map/>
,<set/>
或者<propx/>
元素,该元素可以有子集合<list/>
,<map/>
,<set/>
或者<props/>
元素集成或者重写父集合中的值。也就是,子集合中的值是合并父子集合后的值,其中子集合中的值会覆盖父集合中的值。
这一章节讨论父-子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 child
定义中,指定property
adminEmails
的<props/>
元素中merge=true
属性。当child
bean被容器解析并且实例化时,实例有一个adminEmails
的Properties
集合,该集合包含了父子容器中adminEmails
集合合并后的值。
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子Properties
集合的将继承所有父集合中<props/>
定义的值,并且重名属性值会覆盖父集合中的值.
这个合并行为,同样可以用在<list/>
,<map/>
,<set/>
类型上。对于<list/>
元素,spring合并行为与List
类型的合并一样,也就是,spring合并行为维持集合有序;父集合中的元素索引位置比子集合元素索引位置靠前。对于Map
,Set
,Properties
集合类型,不存在次序。因此,没有次序影响Map
,Set
,Properties
,这涉及到容器内部使用的这些类型的所有实现类。
译注:这里没有提到List会不会发生覆盖 ,既然没提到,那就是List没有覆盖行为。当然了,实践才是王道,动手实验才能验证推测,研读源码才能知道原理,下面上干货
Java代码
public class CollectionMerge {
private List<String> list;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}