1、依赖注入简介

DI—Dependency Injection,即“依赖注入”组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。**依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。**通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器

●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的
。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

2、Spring依赖注入

上面讲解的只是DI依赖注入的设计思想,接下来我们看看Spring是如何实现依赖注入的

2.1、构造器注入

上篇文章中通过标签实现的实际上就是通过构造器方式注入详情参考上篇文章

2.2、Set方式注入

  • 依赖:bean对象的创建依赖于容器
  • 注入:bean对象中的所有属性,由容器来注入

Spring提供了多种类型的注入,以下将测试用例全部集合到一个类中方便演示

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="zhangsanWife" class="cc.leeleo.entity.Wife">
        <property name="name" value="翠花"/>
        <property name="age" value="18"/>
    </bean>
    <bean id="zhangsan" class="cc.leeleo.entity.User">
        <!--普通注入-->
        <property name="name" value="张三"/>
        <!--bean注入-->
        <property name="wife" ref="zhangsanWife"/>
        <!--数组注入-->
        <property name="girlFriends">
            <array>
                <value>翠花</value>
                <value>小妮</value>
                <value>大曼</value>
            </array>
        </property>
        <!--List注入-->
        <property name="hobbies">
            <list>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>看美女</value>
            </list>
        </property>
        <!--Map注入-->
        <property name="certificates">
            <map>
                <entry key="身份证" value="511602199901234567"/>
                <entry key="手机号" value="18888888888"/>
                <entry key="QQ号" value="666666"/>
                <entry key="微信号" value="18888888888"/>
            </map>
        </property>
        <!--Set注入-->
        <property name="cars">
            <set>
                <value>兰博基尼</value>
                <value>布加迪威龙</value>
                <value>法拉利</value>
            </set>
        </property>
        <!--空值注入-->
        <property name="house" value="null"/>
        <!--properties注入-->
        <property name="resume">
            <props>
                <prop key="单位">哔哩哔哩</prop>
                <prop key="职位">java工程师</prop>
                <prop key="工龄">1年</prop>
            </props>
        </property>
    </bean>
</beans>

User类和Wife类

@Data
public class User {
    private String name;
    private Wife wife;
    private String[] girlFriends;
    private List<String> hobbies;
    private Map<String,String> certificates;
    private Set<String> cars;
    private String house;
    private Properties resume;
}
@Data
public class Wife {
    private String name;
    private Integer age;
}

测试:

public class SpringApplicationStarter {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User bean = context.getBean(User.class);
        System.out.println(bean.toString());
    }
}

image-1659953853633

2.3、扩展方式注入

除了以上的基础的及复杂的注入方式意外,Spring官方还提供了两种扩展的属性注入方式

  1. 添加命名空间

    标签中加入以下两个属性xmlns:p xmlns:c

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    </beans>
    
  2. 使用p:属性,等同于<**property>** 标签

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--p:顾名思义我猜是property的缩写-->
        <bean id="xiaoni" class="cc.leeleo.entity.Wife" p:name="小妮" p:age="18" />
    
  3. 使用c:属性,等同于<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"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--c:顾名思义我猜是constructor的缩写-->
        <bean id="daman" class="cc.leeleo.entity.Wife" c:name="大曼" c:age="18"/>
    </beans>
    
  4. 也可以同时使用cp属性

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--p:顾名思义我猜是property的缩写-->
        <bean id="xiaoni" class="cc.leeleo.entity.Wife" c:name="afa" c:age="123" p:name2="小妮" p:age2="18"></bean>
    </beans>
    

    Wife类:

    @Data
    public class Wife {
        private String name;
        private Integer age;
        private String name2;
        private Integer age2;
    
        public Wife(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }
    

    测试结果:

image-1659953871917

2.4、Bean的作用域

image-1659953888273

以上是Bean的几个作用域,目前我们涉及到的bean暂时只有prototype和singleton这俩作用域,其他的几个都是在引入SpringMVC后可能会使用到的作用域。

  • 单例模式(spring默认机制)

    <bean id="xiaoni" class="cc.leeleo.entity.Wife" p:name="小妮" p:age="18" scope="singleton"></bean>
    

    测试结果:

image-1659953954653

  • 原型模式:每次从容器中get时,都会产生一个新的对象

    <bean id="xiaoni" class="cc.leeleo.entity.Wife" p:name="小妮" p:age="18" scope="prototype"></bean>
    

    测试结果:

image-1659953904796

3、自动装配

以上演示的这些例子全都是手动来实现一个bean的装配的,需要手动一个个去写标签,手动去指定需要注入的属性,功能不复杂实现起来还是很简单的,但是在日后的开发中日积月累,bean的数量肯定会越来越多,如果通过这样的方式再去一个个手动的装配bean,那工作量还是巨大的,于是乎,Spring提供了更方便的自动装配方式,下面我们一起来学习Bean的自动装配。

Spring中提供了三种装配的方式:

  1. 在xml中显式的配置
  2. 在java中显式的配置
  3. 隐式的自动装配

3.1、byName的装配方式

这里先列出示例代码:

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="son" class="cc.leeleo.entity.Son">
        <property name="name" value="erzi"/>
    </bean>
    <bean id="father" class="cc.leeleo.entity.Father">
        <property name="name" value="baba" />
        <property name="son" ref="son" />
    </bean>
</beans>

一个Father类和一个Son类

public class Father {
    private String name;
    private Son son;

    public void saySon(){
        System.out.println(this.name+"说:erzi");
    }

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

    public void setSon(Son son) {
        this.son = son;
    }

		public Son getSon() {
        return son;
    }
}
public class Son {
    private String name;

    public void sayDaddy(){
        System.out.println(this.name+"说:baba");
    }

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

}

好了 重点来了,在beans.xml中我们可以看到在注入son这个对象的时候,我们需要去指定id为son的bean对应到father的son属性上,这一步可以通过Spring提供的auto wired属性来省去这一步。

image-1659953977839

我们看到bean标签的autowire的属性中包含五个值,顾名思义,可以是通过名称(bean的id)、通过类型(bean的clas)以及构造器等方式来实现自动装配。

首先我们在bean中加入该属性**autowire="byName"** ,并删除以下值

~~<**property name~="son" ref="son"** />~~

完整代码如下:

<?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="son" class="cc.leeleo.entity.Son">
        <property name="name" value="erzi"/>
    </bean>
    <bean id="father" class="cc.leeleo.entity.Father" autowire="byName">
        <property name="name" value="baba" />
    </bean>
</beans>

接下来我们测试一下

image-1659953992024

可以看到由于我们定义的Father类中的Son的set方法名称setSon和beans中定义的bean id = “son”一致,所以装配成功,我们再试下更改bean的id

image-1659953999451

此时我们发现,由于spring找不到装配时所需的set方法,此时为了验证其到底是不是根据set方法装配,我们将setSon方法更改为setSon1再来看下

image-1659954008641

此时便验证了我们之前所说,使用byName方式Spring会

根据需要装配的bean的set方法后的id值,去查找上下文中包含该id的bean进行装配

需要注意的是 使用byName的方式需要保证装配bean的id是唯一的,并且这个bean需要和注入属性的set方法名的值一致

3.2、byType的装配方式

这里我们更改**autowire="byType" ,**再试一下,使用此种方式可以将bean中的id属性干掉不用

image-1659954030425

由此得知,使用byType方式

Spring会在上下文中,根据需要装配的对象类型,在上下文中查找对应类型的bean进行装配

需要注意的是 使用byType的方式需要保证装配的bean的类上下文中是唯一的

3.3、使用注解的装配方式

在使用了上面两种方式进行装配以后,虽然是便利了些,但是还是差点意思,Spring 还提供了一种更爽的配置方式,注解!

jdk1.5支持的注解,Spring2.5就支持注解了使用注解须知:

  1. 导入约束:context约束
  2. 配置注解的支持:context:annotation-config/

image-1659954040367

完整示例:

beans.xml 这里我们添加上面的三个步骤,并且添加两个bean,这里细心的小伙伴们可能就发现了,咱们bean里面只有一个class属性,id和autowire都干掉了,代码又干净了不少。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解支持-->
    <context:annotation-config/>

		<bean class="cc.leeleo.entity.Son">
        <property name="name" value="erzi"/>
    </bean>
    <bean class="cc.leeleo.entity.Father">
        <property name="name" value="baba"/>
    </bean>
</beans>

Father,这里我们把setSon的方法干掉!并且在son属性上加上@Autowired注解

public class Father {
    private String name;

    @Autowired
    private Son son;

    public void saySon(){
        System.out.println(this.name+"说:erzi");
    }

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

    public Son getSon() {
        return son;
    }
}

上面的代码是不是又简洁了不少?接下来我们再测试下

image-1659954052132

成功执行~

@Autowired 可以在属性上使用 也可以在set方法上使用,使用该方式可以省略set方法

3.3.1、Qualifier 注解

跟@Autowired一起使用的注解还有@Qualifier 注解,它的使用场景是,当一个spring的容器中存在多个相同类型的bean,这里就可以指定你使用哪个bean作为注入的对象

还是上面的代码,这里我们更改一下Fater类,增加@Qualifier注解,指定注入的bean为son2

public class Father {
    private String name;

    @Autowired
    @Qualifier(value = "son2")
    private Son son;

    public void saySon(){
        System.out.println(this.name+"说:erzi");
    }

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

    public Son getSon() {
        return son;
    }
}

还有beans.xml,新增一个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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解支持-->
    <context:annotation-config/>

    <bean class="cc.leeleo.entity.Son">
        <property name="name" value="erzi"/>
    </bean>
    <bean id="son2" class="cc.leeleo.entity.Son">
        <property name="name" value="xiaoerzi"/>
    </bean>
    <bean class="cc.leeleo.entity.Father">
        <property name="name" value="baba"/>
    </bean>
</beans>

测试:

Untitled

需要注意的是,@Qualifier 的value值指定的是Spring上下文中存在的bean的id

3.3.2、@Resource注解

除了Spring提供的@Autowired注解以外,java也提供了一个@Resource注解能实现相同的功能,这里我们废话不多说 上代码:

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解支持-->
    <context:annotation-config/>

    <bean class="cc.leeleo.entity.Son">
        <property name="name" value="erzi"/>
    </bean>
    <bean class="cc.leeleo.entity.Father">
        <property name="name" value="baba"/>
    </bean>
</beans>

Father.java 这里我们使用@Resource注解

public class Father {
    private String name;

    @Resource
    private Son son;

    public void saySon(){
        System.out.println(this.name+"说:erzi");
    }

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

    public Son getSon() {
        return son;
    }
}

测试:

image-1659954079015

如果有多个相同类型的bean,我们也可以通过@Resource(name = “xxx”)来指定所需的bean,

上代码:

Father.java 修改注解,指定name值为xml中bean的id

@Resource(name = "son2")
private Son son;

beans.xml 新增一个bean

<bean id="son2" class="cc.leeleo.entity.Son">
    <property name="name" value="xiaoerzi"/>
</bean>

测试:

image-1659954091676

依旧没有问题~

  • @Autowired@Resource区别:
  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired通过byType实现,必须要求这个对象存在
  • @Resoutce默认通过byName方式实现,如果找不到名字,则通过byType实现
  • 执行顺序不同:@Autowired通过byType实现,@Resoutce通过byName实现