Spring IoC控制反转与DI依赖注入


初识Spring,这篇文章我简单的介绍了一些关于Spring的优势和核心概念。这篇文章对对于初接触Spring的自己来说做个简单的了解

1. 程序的耦合

我们再来谈一谈程序的耦合

I. 程序的耦合:程序间的依赖关系

包括

  • 类之间的依赖

  • 方法间的依赖

II. 为什么会出现程序的耦合呢?

       我们写代码时,会遵循 ‘功能单一性’ 的原则。就是在一个类中,尽量保持它功能的单一性,一个类尽量独立实现一个功能,如果很多代码都写到一个类中,出现问题很难更改。但是,复杂的功能逻辑往往需要调用很多方法来实现,通常都需要两个或者更多的类通过彼此的合作来实现业务逻辑。

       某个对象需要获取与合作对象的引用,如果这个获取的过程需要自己实现(就是使用new实例化对象),代码的耦合度就会高,维护起来的成本就比较高。

解耦:降低程序间的依赖关系

实际开发中应该做到:

  • 编译期不依赖,运行时才依赖。

解耦的思路

  • 第一步:使用反射来创建对象,而避免使用new关键字

  • 第二步:通过读取配置文件来获取要创建的对象全限定类名

2. 控制反转IoC

控制反转理解

以下实例说明参考自二哥的文章《Java:控制反转IoC与依赖注入DI》

我们来通过实例来模拟一下。假如老Q是计算机院的辅导员,他想让数媒专业的班级去听讲座(大一大二的你是不是常常被拉去凑人数,讲座听半天听不懂就瞌睡了),代码可以这样实现:

数媒班级代码如下所示:

public class Media {
    public void liaten() {
        System.out.println("数媒专业在听讲座!");
    }
}

老王类的代码如下所示:

public class LaoQ {
    public void advice() {
        //通知数媒班级听讲座
        new Media().liaten();
    }
}

测试类的代码如下所示:

public class Test {
    public static void main(String[] args) {
        LaoQ laoQ = new LaoQ();
        laoQ.advice();
    }
}

运行结果:

数媒专业在听讲座!

LaoQ 类的 advice()方法中使用 new 关键字创建了一个 Media类的对象——这种代码的耦合度就很高,维护起来的成本就很高,为什么这么说呢?

某一天,学校又来学者作报告了,老Q想起了数媒专业(俺的万精油专业),可数媒专业去上实验了,让谁去听呢,老Q辅导员想起了软工专业,于是 LaoQ 类就不得不重新通知了,于是代码变成了这样:

public class Soft {
    public void listen() {
        System.out.println("软工专业在听讲座!");
    }
}

public class LaoQ {
    public void advice() {
        //通知数媒专业听讲座
        new Media().liaten();
    }

    public void advice1() {
        //通知软工专业听讲座
        new Media().liaten();
    }
}

假如软工专业去实习了,老Q辅导员打算要让计科专业去听讲座。这样下去的话,LaoQ这个类里面都是强耦合了。

老Q最为一个辅导员,这种打酱油的工作,还用得着亲自负责么!于是,他找了一个办公室助理李同学来帮他干这件事。

李同学负责去叫学院班级去执行老Q听讲座的命令。代码可以这样实现:

定义一个听讲座专业的接口,代码如下所示:

public interface Major {
    //该班级听讲座
    void listen();
}

数媒类的代码修改如下所示:

public class Media implements Major {

    @Override
    public void listen() {
        System.out.println("数媒专业去听讲座!");
    }

    public boolean experiment() {
        // 星期三的时候数媒专业要做实验
        return true;
    }
}

软工类的代码修改如下所示:

public class Soft implements Major {

    @Override
    public void listen() {
        System.out.println("软工专业听讲座!");
    }
}

李同学类的代码如下所示:

public class StuLi {
    public static Major getListen() {
        Media media = new Media();
        if (media.experiment()) {
            //如果数媒有实验就通知软工去
            return new Soft();
        }
        return media;
    }
}

如果数媒专业在上实验,就通知软工专业去

这时老Q就轻松了,有啥事告诉李同学,自己不再关心到底哪个专业能去了,具体安排由李同学去协调通知。

老Q类的代码修改如下:

public class LaoQ {
    public void advice() {
        //通知数媒专业听讲座
        new Media().listen();
    }

    public void advice1() {
        System.err.println("数媒专业在做实验...");
        //通知软工专业听讲座
        new Soft().listen();
    }
}

测试类的代码不变:

public class Test {
    public static void main(String[] args) {
        LaoQ laoQ = new LaoQ();
        laoQ.advice();
    }
}

数媒专业在做实验…
软工专业听讲座!

我们替老Q想的这个办法就叫控制反转(Inversion of Control,缩写为 IoC),它不是一种技术,而是一种思想——指导我们设计出松耦合的程序。

       控制反转从词义上可以拆分为“控制”和“反转”,说到控制,就必须找出主语和宾语,谁控制了谁;说到反转,就必须知道正转是什么。

       在紧耦合的情况下,老Q下命令的时候自己要通过new关键字创建依赖的对象(数媒专业和软工专业);而控制反转后,老Q要找班级去听讲座的这件事(具体哪个专业去)由李同学来负责,也就是说控制权由老Q转交到了李同学,是不是反转了呢?


Q0:IoC的作用是什么?

  • 削弱类之间的强耦合(不能完全消除,只是削弱松动)

Q1:谁控制谁?为什么叫反转?

  • 答:IoC容器控制,而以前是应用程序控制,所以叫反转

Q2:控制什么?

  • 答:控制应用程序所需要的资源(对象、文件……)

Q3:为什么控制?

  • 答:解耦组件之间的关系,削减类之间的耦合度

Q4:控制的哪些方面被反转了?

  • 答:程序的控制权发生了反转,从应用程序转移到了IoC容器

控制反转实例

模拟编写持久层UserDao接口及其实现类

UserDao

public interface UserDao {
    /**
     * 用户注册
     */
    void regist();
}

UserDaoImpl

public class UserDaoImpl implements UserDao {
    public void regist() {
        System.out.println("user regist...");
    }
}

模拟编写业务逻辑层UserService接口及其实现类

UserService

public interface UserService {
    /**
     * 用户注册
     */
    void regist();
}

UserServiceImpl

/**
 * @Author: Mr.Q
 * @Date: 2020-04-23 10:58
 * @Description:用户的业务逻辑层接口实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserServiceImpl() {
        System.err.println("对象创建了!");
    }

    public void regist() {
        userDao.regist();
    }
}

现在,我们不直接用new来实例化对象了,用上面的例子来说,就是老Q不再具体通知班级了,交由李同学来管理。李同学就相当于Spring管理资源.

我们要做的事在配置文件中配置UserDaoUserService

applicationContext.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">

    <!--把对象的创建交给spring来管理-->
    <bean id="userService" class="org.iqqcode.service.impl.UserServiceImpl"/>

    <bean id="userDao" class="org.iqqcode.dao.impl.UserDaoImpl"/>

</beans>

模拟编写表现层RegistClient

RegistClient

public class RegistClient {
    public static void main(String[] args) {
        //1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2.根据id获取Bean对象
        UserDao userDao = ac.getBean("userDao",UserDao.class); //通过反射来创建对象
        UserService userService = (UserService) ac.getBean("userService"); //Object类型
        userDao.regist();
    }
}


这个例子中,我们消除了dao层和service接口new的实例化

UserDao userDao = new UserDaoImpl();
UserService userService = new UserServiceImpl();

ApplicationContext 中的new是无可避免的,因为要实例化IoC容器来读取配置文件,获取要创建的对象全限定类名


核心容器的两个接口

获取Spring的IoC核心容器创建对象的策略

ApplicationContext:单例对象适用

它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。

BeanFactory:多例对象使用

它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。

ApplicationContext的三个常用实现类:

  • ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
  • FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
  • AnnotationConfigApplicationContext:它是用于读取注解创建容器的
获取Bean对象

由于是Spring来管理我们对象的产生,不知道传入的Bean对象是什么类型的,所以有两种方法来获取Bean对象

  • Object类(需要强转)

  • 反射获取

3. Bean对象

Bean对象

JavaBean,Spring Bean对象的理解

4. 依赖注入DI

依赖注入(Dependency Injection,简称 DI)是实现控制反转的主要方式。它是Spring框架核心IoC的具体实现。

我们的程序在编写时,通过控制反转(IoC),把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IoC解耦只是降低他们的依赖关系,但不会消除。

例如:我们在业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

在当前类需要用到其他类的对象,由Spring为我们提供,我们只需要在配置文件中说明。

在类A的实例创建过程中就创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中。


【注入DI的方式】

大概有 3 种具体的实现形式:

  1. 构造函数注入

  2. Setter()注入

  3. 使用注解提供

【注入的数据】

能注入的数据有三类:

  1. 基本类型和String

  2. 其他Bean类型(在配置文件中或者注解配置过的Bean)

  3. 复杂类型 / 集合类型


构造函数注入

使用的标签: constructor-arg

标签出现的位置:bean标签的内部

标签中的属性

  • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型

  • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始

  • name:用于指定给构造函数中指定名称的参数赋值

以上三个用于指定给构造函数中哪个参数赋值

  • value:用于提供基本类型和String类型的数据

  • ref:用于指定其他的Bean类型数据。它指的就是在Spring的IoC核心容器中出现过的Bean对象(如Date类对象)

构造函数注入注入对比

优势:

  • 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功

弊端:

  • 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供

Setter()注入

Setter方法注入(更常用的方式)

涉及的标签:property

出现的位置:bean标签的内部

标签的属性

  • name:用于指定注入时所调用的set方法名称

  • value:用于提供基本类型和String类型的数据

  • ref:用于指定其他的bean类型数据。它指的就是在Spring的IoC核心容器中出现过的bean对象

优势:

  • 创建对象时没有明确的限制,可以直接使用默认构造函数

弊端:

  • 如果有某个成员必须有值,则获取对象时set()方法有可能没有执行

使用注解注入

下篇文章肝吧…


集合类型数据注入

对于ListSetMapProps集合类型的数据来说,我们使用Sette()来注入

结构相同,标签可以互换

用于给List结构集合注入的标签

  • listarrset

用于给Map结构 [key-value] 集合注入的标签:

  • mapprops

好啦,Spring的解耦思想,IoC,Bean,DI就介绍到此啦。下一篇讲注解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值