Spring-依赖注入

来源:互联网 发布:免费舆情采集软件 编辑:程序博客网 时间:2024/05/13 10:30

  • 概述
  • 属性注入
    • 属性注入实例
      • 代码演示
    • JavaBean关于属性命名的特殊规范
  • 构造函数注入
    • 按类型匹配入参
    • 按索引匹配入参
    • 联合使用类型和索引匹配入参
    • 通过自身反射类型匹配入参
    • 循环依赖问题
  • 工厂方法注入
  • 选择注入方式的考量

概述

Spring支持两种依赖注入的方式

  • 属性注入
  • 构造函数注入

此外Spring还支持工厂方法注入。 这篇博文我们将了解到不同注入方式的具体配置方法。


属性注入

属性注入指的是通过setXxx()方法注入Bean的属性值或者依赖对象。

由于属性注入方式具有可选择性和灵活性高的有点,因此属性注入是实际应用中最常用的注入方式。


属性注入实例

属性注入的要求

  1. 提供一个默认的构造函数
  2. 为需要注入的属性提供对应的Setter方法

Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射调用Setter方法注入属性值。

代码演示

这里写图片描述

POJO对象

package com.xgj.ioc.inject.set;public class Plane {    private String brand;    private String color;    private int speed;    /**     * 在没有其他显示构造函数的情况下,默认构造函数可省略,有则必须声明     */    public Plane(){    }    public Plane(String brand,String color,int speed){        this.brand = brand;        this.color = color;        this.speed = speed;    }    public void setBrand(String brand) {        this.brand = brand;    }    public void setColor(String color) {        this.color = color;    }    public void setSpeed(int speed) {        this.speed = speed;    }    public void introduce() {        System.out.println("Plane brand:" + brand + ",color:" + color                + ",speed," + speed);    }}

Bean配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="plane" class="com.xgj.ioc.inject.set.Plane">        <property name="brand">            <value>A380</value>        </property>        <property name="color">            <value>red</value>        </property>        <property name="speed">            <value>700</value>        </property>    </bean></beans>

测试类:

package com.xgj.ioc.inject.set;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SetTest {    public static void main(String[] args) {        // 加载配置文件,实例化bean        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/set/plane.xml");        // 从容器中获取bean        Plane plane = ctx.getBean("plane",Plane.class);        // 调用方法        plane.introduce();    }}

结果:
这里写图片描述

解读:

上面的代码配置了一个Bean,并为该Bean的3个属性提供了属性值, 具体来说Bean的每一个属性对应一个property标签,name为属性的名称(上述配置较为繁琐,后续介绍p的用法),在Bean实现类中拥有与其对应的Setter方法: 比如 brand对应setBrand()。

有一点需要注意: spring只会检查Bean中是否有对应的Setter方法,至于Bean中是否有对应的属性成员变更则不做要求。
举个例子:
配置文件中<property name="brand">的属性配置项仅要求Plane中拥有setBrand()方法,但Plane类中不一定要拥有brand成员变量。

比如

private String color;private int speed;// 仅拥有setBrand方法,但是类中没有brand成员变量public void setBrand(String brand) {        ......    }

但一般情况下,还是按照约定俗成的方式在Bean中提供同名的属性变量


注意:
默认构造函数是不带参数的构造函数。 Java语言规定,如果类中没有定义任何构造函数,JVM会自动为其生成一个默认的构造函数;反之,如果类中显式的定义了构造函数,JVM则不会为其生成默认的构造函数。如果类中显式的声明了其他构造函数,如果未提供一个默认的构造函数,则属性注入时,会抛出异常。

举例:
我们在Plane中增加一个显式构造函数,去掉默认的构造函数

public Plane(String brand,String color,int speed){        this.brand = brand;        this.color = color;        this.speed = speed;    }

运行测试类,

Failed to instantiate [com.xgj.ioc.inject.set.Plane]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.xgj.ioc.inject.set.Plane.<init>()

这里写图片描述


JavaBean关于属性命名的特殊规范

Spring配置文件中property元素所指定的属性名和Bean实现类的Setter方法满足Sun JavaBean的属性命名规范: xxx的属性对应setXxx()方法.

一般情况下,Java的属性变量名都以小写字母开头,比如brand,speed等,但也有些特殊情况的存在。考虑到一些特定意义的大写英文缩略字母(比如USA、XML等),Javabean也允许以大写字母开头的属性变量名,不过必须满足 变量的前两个字母要么全部大写,要么全部小写。

比如brand,IDCode、IC等是合法的,而iC、iDCard等是非法的。

比如我们在Plane类中添加属性和setter方法

// 非法的属性变量名,但是Java并不会报错,因为它将iDCard看做普通的变量    private String iDCard;    // 改setter方法对应IDCard属性,而非iDCard属性    public void setIDCard(String iDCard) {        this.iDCard = iDCard;    }

因为xxx的属性对应setXxx()方法. 所以是setIDCard().

bean配置文件

这里写图片描述

STS中校验都未通过,更加无法运行了。

总结:
以大写字母开头的变量总是显得比较另类,为了规避这种诡异的错误,用户可遵照以下编程的经验:比如QQ、MSN、ID等正常情况下以大写字母出现的专业术语,在Java中一律调整为小写形式,如qq、msn、id等,以保证命名的统一性(变量名称都小写字母开头),减少出错概率。


构造函数注入

构造函数注入是除了属性注入之外另外一种常用的注入方式,构造函数注入保证一些必要的属性在Bean实例化的时候得到设置,确保Bean在实例化之后就可以使用


按类型匹配入参

举个例子,假设任何使用Tank对象都必须提供brand和weight,若使用属性注入 方式,这只能人为在配置时候提供保证而无法再语法级提供保证,这时候构造函数注入就可以很好地满足这一个需求。

使用构造函数的前提是Bean必须提供带参的构造函数。

代码演示:

这里写图片描述

Pojo Bean实现类

package com.xgj.ioc.inject.construct;public class Tank {    private String brand;    private double weight;    public Tank(String brand, double weight) {        super();        this.brand = brand;        this.weight = weight;    }    public void introduce() {        System.out.println("Tank information: brand:" + brand + ",weight:"                + weight + "KG");    }}

配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="tank" class="com.xgj.ioc.inject.construct.Tank">        <constructor-arg type="java.lang.String">            <value>T72</value>        </constructor-arg>        <constructor-arg type="double">            <value>15000.00</value>        </constructor-arg>    </bean></beans>

测试类:

package com.xgj.ioc.inject.construct;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {    public static void main(String[] args) {        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/tank.xml");        Tank tank = ctx.getBean("tank", Tank.class);        tank.introduce();    }}

运行结果:

这里写图片描述

解读:

在constructor-arg的元素中有个type属性,它为Spring提供了判断配置项和构造函数入参对应关系的信息。

一个疑问

  • 配置文件中constructor-arg声明顺序难道不能用于确定构造函数入参的顺序吗?—在只有一个构造函数的情况下当然是可以的,如果类中定义了多个具有相同入参的构造函数,这种顺序标识就失效了。

另外,Spring的配置文件采用和元素标签顺序无关的策略,一定程度上保证了配置信息的确定性,避免一些似是而非的问题。

    <constructor-arg type="java.lang.String">            <value>T72</value>        </constructor-arg>        <constructor-arg type="double">            <value>15000.00</value>        </constructor-arg>

这两个参数的位置并不会影响对最终的配置效果产生影响。


按索引匹配入参

众所周知,Java语言通过入参的类型和顺序区分不同的重载方法。

如果Tank类中有两个相同类型的入参,仅仅通过type就无法确定的对应关系了。这是需要通过入参索引的方式进行确定。

这里写图片描述

POJO对象

package com.xgj.ioc.inject.construct.index;public class Tank {    private String brand;    private double weight;    private double speed;    /**     *      * @param brand     * @param weight     * @param speed     * 第二个参数和第三个参数同为double类型     */    public Tank(String brand, double weight, double speed) {        this.brand = brand;        this.weight = weight;        this.speed = speed;    }    public void introduce() {        System.out.println("Tank information: brand:" + brand + ",weight:"                + weight + "KG,speed:" + speed + "km/h");    }}

第二个参数和第三个参数同为double类型,所以Spring无法确定type为double到底对应constructor-arg中的哪个,但是通过显示指定参数的索引能消除这种不确定性。

配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="tank" class="com.xgj.ioc.inject.construct.index.Tank">        <constructor-arg index="0" value="T72" />        <constructor-arg index="1" value="20000" />        <constructor-arg index="2" value="300" />    </bean></beans>

构造函数的一个参数索引为0,第二个为1,以此类推

测试类

package com.xgj.ioc.inject.construct.index;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {    public static void main(String[] args) {        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/index/tank_index_match.xml");        Tank tank = ctx.getBean("tank", Tank.class);        tank.introduce();    }}

运行结果:

这里写图片描述


联合使用类型和索引匹配入参

有时候需要type和 index联合使用才能确定配置项和构造函数入参的对应关系。

这里写图片描述

POJO 对象

package com.xgj.ioc.inject.construct.joint;public class Tank {    private String brand;    private double weight;    private double speed;    // 载人数量    private int manned;    /**     *      * @param brand     * @param weight     * @param speed     * 第二个参数和第三个参数同为double类型     */    public Tank(String brand, double weight, double speed) {        this.brand = brand;        this.weight = weight;        this.speed = speed;    }    /**     *      * @param brand     * @param weight     * @param manned     */    public Tank(String brand, double weight,int manned){        this.brand =brand;        this.weight = weight;        this.manned = manned;    }    public void introduce() {        System.out.println("Tank information: brand:" + brand + ",weight:"                + weight + "KG,speed:" + speed + "km/h");    }    public void introduce2() {        System.out.println("Tank information: brand:" + brand + ",weight:"                + weight + "KG,manned:" + manned + "/person");    }}

配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="tank" class="com.xgj.ioc.inject.construct.joint.Tank">        <constructor-arg type="java.lang.String" index="0" value="T72" />        <constructor-arg type="double" index="1" value="25000" />        <constructor-arg type="int" index="2" value="3" />    </bean></beans>

如果仅仅根据index来配置,Spring无法知道第三个入参配置的类型究竟是int还是double ,因此需要明确指出第三个入参的类型以消除歧义。
事实上,constructor-arg中前两个的type属性可以去掉、

当然了,也可以直接用type来判断。

测试类

package com.xgj.ioc.inject.construct.joint;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {    public static void main(String[] args) {        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/joint/tank_joint_match.xml");        Tank tank = ctx.getBean("tank", Tank.class);        tank.introduce2();    }}

运行结果:
这里写图片描述


通过自身反射类型匹配入参

如果Bean构造函数入参的类型是可辩别的(非基础数据类型且入参类型各不相同),由于Java反射机制可以获取构造函数的入参类型,即使构造函数注入的配置不提供类型和索引的信息,Spring依然可以正确的完成构造函数的注入工作。

例子:

这里写图片描述

POJO类-Tank

package com.xgj.ioc.inject.construct.reflect;public class Tank {    public void attack() {        System.out.println("tank begins to attack");    }}

POJO类-Plane

package com.xgj.ioc.inject.construct.reflect;public class Plane {    public void attack(){        System.out.println("plane begins to attack");    }}

配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="plane" class="com.xgj.ioc.inject.construct.reflect.Plane"/>    <bean id="tank" class="com.xgj.ioc.inject.construct.reflect.Tank"/>    <bean id="commander" class="com.xgj.ioc.inject.construct.reflect.Commander">    <!-- 没有设置type和index属性,通过入参值的类型完成映射匹配 -->        <constructor-arg value="XGJ"/>        <constructor-arg ref="plane"/>        <constructor-arg ref="tank"/>    </bean></beans>

解析:

由于plane tank name入参的类型都是可辨别的,所以无需再构造函数注入的配置时指定constructor-arg的type和index,因此可以采用如上的简易配置方式

测试类

package com.xgj.ioc.inject.construct.reflect;public class Commander {    private Plane plane;    private Tank tank;    private String name;    public Commander(Plane plane, Tank tank, String name) {        super();        this.plane = plane;        this.tank = tank;        this.name = name;    }    public void direct(){        System.out.println("Commamder name:" + name);        System.out.println("Commnader begins to direct the army");        plane.attack();        tank.attack();    }}
package com.xgj.ioc.inject.construct.reflect;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {    public static void main(String[] args) {        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/reflect/beans.xml");        Commander commander = ctx.getBean("commander", Commander.class);        commander.direct();    }}

运行结果

这里写图片描述


循环依赖问题

Spring容器能够对构造函数配置的Bean进行实例化有一个前提:Bean构造函数入参所引用的对象必须已经准备就绪。

鉴于这个机制,如果两个Bean都采用构造函数注入,并且都通过构造函数入参引用对方,就会发生类属于线程死锁的的循环依赖问题。

举个例子说明一下(飞行员和飞机):

这里写图片描述

package com.xgj.ioc.inject.construct.loop;public class Pilot {    private String pilotNname;    private Plane plane;    public Pilot(String pilotNname, Plane plane) {        super();        this.pilotNname = pilotNname;        this.plane = plane;    }    public void drivePlane() {        plane.fly();    }}
package com.xgj.ioc.inject.construct.loop;public class Plane {    private Pilot pilot;    private String planeBrand;    public Plane(Pilot pilot, String planeBrand) {        super();        this.pilot = pilot;        this.planeBrand = planeBrand;    }    public void fly() {        System.out.println("Plane :" + planeBrand + " is reday to fly");    }}
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="pilot" class="com.xgj.ioc.inject.construct.loop.Pilot">        <constructor-arg ref="plane"/>        <constructor-arg value="F35"/>    </bean>    <bean id="plane" class="com.xgj.ioc.inject.construct.loop.Plane">        <constructor-arg ref="pilot"/>        <constructor-arg value="XGJ"/>    </bean></beans>
package com.xgj.ioc.inject.construct.loop;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest {    public static void main(String[] args) {        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/loop/beans.xml");        Pilot pilot = ctx.getBean("pilot", Pilot.class);        pilot.drivePlane();    }}

运行结果:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pilot' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'plane' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'plane' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'pilot' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'pilot': Requested bean is currently in creation: Is there an unresolvable circular reference?    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359).....................org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)    at com.xgj.ioc.inject.construct.loop.ConstructInjectTest.main(ConstructInjectTest.java:10)Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'plane' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'pilot' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'pilot': Requested bean is currently in creation: Is there an unresolvable circular reference?    at .......    .......    ... 29 more

如何解决呢? 构造函数注入修改为 属性注入即可。

如下所示:

只需要修改beans.xml即可

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="pilot" class="com.xgj.ioc.inject.construct.loopsolve.Pilot">        <property name="pilotNname" value="XGJ"></property>        <property name="plane" ref="plane"></property>    </bean>    <bean id="plane" class="com.xgj.ioc.inject.construct.loopsolve.Plane">        <property name="planeBrand" value="F35"></property>        <property name="pilot" ref="pilot"></property>    </bean></beans>

运行结果

这里写图片描述


工厂方法注入

分为非静态工厂方法和静态工厂方法。

对于一个全新开发的应用来说,我们不推荐使用工厂方法的注入方式。因为工厂方法需要额外的类和代码,这些功能和业务并无关系。


选择注入方式的考量

仁者见仁 智者见智,并无定论。在合适的场景下使用合适的注入方式。

原创粉丝点击