spring之依赖注入

 

 

依赖注入

    Spring框架的核心功能有两个:

  1. Spring容器作为超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。
  2. Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称为"依赖注入"的方式来管理Bean之间的依赖关系。

使用依赖注入,不仅可以为Bean注入普通的属性值,还可以注入其他Bean的引用。依赖注入是一种优秀的解耦方式,其可以让Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起。

 

理解依赖注入

 IoC(控制反转)和DI(依赖注入)的意思是相同的,只是表述方式不一样。

spring之依赖注入

 

当某个Java对象(调用者)需要调用另一个Java对象(被依赖对象)的方法时,在传统模式下通常有两种做法:

1.原始做法: 调用者主动创建被依赖对象,然后再调用被依赖对象的方法。

就比如A类中要使用B类的方法,必须要先创建B类实例,再由B类实例调用B类的方法

 

2.简单工厂模式:调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。

类似这种方式:

Phone p = PhoneFactory.getPhone();

p.callSomeOne(String phoneNum);

 

但注意上面的主动二字,这必然会导致调用者与被依赖对象实现类的编码耦合,非常不利于项目的升级和维护。使用Spring框架之后,调用者无需主动获取被依赖对象,调用者只需要被动的接收Spring容器为调用者的成员变量赋值即可。

spring之依赖注入

 

核心配置文件中的定义:

    ref可以是标签,也可以是属性,用于为 Bean 的属性或构造器参数指定对 Bean 的引用

    <!--给属性赋值 相当于java的set方法-->
    <bean id="a" class="demo01.iocDemo.A">
        <property name="id" value="1"></property>
        <property name="name" value="张三"></property>
    </bean>
    <!--对象赋值-->
    <bean id="b" class="demo01.iocDemo.B">
        <property name="bname" value="李四"></property>
        <property name="a" ref="a"></property>
    </bean>

 

最后Java代码中就不需要主动去实例化依赖的对象,这个依赖的过程由Spring实现:

 ApplicationContext context = new ClassPathXmlApplicationContext("demo01/iocDemo/iocSpring.xml");
        A a =(A) context.getBean("a");
        System.out.println(a.getId());
        System.out.println(a.getName());

        B b =(B) context.getBean("b");
        System.out.println(b.getBname());
        System.out.println(b.getA().getName());

 

依赖注入的方式:

    Spring 支持 3 种依赖注入的方式,分别是:

    属性注入构造器注入工厂方法注入(很少使用,不推荐)

   

1.属性注入

设值注入是指IoC容器通过成员变量的setter方法来注入被依赖对象。这种注入方式简单、直观,因而在Spring的依赖注入里大量使用。通过setter方法注入Bean的属性值或者依赖的对象,属性注入使<property> 元素, 使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值 :

    <!--给属性赋值 相当于java的set方法-->
    <bean id="a" class="demo01.iocDemo.A">
        <property name="id" value="1"></property>
        <property name="name" value="张三"></property>
    </bean>

 

    2.构造器注入

利用构造器来设置依赖关系的方式,被称为构造注入。通俗来说,就是驱动Spring在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化——这就是构造注入的本质。

public class C {
    private String name;
    private String sex;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

 构造方法注入的传参方式有三种:

 

    1.按照索引匹配入参:

    <!-- 构造器注入 可以根据下标(index) 变量名(name) 类型(type="java.lang.String") 来进行赋值 -->
    <bean id="c" class="demo01.iocDemo.C">
        <constructor-arg index="0" value="王五"></constructor-arg>
        <constructor-arg index="1" value="男"></constructor-arg>
        <constructor-arg index="2" value="23"></constructor-arg>
    </bean>

 索引从0开始,表示构造器参数列表中的顺序,这种方式的缺点也很明显:参数列表的顺序如果调整了,配置文件也需要跟着调整。

 

2.按类型匹配入参:

    <!-- 构造器注入 可以根据下标(index) 变量名(name) 类型(type="java.lang.String") 来进行赋值 -->
    <bean id="c" class="demo01.iocDemo.C">
        <constructor-arg type="java.lang.String" value="张三"></constructor-arg>
        <constructor-arg type="java.lang.String" value="女"></constructor-arg>
        <constructor-arg type="int" value="22"></constructor-arg>
    </bean>

  type的值是类型的全路径,这里constructor标签的顺序也表示参数列表的顺序,如果构造器中列表是这样的:

(String name ,String sex , int age),那么为name赋值的constructor标签应该放在第一位,sex第二,age第三。

 

    3.根据属性名入参:

    <!-- 构造器注入 可以根据下标(index) 变量名(name) 类型(type="java.lang.String") 来进行赋值 -->
    <bean id="c" class="demo01.iocDemo.C">
        <constructor-arg name="name" value="王五"></constructor-arg>
        <constructor-arg name="sex" value="男"></constructor-arg>
        <constructor-arg name="age" value="25"></constructor-arg>
    </bean>

    这种方式是最稳的了 

 

 

两种注入方式的对比

设值注入有如下优点:

  1. 与传统的JavaBean的写法更相似,程序开发人员更容易理解、接受。通过setter方法设定依赖关系显得更加直观、自然。
  2. 对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
  3. 尤其在某些成员变量可选的情况下,多参数的构造器更加笨重。

构造注入优势如下:

  1. 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。
  2. 对于依赖关系无需变化的Bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏。
  3. 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

注意:

    建议采用设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入;而其他依赖关系的注入,则考虑采用设值注入。

 

自动装配

    Spring IOC 容器可以自动装配 Bean,需要做的仅仅是在 <bean> 的 autowire 属性里指定自动装配的模式,它能够将当前实例中所有的对象属性全部进行填充。

    <!--byName根据名称来进行装配 byType根据类型进行装配 -->
    <bean id="a" class="demo01.autowire.A">
        <property name="name" value="李四"></property>
    </bean>

    <!--<bean id="b" class="demo01.autowire.B" autowire="byName"></bean>-->
    <bean id="b" class="demo01.autowire.B" autowire="byType"></bean>

 

byType是根据类型自动装配,但要求是依赖Bean在容器中只能有一个实例。若IoC容器中有多个与目标Bean类型一致的Bean,容器将无法判断哪个Bean最适合该属性,会报错。

    <bean id="a" class="demo01.autowire.A">
        <property name="name" value="李四"></property>
    </bean>

    <bean id="aa" class="demo01.autowire.A">
        <property name="name" value="李四"></property>
    </bean>
    <bean id="b" class="demo01.autowire.B" autowire="byType"></bean>

 

 byName是根据名称自动装配,要求是依赖的Bean的名称和属性名设置的完全相同。这个属性名指的是Bean中定义的字段名 

public class B {
	private A a;
	private String name;

	public A getA() {
		return a;
	}

	public void setA(A a) {
		this.a = a;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

在XML中对应的依赖Bean的id值,也得是a:

    <bean id="a" class="demo01.autowire.A">
        <property name="name" value="李四"></property>
    </bean>

    <bean id="aa" class="demo01.autowire.A">
        <property name="name" value="王五"></property>
    </bean>
    <bean id="b" class="demo01.autowire.B" autowire="byName"></bean>

这时候取出来的Bean,就是a了:

 ApplicationContext context = new ClassPathXmlApplicationContext("demo01/autowire/autowireSpring.xml");
        B b =(B) context.getBean("b");
        System.out.println(b.getA().getName());

 spring之依赖注入

 

自动装配的缺点:

1.在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性,然而, 若只希望装配个别属性时,autowire 属性就不够灵活了。

2.autowire 属性要么根据类型自动装配,要么根据名称自动装配, 不能两者兼而有之。

3.一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些

 

Bean的作用域

在 Spring 中, 可以在 <bean> 元素的 scope 属性里设置 Bean 的作用域。

默认情况下, Spring 只为每个在 IOC 容器里声明的 Bean 创建唯一一个实例,整个 IOC 容器范围内都能共享该实例:所有后续的 getBean() 调用和 Bean 引用都将返回这个唯一的 Bean 实例,该作用域被称为 singleton,它是所有 Bean 的默认作用域。

spring之依赖注入

验证一下:

    XML中定义了一个A类

	<!-- spring中bean默认的都是单实例
		 singleton 单例 默认值   容器初始化时实例化
		 prototype 多例   getBean对象 实例化-->
	<bean id="a" class="demo01.prototype.A" scope="singleton"></bean>
ApplicationContext context =new ClassPathXmlApplicationContext("demo01/prototype/prototypeSpring.xml");
		A a =(A)context.getBean("a");
		A a1 =(A)context.getBean("a");
		System.out.println(a==a1);

结果是true,说明在Spring容器中只声明过一次的Bean默认只会产生一个实例,因为scope属性默认就是singleton。换成prototype则是每次都返回一个新的实例,这里不贴例子了。

 

singleton是在容器启动的时候初始化,而prototype则是每次新建实例(getBean)的时候创建。