[Spring] Bean的实例化及依赖注入

Spring框架最厉害的是提出了两个概念,IOC容器和AOP切面编程。

IOC容器的本质是个创建并管理对象的工厂,关于它的两个重要特性,是“控制反转”和“依赖注入”:

  1. 控制反转:这里的“控制”是指创建对象,这原本是程序员的权利,但是应用IOC容器以后,对象的创建权被容器拿走了,留给程序员做的工作,就是按照Spring的规范,声明对象及对象间的依赖关系,这就是“反转”的含义。
  2. 依赖注入:对象内部的属性就是该对象的依赖,如果没有这些属性,对象就不能正常工作。程序员声明出对象间依赖关系后,IOC容器会在对象的生命周期内,负责为对象注入这些属性。

IOC容器创建并管理的对象都叫做Bean,本文讲述Bean的声明及实例化方法,以及依赖注入的方法。

Spring声明对象及其依赖的方式有两种:XML、注解。

平时应用中,可能注解的使用频率更高,但我觉的XML更好理解,所以下文讲解都采用XML声明方式。

Bean的声明及实例化

方法一:构造器

写个User类定义:

package x.y;

public class User {
}

按照Spring规范,声明如下:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="x.y.User"></bean>

</beans>

其中,id声明对象在容器中的唯一标识符,class声明对象的全路径类名。

方法二:静态工厂方法

改造User类定义,构造器私有化,提供静态工厂方法创建对象:

package x.y;

public class User {

    private static User user = new User();

    private User() {
    }

    public static User createInstance() {
        return user;
    }

}

Spring声明如下:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="x.y.User" factory-method="createInstance"></bean>

</beans>

factory-method声明告诉容器,在进行实例化时,需调用内部工厂方法createInstance()创建对象。这里有两点需要注意:

  1. 工厂方法的返回值必须是class声明的类型
  2. 工厂方法必须属于class声明的类

方法三:对象工厂方法

User类定义回到最初的样子:

package x.y;

public class User {
}

新增Locator工厂类定义,通过工厂方法提供User类对象:

package x.y;

public class Locator {

    private static User user = new User();

    public User createUserInstance() {
        return user;
    }

}

Spring声明如下:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="locator" class="x.y.Locator"></bean>

    <bean id="user" factory-bean="locator" factory-method="createUserInstance"></bean>

</beans>

声明中,locator对象通过构造器创建,user对象通过locatorcreateUserInstance方法创建。

Bean的依赖注入

注入对象的依赖属性,总体而言有两种方法:

  1. 通过对象构造器注入
  2. 通过对象的setter方法注入

构造器注入

对象需要提供有参构造器,将依赖属性通过参数传入。

根据构造器的参数类型及顺序,这里又分为两种情况。

构造参数无歧义

如下,通过构造参数类型就可以很明显的区分依赖对象:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

XML声明就可以如下:

<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>

构造参数有歧义

有歧义的示例如下:

package examples;

public class ExampleBean {

    private int years;

    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

如果只看这个类的构造器,是看不出参数有什么歧义的。但是要考虑到,XML的配置只能提供字符串类型的值,歧义就出现了。

比方说,XML提供如下配置(错误的配置):

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg value="7500000"/>
    <constructor-arg value="42"/>
</bean>

这两个值的注入,能分清哪一个是years,哪一个是ultimateAnswer吗?

如果有类似这样的歧义出现,需要使用如下更详细的声明方式:

提供构造参数的具体类型
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
提供构造参数的定义顺序(索引从0开始)
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>
提供构造参数的名称
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

注意!采用这种声明方式,代码编译时必须启用debug模式!因为release模式编译出来的字节码中,构造参数的名称会发现变化,导致SpringXML声明对应不上!

如果必须采用release模式编译,可以在Java类构造器中通过注解java.beans.ConstructorProperties明确定义出构造参数名称:

package examples;

public class ExampleBean {

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
    
}

setter方法注入

setter注入比构造器注入要简单,因为setter注入针对的都是单一属性,而构造器可以同时注入多个依赖属性。

两种注入方式的XML声明配置都是一样的,只是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;
    }
}

对应的XML声明:

<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"/>

构造器注入和setter注入的选择

Spring官网文档提供的经验法则是:

  1. 强制依赖项通过构造器注入
  2. 可选依赖项通过setter注入

Spring团队通常是提倡使用构造器注入的,因为它允许你将组件实现为不可变对象,并确保所有的依赖项都不为空。此外,通过构造器注入依赖的对象,总是能以完全初始化的状态返回给客户端调用。

采用setter注入的一个好处是留有一个途径,以后可以重新注入依赖。

总体来说,还是要根据不同的特定类,选择最有意义的注入方式。比如说,某个第三方类不提供setter方法的时候,就只能选择构造器注入的方式了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值