前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring-依赖注入

Spring-依赖注入

作者头像
小小工匠
发布2021-08-16 16:27:02
5240
发布2021-08-16 16:27:02
举报
文章被收录于专栏:小工匠聊架构

概述

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

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

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


属性注入

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

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


属性注入实例

属性注入的要求

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

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

代码演示

这里写图片描述
这里写图片描述

POJO对象

代码语言:javascript
复制
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配置文件

代码语言:javascript
复制
<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>A380value>
        property>

        <property name="color">
            <value>redvalue>
        property>

        <property name="speed">
            <value>700value>
        property>
    bean>
beans>

测试类:

代码语言:javascript
复制
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中是否有对应的属性成员变更则不做要求。 举个例子: 配置文件中的属性配置项仅要求Plane中拥有setBrand()方法,但Plane类中不一定要拥有brand成员变量。

比如

代码语言:javascript
复制
private String color;
private int speed;
// 仅拥有setBrand方法,但是类中没有brand成员变量
public void setBrand(String brand) {
        ......
    }

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


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

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

代码语言:javascript
复制
public Plane(String brand,String color,int speed){
        this.brand = brand;
        this.color = color;
        this.speed = speed;
    }

运行测试类,

代码语言:javascript
复制
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.()
这里写图片描述
这里写图片描述

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

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

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

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

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

代码语言:javascript
复制
// 非法的属性变量名,但是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实现类

代码语言:javascript
复制
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");
    }

}

配置文件

代码语言:javascript
复制
<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>T72value>
        constructor-arg>

        <constructor-arg type="double">
            <value>15000.00value>
        constructor-arg>

    bean>
beans>

测试类:

代码语言:javascript
复制
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的配置文件采用和元素标签顺序无关的策略,一定程度上保证了配置信息的确定性,避免一些似是而非的问题。

代码语言:javascript
复制
    <constructor-arg type="java.lang.String">
            <value>T72value>
        constructor-arg>

        <constructor-arg type="double">
            <value>15000.00value>
        constructor-arg>

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


按索引匹配入参

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

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

这里写图片描述
这里写图片描述

POJO对象

代码语言:javascript
复制
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中的哪个,但是通过显示指定参数的索引能消除这种不确定性。

配置文件

代码语言:javascript
复制
<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,以此类推

测试类

代码语言:javascript
复制
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 对象

代码语言:javascript
复制
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");
    }


}

配置文件

代码语言:javascript
复制
<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来判断。

测试类

代码语言:javascript
复制
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

代码语言:javascript
复制
package com.xgj.ioc.inject.construct.reflect;

public class Tank {

    public void attack() {
        System.out.println("tank begins to attack");
    }

}

POJO类-Plane

代码语言:javascript
复制
package com.xgj.ioc.inject.construct.reflect;

public class Plane {

    public void attack(){
        System.out.println("plane begins to attack");
    }

}

配置文件

代码语言:javascript
复制
<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">
    
        <constructor-arg value="XGJ"/>
        <constructor-arg ref="plane"/>
        <constructor-arg ref="tank"/>
    bean>

beans>

解析:

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

测试类

代码语言:javascript
复制
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();

    }

}
代码语言:javascript
复制
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都采用构造函数注入,并且都通过构造函数入参引用对方,就会发生类属于线程死锁的的循环依赖问题。

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

这里写图片描述
这里写图片描述
代码语言:javascript
复制
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();
    }

}
代码语言:javascript
复制
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");
    }
}
代码语言:javascript
复制
<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>
代码语言:javascript
复制
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();
    }

}

运行结果:

代码语言:javascript
复制
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.(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即可

代码语言:javascript
复制
<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>

运行结果

这里写图片描述
这里写图片描述

工厂方法注入

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

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


选择注入方式的考量

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017/07/16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 属性注入
    • 属性注入实例
      • 代码演示
    • JavaBean关于属性命名的特殊规范
    • 构造函数注入
      • 按类型匹配入参
        • 按索引匹配入参
          • 联合使用类型和索引匹配入参
            • 通过自身反射类型匹配入参
              • 循环依赖问题
              • 工厂方法注入
              • 选择注入方式的考量
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档