Spring之依赖注入
依赖注入
当一个Bean定义了其依赖的Bean(通过构造器参数、工厂方法参数、setter方法参数等方式)时,Spring在创建该Bean时将会把该Bean依赖的Bean注入到其对象实例中。用户只是定义了该依赖关系,具体的依赖注入过程由Spring容器完成。
构造器注入
Spring通过调用Bean的有参构造器来完成构造器注入,每一个参数代表Bean的一个依赖。
按类型匹配入参
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
如果`ThingTwo`和`ThingThree`没有继承关系且没有潜在的歧义存在,则Spring可以按照类型自动匹配。下面的配置可以很好的工作:
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
如果Bean的参数不是引用类型,而是原始类型呢?此时Spring就无法确定`value`的类型了,此时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>
按索引匹配入参
上面例子中的`ExampleBean`,也可以使用指定参数索引的方式匹配构造函数的参数。
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
按名称匹配入参
上面例子中的`ExampleBean`,也可以使用指定参数名称的方式匹配构造函数的参数。
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
构造器注入引起的循环依赖
Spring容器能够对基于构造器注入的Bean进行实例化的前提是:Bean构造函数的参数引用的Bean对象必须已经实例化完成并准备就绪。如果两个Bean都采用构造器注入,并且构造器函数入参都引用对方,就会发生循环依赖问题。如下:
class A {
private B b;
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public B(A a) {
this.a = a;
}
}
<bean id="a" class="A">
<constructor-arg ref="b"></constructor-arg>
</bean>
<bean id="b" class="B">
<constructor-arg ref="a"></constructor-arg>
</bean>
以上代码及配置会导致循环依赖问题,Spring会抛出 `BeanCurrentlyInCreationException`异常。解决此类循环依赖问题通常只需要将构造器注入替换为`setter`方法注入即可。
属性注入
属性注入即基于`setter`方法的注入方式。基于属性注入的方式需为Bean提供一个无参构造器,以完成实例的初始化。
<bean id="att" class="com.zzvcom.bean.Attachment">
<property name="name" value="att2"/>
</bean>
public class Attachment {
private String name;
public Attachment() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Attachment{" +
"name='" + name + '\'' +
'}';
}
}
工厂方法注入
工厂方法注入分为静态工厂方法和实例工厂方法。该模式是通过特定的工厂方法调用获取Bean实例,进行注入。其配置及使用方式在介绍Bean配置时已经展现,此处不再赘述。
方法注入
lookup-method
一个无状态的Bean其作用域一般为`Singleton`,此时每次从容器中获取Bean都将返回相同的Bean实例。但是如果一个Bean(假设名称为Person)的作用域为`Singleton`,但是其依赖的Bean(假设名称为Car)作用域为`prototype`,由于依赖注入是在Spring Bean创建时完成的,所以一旦Bean创建完成,该Bean也就不会变化,其依赖的Bean也不会变化。因此,我们每次获取到`Person`实例时,其依赖的`Car`实例也是同一个。如果我们想每次获取`Person`时,获取到的`Car`都是不同的,应该怎么做呢?
一种方法是使`Person`实现`BeanFactoryAware`,然后在`getCar()`方法中使用`beanFactory`获取新的`Car`实例,如下:
public class Person implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
//由于Car的作用域为prototype,因此每次调用都会返回新的car实例
public Car getCar(){
return (Car) beanFactory.getBean("car");
}
}
另一种方法是使用lookup方法注入。CGLib包使得Spring可以再运行期动态操作字节码,为Bean动态创建子类或实现类,也使得Spring拥有重写Bean方法的能力。
<bean id="car" class="com.zzvcom.bean.Car" scope="prototype">
<property name="brand" value="红旗"/>
</bean>
<bean id="person" class="com.zzvcom.bean.Person">
<lookup-method name="getCar" bean="car"></lookup-method>
</bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
Person p1 = (Person)context.getBean("person");
Person p2 = (Person)context.getBean("person");
System.out.println(p1 == p2);//返回true
System.out.println(p1.getCar() == p2.getCar()); //返回false
这种方式相当于Spring在运行期自动实现了如下代码:
public class SubPerson extends Person implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public Car getCar() {
return (Car) context.getBean("car");
}
}
replace-method
Spring中也可以使用某一个Bean的方法去替换另一个人的方法。不过,某一个Bean如果想去替代其他Bean的方法,那么他必须实现`MethodReplacer`接口。如:
public class PersonReplacer implements MethodReplacer {
@Override
public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
//do anything you can
Car car = new Car();
car.setBrand("捷豹");
return car;
}
}
然后使用replace-method配置bean:
<bean id="person2" class="com.zzvcom.bean.Person">
<replaced-method replacer="replacer" name="getCar"></replaced-method>
</bean>
<bean id="replacer" class="com.zzvcom.bean.PersonReplacer"></bean>
如何选择注入方式
构造方法注入的优点:可以保证Bean的重要属性在初始化时就设置好,避免null值存在或无用的Bean实例存在。但也有其缺点:构造方法不利于继承和扩展,甚至会造成循环依赖。
属性注入的优点:更加灵活。
方法注入:需要提供工厂类、工厂实例或者继承实现特殊接口,具有侵入性,一般不使用。
浙公网安备 33010602011771号