今天我们来看一下spring的第二大特性,依赖注入,英文名称“Dependency Injecttion”,简称DI。在上一篇博客中我们讨论了IOC的作用就是降低程序之间的耦合性。简单说依赖注入就是给系统中的某个变量赋值。
那问题来了,那这种依赖关系由谁来管理那?毫无疑问,肯定是交给我们的spring容器了,我们只需在配置文件中说明即可。这种依赖关系的维护就称之为依赖的注入。
接下来我们 创建一个maven项目,导入spring的jar包。完成spring的基本开发环境。
jar包如下:

<!--spring依赖-->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
    </dependencies>

这里说明一下,为什么一定要用maven来导入jar包那。看下图:

spring依赖的版本是红色的有影响吗 spring依赖关系_spring


打开"External Libraries",发现下面有好多子包,其中我们发现有我们需要的sprign-context这个jar包。这就对了。这就是maven的强大之处。在今天的案例当中,我们只能用到spring-context这个jar包。如果我们要做一个完整的项目,上面的这些jar包是最基本的spring环境,如果我们手动的去导包,那多麻烦,还要面临版本兼容的问题。而利用maven来管理jar包的话,他会把我们与spring开发相关的jar都导进来。这就是他的好处。不会存在版本的兼容问题。推荐大家以后使用maven来管理jar包。

还是转入正题,看看spring的如何注入数据的?

spring注入的数据类型有哪些?
1. 基本数据类型和String
2. 其他bean类型(在配置文件中或注解中配置过的bean)
3. 复杂类型/集合类型
spring的注入方式有哪些?
1. 使用构造器来注入
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
	type:用于指定要注入的数据类型,该数据类型也是构造函数中某个或某些参数的数据类型
	index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置是从0开始
	name:用于指定给构造函数中指定名称的参数赋值
	==============以上三个用于指定给构造函数中的那个参数赋值==================
	value:用于指定给构造函数和String类型的数据注入
	ref:用于指定其他bean类型的数据,他指的就是在spring的IOC核心容器中出现过的bean对象。
优点:
	在获取bean对象时,注入的数据是必须的操作,否则对象无法创建。
缺点:
	改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据也必须提供

代码如下:
备注:这里展示的是部分代码:只展示spring的配置文件和要交给spring容器管理的类。

import java.util.Date;
public class EntityServiceImpl  {
    private Integer age;     //基本类型的包装类
    private String name;   //String类型
    private Date birthday;  //其他类型
    
    public EntityServiceImpl(Integer age, String name, Date birthday){
        this.age = age;
        this.name = name;
        this.birthday = birthday;
    }
	//测试方法
    public void print(){
        System.out.println("name: "+name+"  age: "+age+"   birthday:  "+birthday);
    }
}

配置文件:

<bean id="EntityService1" class="jdbc.service.EntityServiceImpl">
        <constructor-arg type="java.lang.Integer" index="0" name="age" value="19"></constructor-arg>
        <constructor-arg name="name" value="zyy"></constructor-arg>
        
        <!-- 这个now就是bean对象的其他类型,要想使用,就必须现在spring中配置他(或者也叫实例化)-->
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>

其实,我们可以看出,type和index就可以唯一确定一个构造函数中参数的位置,所以,在没有必要写name属性了。一般情况下使用name属性来进行注入。
很明显,这种方式是有前提条件的。就是我们要在类EntityServiceImpl 中有一个指定参数的构造函数。此例中的构造函数有三个参数,所以,在使用构造器进行注入的时候必须也是三个。否则就会报错。这就是我们总结出来的缺点,有时候,有些数据在实例化的时候我们并不用,但是没办法,即使不用,也要注入,这样,才能保证不会出错。

2. 使用set方法来注入
使用的标签:property
出现的位置:bean标签的内部
标签的属性:
	name: 用于指定注入时所调用的set方法名称(其实就是属性名)
	value:用于提供提供基本数据类型和String类型的数据
	ref:用于指定其他bean 类型的数据,他指的就是在spring的IOC容器中出现过的bean对象
优点:
	创建对象时没有明确的限制,可以直接使用默认的构造函数
缺点:
	如果某个成员必须有值,则获取对象时有可能set方法没有执行

代码如下:

import java.util.Date;
public class EntityServiceImpl  {

    private Integer age;
    private String name;
    private Date birthday;
    
    //默认构造函数必须存在(如果自己创建了构造器,默认的构造器将不会存在)
    public EntityServiceImpl(){}
    
    //有参构造器(使用set方法注入时,可以去掉有参构造器)
    public EntityServiceImpl(Integer age, String name, Date birthday){
        this.age = age;
        this.name = name;
        this.birthday = birthday;
    }
    
    public void setAge(Integer age) {
        this.age = age;
    }

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

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    //测试方法
    public void print(){
        System.out.println("name: "+name+"  age: "+age+"   birthday:  "+birthday);
    }
}

spring 配置文件如下:

<bean id="EntityService2" class="jdbc.service.EntityServiceImpl">
        <property name="age" value="22"></property>
        <property name="name" value="yyl"></property>
        
        <!--  要使用now,必须现在spring的IOC容器中注入这个bean类型 -->
        <property name="birthday" ref="now"></property>
    </bean>

小结一下:set方式来注入数据。第一:我们必须给类中的成员变量提供标准的set方法,第二,类中默认的构造方法必须有。

3. 使用注解注入(请看下篇)

spring框架的注解配置应该是现在比较流行的配置方式,我们一定要掌握。其好处多多。什么简化配置,方便使用,提高开发效率等等一系列的好看、好听、中用的词汇都适合用来修饰spring使用注解带来的好处。但是,有人就会问,既然注解这么多好处,我们为什么还要学基于XML注入的配置。原因很简单,不入虎穴,焉得虎子。就是这个道理,你不了解spring的过去,怎么可能掌握未来。好了,接下来我们看一下spring的注解如何初始化bean对象和注入数据的。
在看注解之前,我们先在复习一下曾经的XML配置(XML曾经也牛掰过):

<bean id="userService" class="com.tff.demo.service.impl.UserServiceImpl"
scope="" destroy-method="" init-method="" >
         <property name="" ref=""| value=""></property>
</bean>
bean: 标签就是用于实例化对象的
id  : 用来表示一个一个唯一的bean对象
class:用来表示这个对象的全路径
scope:用来表示这个bean对象的作用范围
destroy-method:表示bean对象的销毁方法
init-method:用来表示对象的初始化方法
property:用来给这个类的属性注入数据

我们再来看一下了Spring注解是如何完成的。

spring的注解基本上可以分为四类
  • 用于创建对象的
    他们的作用就和XML文件中的bean标签实现的功能一样
    @Component
    作用: 用于把当前对象存入spring容器当中
    @Controller
    @Service
    @Repository
    属性(这四个注解的属性一致):
    value:用于指定bean的id,相当于我们用XML配置时的id属性,可以忽略不写, 当我们不写时,他的默认值是当前的类名(类名的首字母小写)。
    小结:以上三个注解的作用与属性和Component是一样的,只是spring框架为我们提供明确的三层使用的注解,使三层模式更清晰
  • 用于注入数据的
    他们的作用就和在XML配置文件中的bean标签中的property标签的作用是一样的
    @Autowired:
    作用:自动化装配,自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型相匹配时,就可以注入成功。如果没有,就会注入失败。
    如果IOC容器中有多个类型匹配时:解决办法如下:
  • 1.使用Qualifier注解
    作用:在按照类型注入的基础上再按照名称注入,它在给类成员注入时不能单独使用,必须和Autowired注解联合使用,但是在给方法注入时可以单独使用。
    属性:value:用于指定注入的bean的id。
  • 2.Resource注解:
    作用:直接按照bean的id注入,他可以独立使用
    属性:
    name :用于指定bean的id。

备注:以上三个注入都只能注入bean类型的数据,而基本数据类型和String类型都无法使用上述注入方式。另外,集合类的注入只能通过XML来实现。

  • 用于改变作用范围的
    他们的作用和在bean标签中的scope属性是一样的
    @Scope:
    属性值:
    singleton: 单例对象,默认的就是单例对象
    prototype: 多例对象
  • 和生命周期有关的
    他们的作用就和在bean标签中使用init-method和destory-method作用是一样的。
    @PostConstruct
    @PreDestory
    代码如下:
    dao层接口和实现类:
package com.tff.demo.dao;
public interface EntityDao {
    void saveEntity();
    void insertEntity();
    void updateEntity();
    void deleteEntity();
}
package com.tff.demo.dao.impl;
import com.tff.demo.dao.EntityDao;
import org.springframework.stereotype.Repository;
//@Repository(value="dao")
@Repository
public class EntityDaoImpl implements EntityDao {
    public void saveEntity() {
        System.out.println("dao层的添加方法");
    }
    
    public void insertEntity() {
        System.out.println("dao层的插入方法");
    }
    
    public void updateEntity() {
        System.out.println("dao层的修改方法");
    }
    
    public void deleteEntity() {
        System.out.println("dao层的删除方法");
    }
    
}

Service 层接口和实现类

package com.tff.demo.service;
public interface EntityService {
    void saveEntity();
    void insertEntity();
    void updateEntity();
    void deleteEntity();
}
package com.tff.demo.service.impl;
import com.tff.demo.dao.EntityDao;
import com.tff.demo.service.EntityService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Service(value="service")
@Service
@Scope("singleton")
public class EntityServiceImpl implements EntityService {

   /* @Autowired
    @Qualifier(value = "bean的id也就是实例化对象注解的属性的value值")*/
   @Resource(name = "dao")
    private EntityDao entityDao;

    public void saveEntity() {
       entityDao.saveEntity();
    }

    public void insertEntity() {
     entityDao.insertEntity();
    }

    public void updateEntity() {
       entityDao.updateEntity();
    }

    public void deleteEntity() {
        entityDao.deleteEntity();
    }

    @PostConstruct
    public void init(){
        System.out.println("模拟初始化方法");
    }
    
    @PreDestroy
    public void destory(){
        System.out.println("模拟销毁方法");
    }
    
}

测试类:

package com.tff.demo.test;
import com.tff.demo.service.EntityService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestMethod {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        EntityService entityService = ac.getBean("entityServiceImpl",EntityService.class);
        entityService.saveEntity();
        
    }

}

还有最后一步:
就是大家可以看到,我们main函数中依然加载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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx">
    <!--
        告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean的约束中
        ,而是一个名称为context名称空间和约束中
    -->
    <context:component-scan base-package="com.tff.demo"></context:component-scan>
</beans>

我相信上面的注释已经说的很秦楚了。虽然我们已经在要交给spring容器帮我们实例化的类上面加了相应的注解,但是,spring并不知道在那个包下面去扫描这些类。所以,只欠我们告诉spring你应该去哪里找就OK了。

还有一个问题需要说明一下,就是前面提到的,如果IOC容器中有多个类型与bean对象匹配时,我们有两种解决方案:

第一种:两个注解联合使用(@Autowired+@Qualifier),按照先类型在id的匹配方式诸如数据。
第二种:一个注解解决问题(@Resource),按照指定的id进行注入

但是,问题来了,IOC容器中多个类型bean对象匹配,那怎么样才能使这样的情况发生那?那就是多个类实现了同一个接口或者多个类集成了同一个抽象类。这样,就导致一个接口或者一个抽象类有好多个子类。我们在注入的时候都喜欢用多态的形式来给对象赋值:用图片说吧!

spring依赖的版本是红色的有影响吗 spring依赖关系_spring_02


现在我们直接交给spring来实例化。我们可以看到前面的类型是接口类型,后面是实现类类型。我们知道spring在实例化bean对象的时候,id属性是不允许重负的。他帮我们实例化的是等号右边的,也就是帮我们实例化实现类的(这个就不用解释了吧!,抽象类和接口都不能被实例化,java基础知识),而我们的类型是接口,spring实例化的是实现类。一个接口有很多个实现类。很明显,就出现了IOC容器中多个类型与bean对象匹配。哎呀,不知道你们能不能理解了。我尽力了(作者当时也是废了好大的劲)。

这时候,就是上面的两者解决办法

第一种:

@AutoWried

@Qualifier(value=“如果这个对象交给spring实例化的时候没有没有给id属性赋值,那这里默认就是这个类名(首字母小写)反之,就是id属性的值”)

备注:当然,如果你没有用XML形式实例化,用的是注解形式的话,那就是value的属性值。

第二种:

@Resource(name=“id|value”)

这里的id和value同上面Qualifier注解里面value的值。

复杂类型的注入/集合类型的注入

这个复杂类型就包括我们常用的List,Array,Set,Properties,Map类型的。注入的方式还是通过set方式来注入的。那就意味着我们必须为类中的每个成员提供必要的标准的set方法和默认构造函数。
下面我们上代码:

import java.util.*;
public class EntityService2 {

    private String [] arrStr;
    private List<Object> list;
    private Set<Object> set;
    private Map<String,Object> map;
    private Properties pros;

    public void setArrStr(String[] arrStr) {
        this.arrStr = arrStr;
    }

    public void setList(List<Object> list) {
        this.list = list;
    }

    public void setSet(Set<Object> set) {
        this.set = set;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public void setPros(Properties pros) {
        this.pros = pros;
    }
	//测试方法
    public void print(){
        System.out.println("array: "+Arrays.toString(arrStr));
        System.out.println(list);
        System.out.println(map);
        System.out.println(pros);
        System.out.println(set);
    }
}

spring配置文件

<!--   给数组注入   -->
        <property name="arrStr">
            <array>
                <value>zyy</value>
                <value>gxr</value>
                <value>wwh</value>
            </array>
        </property>
        
        <!-- 给list集合注入数据  -->
        <property name="list">
            <array>
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
            </array>
        </property>
        
        <!--  给set集合注入  -->
        <property name="set">
            <set>
                <value>lisi</value>
                <value>duanting</value>
                <value>zhaiyuting</value>
                <value>lisiguang</value>
            </set>
        </property>
        
        <!--    map集合注入数据   -->
        <property name="map">
            <map>
                <entry key="name1" value="yyl"></entry>
                <entry key="name2" value="sty"></entry>
                <entry key="name3" value="zjs"></entry>
            </map>
        </property>
		<!-- 给Properties注入数据-->
        <property name="pros">
            <props>
                <prop key="driver">com.jabc.mysql.Driver</prop>
                <prop key="url">http://localhost:8080/loveyou</prop>
                <prop key="username">gxr</prop>
                <prop key="password">1314520</prop>
            </props>
        </property>
        
    </bean>

小结:我们可以看出,给什么类型注入数据,就用什么标签,数组就是Array标签,list就是list标签,Set就是set标签等等。
不过我们还发现,这五类当中,基本可以划分为两大类:
第一类:线性结构:Array、List、Set
线性结构是我们用以上三个任意一个标签都可以注入成功
第二类:键值对 : Properties、Map
键值对时,我们就采用提供标签对来进行对数据的注入。

最后,我同意给出测试类的代码和运行结果:

import jdbc.service.EntityService2;
import jdbc.service.EntityServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMethod {
    //private static  EntityService entityService = new EntityServiceImpl();
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        
        //使用构造器注入
        EntityServiceImpl entityService = (EntityServiceImpl) ac.getBean("EntityService1");
        entityService.print();

        //使用set方式来注入
        EntityServiceImpl entityService2 = (EntityServiceImpl) ac.getBean("EntityService2");
        entityService2.print();
        
        //注入复杂类型的数据
       EntityService2 entity =  ac.getBean("EntityService3",EntityService2.class);
        entity.print();
    }
}

代码运行结果:

spring依赖的版本是红色的有影响吗 spring依赖关系_spring_03


说明:这里需要说明一个问题,就是什么样的数据需要注入。一句话,就是经常变换的数据不适合注入。我们需要注入的数据基本上都是不变的。比如数据库的配置文件等。