依赖注入有两种方式:构造器注入和Setter注入。
1 构造器注入
1.1 构造器参数解析
1.1.1 按照定义顺序解析
如果构造器参数的类型定义没有潜在的歧义,那么构造器会按照bean定义中构造器参数的定义顺序依次进行匹配。看下面的代码:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
上述代码中参数类型定义不存在潜在的歧义,我们假设Bar
和Baz
之间不存在继承关系。因此,下面代码中在元素<constructor-arg/>
的配置即使没有明确指定构造参数顺序或者类型也会起作用。
<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>
1.1.2 使用type
属性来显式指定构造参数类型
当我们使用简单类型,比如<value>true</value>
。Spring并不能知道该值的类型,不借助其他帮助Spring将不能通过类型进行匹配。看下面的类:
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>
1.1.3 使用index
属性来显式指定构造参数的索引
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
使用索引可以解决多个简单值的混淆,还能解决构造方法有两个相同类型的参数的混淆问题,注意index是从0开始的。
1.1.4 使用构造器参数命名来指定值的类型
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住为了使这个起作用,你的代码编译时要打开编译模式,这样Spring可以检查构造方法的参数。如果你不打开调试模式(或者不想打开),也可以使用@ConstructorProperties JDK注解明确指出构造函数的参数。下面是简单的例子:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
1.2 代码示例
1.2.1 准备Bean类
package com.ws.edu.spring;
public class Food {
}
package com.ws.edu.spring;
public class Game {
}
package com.ws.edu.spring;
public class Person {
private int id;
private String name;
private Food food;
private Game game;
public Person() {
super();
}
public Person(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Person(Food food, Game game) {
super();
this.food = food;
this.game = game;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Food getFood() {
return food;
}
public void setFood(Food food) {
this.food = food;
}
public Game getGame() {
return game;
}
public void setGame(Game game) {
this.game = game;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", food=" + food
+ ", game=" + game + "]";
}
}
1.2.2 配置xml
<?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="food" class="com.ws.edu.spring.Food"/>
<bean id="game" class="com.ws.edu.spring.Game"/>
<!-- 按照定义顺序解析 -->
<bean id="person1" class="com.ws.edu.spring.Person">
<constructor-arg ref="food"/>
<constructor-arg ref="game"/>
</bean>
<!-- 使用type属性来显式指定构造参数类型 -->
<bean id="person2" class="com.ws.edu.spring.Person">
<constructor-arg type="int" value="2"/>
<constructor-arg type="java.lang.String" value="小明"/>
</bean>
<!-- 使用index属性来显式指定构造参数的索引 -->
<bean id="person3" class="com.ws.edu.spring.Person">
<constructor-arg index="0" value="3"/>
<constructor-arg index="1" value="小明"/>
</bean>
<!-- 使用构造器参数命名来指定值的类型 -->
<bean id="person4" class="com.ws.edu.spring.Person">
<constructor-arg name="id" value="4"/>
<constructor-arg name="name" value="小明"/>
</bean>
</beans>
1.2.3 编写启动类
package com.ws.edu.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
@SuppressWarnings("resource")
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
System.out.println(context.getBean("person1",Person.class));
System.out.println(context.getBean("person2",Person.class));
System.out.println(context.getBean("person3",Person.class));
System.out.println(context.getBean("person4",Person.class));
}
}
1.2.4 运行结果
2 Setter注入
示例如下:
<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"/>
3 循环依赖
如果你主要使用构造器注入,很有可能会产生无法解决的循环依赖问题。 举个例子:A类需要通过构造器注入B类的实例,并且B类又需要通过构造器注入A类的实例。如果为类A和类B配置的bean被相互注入的话,Spring IoC容器在运行时会检测到这个循环依赖并且抛出一个BeanCurrentlyInCreationException
异常。
一个可能的解决方法是修改类的源代码,将构造器注入改为setter注入。或者只使用setter注入避免使用构造器注入。换句话说,虽然这并不被推荐使用,你可以使用setter注入配置循环依赖。 和通常的情况不同(没有循环依赖),bean A 和bean B之间的循环依赖将会导致其中一个bean在被完全初始化的之前被注入到另一个bean里(先有鸡先有蛋的问题)