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实例存在。但也有其缺点:构造方法不利于继承和扩展,甚至会造成循环依赖。

属性注入的优点:更加灵活。

方法注入:需要提供工厂类、工厂实例或者继承实现特殊接口,具有侵入性,一般不使用。
posted @ 2023-01-31 14:34  小张同学哈  阅读(77)  评论(0)    收藏  举报