Spring Bean 依赖注入

依赖注入是 Spring 协调不同 Bean 实例之间的引用而提供的一种机制,当 Bean 实例A运行过程中需要引用另外一个 Bean 的实例B时,容器会创建实例B,并将实例B通过实例A的构造函数、setter方法等方式注入到实例A中,这个过程称为依赖注入。

依赖注入的好处就是隔离 Bean 之间的代码耦合,提高 Bean 的可重用性。Spring框架提供了构造函数注入、设置方法注入、自动装配注入和注解注入四种注入方式。

注入方法

使用构造器注入

通过配置构造器参数实现。除了构造器方式,还有静态工厂、实例工厂方法可以进行构造器注入,有如下 Java 类

public class HelloImpl implements IHelloService {
    private String message;
    private int index;

    public HelloImpl(String message, int index) {
        this.message = message;
        this.index = index;
    }
}
  1. 根据参数索引注入
<bean id="hello1" class="xxxx.HelloImpl">
    <constructor-arg index="0" value="Hello"></constructor-arg>
    <constructor-arg index="1" value="1"></constructor-arg>
</bean>
  1. 根据参数类型进行注入,会按顺序匹配
<bean id="hello2" class="xxxx.HelloImpl">
    <constructor-arg type="java.lang.String" value="Hello"></constructor-arg>
    <constructor-arg type="int" value="1"></constructor-arg>
</bean>
  1. 根据参数名进行注入
<bean id="hello3" class="xxxx.HelloImpl">
    <constructor-arg name="message" value="Hello"></constructor-arg>
    <constructor-arg name="index" value="2"></constructor-arg>
</bean>
  1. 还可以通过在构造器上添加 @java.beans.ConstructorProperties({“message”, “index”}) 注解来指定参数名字

Spring 类型转换系统对于 boolean 类型进行了容错处理,除了可以使用 “true/false” 标准的 Java 值进行注入,还能使用 “yes/no”、“on/off”、“1/0” 来代表。

Setter 方法注入

Setter 方法注入在,构造器注入后执行,对于上面的 Java 类进行如下配置,会覆盖构造器注入的属性值

<bean id="hello4" class="com.alex.practise.springhelloworld.HelloImpl">
    <property name="message" value="Hello Ss"></property>
    <property name="index" value="1"></property>
</bean>

Spring 如何知道 setter 方法?并将值注入?

方法名是要遵守约定的,setter 注入的方法名要遵循 “JavaBean getter/setter 方法命名约定”:

  • 必须要有无参构造器
  • 属性为 private 访问级别
  • 属性可通过一组 setter 和 getter 方法来访问

setter方法,以“set” 开头,后跟首字母大写的属性名
getter方法,一般属性以“get”开头,对于 boolean 类型一般以“is”开头,后跟首字母大写的属性名
其他特殊情况,比如属性有连续两个大写字母开头,如“URL”,则 setter/getter 方法分别为:“setURL”和“getURL”

特殊类型注入

注入常量、集合、数组、字典、ID等

<property name="message" value="Hello Ss"></property>
<property name="values">  
    <list>  
        <value>1</value>  
        <value>2</value>  
    </list>  
</property> 
<property name="values">  
    <set>  
        <value>1</value>  
        <value>2</value>  
    </set>  
</property>  

注入依赖Bean及注入内部Bean

public class HelloDecorator implements IHelloService {

    private IHelloService helloService;

    // 无参构造器
    public HelloDecorator() { }

    // 有参构造器
    public HelloDecorator(IHelloService helloService) {
        this.helloService = helloService;
    }

    // setter 方法
    public void setHelloService(IHelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public void sayHello() {
        helloService.sayHello();
    }
}
<bean id="hello" class="xxxx.HelloImpl"></bean>
<bean id="decorator1" class="xxxx.HelloDecorator">
    <constructor-arg index="0" ref="hello"></constructor-arg>
</bean>
<bean id="decorator2" class="xxxx.HelloDecorator">
    <property name="helloService">
        <ref bean="hello" />
    </property>
</bean>

内部 Bean

内通过 标签定义的 Bean,不管是否指定id或name,都会有唯一的匿名标识符,而且不能指定别名,对其他外部 Bean 不可见

<bean id="bean0" class="xxxx.HelloDecorator">
    <property name="helloService">
        <bean id="innerBean" class="xxxx.HelloImpl"></bean>
    </property>
</bean>

注入 NULL 值

通过 标签注入 NULL 值

循环依赖问题

循环依赖就是循环引用,两个或多个 Bean 相互之间的持有对方,比如 CircleA 引用 CircleB,CircleB 引用 CircleC,CircleC 引用CircleA,最终反映为一个环。
注意:此处不是循环调用,循环调用是方法之间的环状调用, 循环调用是无法解决的,除非有终结条件,否则就是死循环。

Spring 如何解决循环依赖
如下代码所示,3个类互相依赖:

public class CircleA {
    private CircleB circleB;
    public CircleA() { }
    public CircleA(CircleB circleB) {
        this.circleB = circleB;
    }
    public void setCircleB(CircleB circleB) {
        this.circleB = circleB;
    }
    public void a() {
        circleB.b();
    }
}

public class CircleB {
    private CircleC circleC;
    public CircleB() { }
    public CircleB(CircleC circleC) {
        this.circleC = circleC;
    }
    public void setCircleC(CircleC circleC) {
        this.circleC = circleC;
    }
    public void b() {
        circleC.c();
    }
}

public class CircleC {
    private CircleA circleA;
    public CircleC() { }
    public CircleC(CircleA circleA) {
        this.circleA = circleA;
    }
    public void setCirclaC(CircleA circleA) {
        this.circleA = circleA;
    }
    public void c() {
        circleA.a();
    }
}

构造器循环依赖

通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'circleA': Requested bean is currently in creation: 
Is there an unresolvable circular reference?

Setter 方法循环依赖

对于 setter 注入造成的依赖是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤(如 setter 注入)的 Bean 来完成的,而且只能解决单例作用域的 Bean 循环依赖

通过暴露一个单例工厂方法,从而使其他 Bean 能引用到该 Bean

<bean id="circleA" class="xxxx.CircleA" >  
    <property name="circleB" ref="circleB"/>  
</bean>  
<bean id="circleB" class="xxxx.CircleB" >  
    <property name="circleC" ref="circleC"/>  
</bean>  
<bean id="circleC" class="xxxx.CircleC" >  
    <property name="circleA" ref="circleA"/>   
</bean> 

对于 prototype bean,Spring 容器无法完成依赖注入,因为 Spring 容器不缓存 prototype bean

解释:
Spring 内部所使用的两个内部属性:singletonFactories 和 earlySingletonObjects,这两个属性在 DefaultSingletonBeanRegistry 中被定义

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory> singletonFactories = 
    new HashMap<String, ObjectFactory>();
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = 
    new HashMap<String, Object>();

singletonFactories,用于存储在 Spring 内部所使用的 beanName->对象工厂的引用,一旦最终对象被创建,此引用信息将删除。
earlySingletonObjects ,用于存储在创建 Bean 早期对创建的原始 Bean 的一个引用,注意这里是原始 Bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除。

Spring 源码

protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized(singletonObjects) {
        if(!singletonObjects.containsKey(beanName)) {
            singletonFactories.put(beanName, singletonFactory);
            earlySingletonObjects.remove(beanName);
            registeredSingletons.add(beanName);
        }
    }
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = singletonObjects.get(beanName);
    if(singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized(singletonObjects) {
            singletonObject = earlySingletonObjects.get(beanName);
            if(singletonObject == null && allowEarlyReference) {
                ObjectFactory singletonFactory = (ObjectFactory)singletonFactories.get(beanName);
                if(singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    earlySingletonObjects.put(beanName, singletonObject);
                    singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject == NULL_OBJECT ? null : singletonObject;
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized(singletonObjects) {
        singletonObjects.put(beanName, singletonObject == null ? NULL_OBJECT : singletonObject);
        singletonFactories.remove(beanName);
        earlySingletonObjects.remove(beanName);
        registeredSingletons.add(beanName);
    }
} 

Tags:

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注