我对控制反转以及依赖注入的认识

简介: IOC诞生的历史 在没有IoC时,关联不同模块是通过类实例实现的,代码可能是这样子的: // 代码清单1 public interface YourService { void func1(); void func2(); } // 代码清单2 public class.

IoC诞生的历史

在没有IoC时,关联不同模块是通过类实例实现的,代码可能是这样子的:

// 代码清单1
public interface YourService {
    void func1();
    
    void func2();
}

// 代码清单2
public class MyServiceImpl {
    private YourServiceImpl yourServiceImpl;
    
    public MyServiceImpl() {
        this.yourServiceImpl = new YourServiceImpl();
    }
    
    public void process() {
        // do something
        this.yourServiceImpl.func1(...);
        // do something
        this.yourServiceImpl.func2(...);
    }
}
// 代码清单3
public class MyServiceImpl {
    private YourService yourService;
    
    public MyServiceImpl(YourService yourService) {
        this.yourService = yourService;
    }
    
    public void process() {
        // do something
        this.yourService.func1(...);
        // do something
        this.yourService.func2(...);
    }
}

当YourServiceImpl的接口不变时,只需要根据业务需要更换不同的YourService实现类即可。一旦更换实现类时(如将yourServiceImpl更换为NewServiceImpl),MyServiceImpl中所有引用YourService的代码都要替换为NewServiceImpl,这严重违背了面向对象设计中DIP(Dependence Inversion Principle)原则。

* 上层业务应该依赖抽象,抽象定义了"要做什么",而不应依赖实现细节"怎么做"。
* 模块间通过接口建立依赖关系
* 实现类依赖接口或抽象类

IOC实现的原理

IoC: Inversion of Control(依赖反转), 是针对之前模块间通过实现类建立关联而言,反转指"模块实现依赖实现类"->"模块实现依赖(实现类的)接口"。

执行IoC原则后,模块内原本生成实例的语句被替换成接口调用(代码清单3)。我们知道JVM是在运行时才申请内存并生成类实例对象的,运行时系统怎么知晓具体接口对应的实现是什么呢?

这时候就轮到反射和DI登场了。DI(Dependency injection)依赖注入,在运行时根据spring的xml配置文件(有实现类的全路径名称)选择适当的时机构造依赖对象实例并注入到依赖实现类接口的模块中。而如何完成依赖实例对象的构造以及初始化是Spring DI的核心。

// 代码清单4
    <bean id="userDAO" class="dao.impl.UserDAOImpl">
        <property name="sellerDAO" ref="sellerDAO" />
    </bean>
    
    <bean id="sellerDAO" class="dao.impl.SellerDAOImpl" />

代码清单4中演示了spring的bean配置代码。Spring解析bean的xml文件,拿到bean的类全路径名称后调用Class.forName(String className)方法从对应的类加载器中获取类信息,并调用Class.newInstance()方法生成类实例。

此时类实例并未完成初始化,还需要注入类实例的依赖项。Spring解析bean下的property条目,以property name为优先查找实例类的set方法并匹配入参,如果有则调set方法注入property。此时给模块返回完整的依赖对象。

注入方法

set方法注入

该方式要求被注入的属性在实现类里有set方法。set注入支持简单类型和引用类型。

构造函数注入

// 代码清单5
/**
 * Created by fujianbo on 2018/6/17.
 *
 * @author fujianbo
 * @date 2018/06/17
 */
public class UserDAOImpl implements UserDAO {
    private SellerDAO sellerDAO;
    
    public void setSellerDAO(SellerDAO sellerDAO) {
        this.sellerDAO = sellerDAO;
    }
    
    public UserDAOImpl() {
    }
    
    public UserDAOImpl(SellerDAO sellerDAO) {
        this.sellerDAO = sellerDAO;
    }
}

// 代码清单6
<bean id="sellerDAO" class="dao.impl.SellerDAOImpl" />
<bean id="userDAO" class="dao.impl.UserDAOImpl">
    <constructor-arg ref="sellerDAO"/>
</bean>

当有多个constructor-arg时,Spring根据传入的依赖参数找到对应的构造函数而并不关心书写顺序。但如果构造函数只是入参顺序不同,定义constructor-arg时需要加index参数,否则Spring创建类实例失败。

// 代码清单7
public class User {
    private String userName;
    private Integer age;

    public User(int age, String userName) {
        this.age = age;
        this.userName = userName;
    }

    public User(String userName, int age) {
        this.userName = userName;
        this.age = age;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

...
// yourConfig.xml
<bean id="user" class="..." >
    <constructor-arg index="0" value="Jack"/>
    <constructor-arg index="1" value="26"/>
</bean>

IoC & DI

以下个人对于这两个兄弟的理解,如有谬误还请指正!

IoC

1、IoC(控制反转)是模块间拜托对类实例依赖的桥梁,它统一管理了类实例对象的创建、初始化、注入以及销毁过程。

2、模块间依赖实现类的接口,从调用者角度来看,多数情况无需关注细节以及感知实现细节的变化,只需关注接口入参和返回值,将OOP上升到面向接口、面向切面编程的层次,是工厂模式的升华(参考引用2)。每个模块相当于一个垂直业务,IoC抽象了模块内部创建类实例的代码,依托反射和DI,给出了类实例类型识别解析、初始化、对象管理(spring容器)的解决方案。

3、当模块间不再显示创建类实例总得有人接下这项"苦差事"吧,这人就是Spring。Spring通过bean配置文件或者@Autowired, @Component, @Resource(J2EE提供), @Service, @Repository等注解,结合java反射原理生成类实例对象并给模块间的接口赋值。

DI

Spring提供的传递模块间依赖的接口的实例的方法,是IoC解决方案的一部分。

引用

目录
相关文章
|
数据采集 运维 Java
有了 Dataphin v4.0,跨系统调度依赖再也不是难题
Dataphin v4.0引入了新的触发式节点,用于解决多数据平台间的调度问题。当上游系统(如Unix的crontab)完成数据采集后,可通过触发式节点通知Dataphin开始拉取数据,避免传统轮询方式的效率低和资源占用。触发式节点需满足Dataphin OpenAPI开通和网络连通条件,并通过SDK进行外部触发。示例展示了如何创建和使用触发式节点,以及使用Java SDK模拟触发请求。
688 0
|
Prometheus 监控 Cloud Native
Prometheus(普罗米修斯)
Prometheus(普罗米修斯)
1225 0
|
7月前
|
存储 Java 关系型数据库
ssm150旅游网站的设计与实现+jsp(文档+源码)_kaic
本旅游网站基于现代经济快节奏发展和信息化技术的升级,采用SSM框架、Java语言及Mysql数据库开发。它实现了景点、新闻、酒店、飞机票和火车票管理等功能,帮助管理者高效处理大量数据信息,提升工作效率。系统界面简洁美观,功能布局合理,同时提供了数据安全解决方案,确保信息的安全性和可靠性。该网站不仅提高了事务处理效率,还实现了数据的整体化、规范化与自动化管理。关键词:旅游网站;SSM框架;Mysql;自动化。
|
数据采集 自然语言处理 数据挖掘
利用ChatGPT进行数据分析——如何提出一个好的prompt
利用ChatGPT进行数据分析——如何提出一个好的prompt
485 0
|
11月前
|
前端开发 JavaScript 虚拟化
React 树形组件 Tree View
本文从零开始构建了一个简单的React树形组件,介绍了环境准备、项目创建、基础组件构建等步骤,并探讨了常见问题及解决方案,包括层次嵌套过深、状态管理复杂、事件处理不当和样式问题,帮助读者在实际项目中更好地应用树形组件。
527 3
【科研技巧】简单的在Office Word 2019中设置页脚的页码从指定页(正文)开始
如何在Microsoft Word 2019中设置页码从指定页面(通常是正文开始页)启动的方法。
415 2
|
存储 JSON JavaScript
Python教程:一文了解Python中的json库
JSON(JavaScript Object Notation)是一种轻量级数据交换格式,易于人类阅读和编写,也易于计算机解析和生成。在Python中,JSON通常用于数据交换和存储,因为它与Python的字典和列表类型相似。
1099 2
|
负载均衡 安全 Linux
为何一个网卡需要配置多个IP地址?🌐
在Linux环境中,一个网卡配置多个IP地址是一个常见且强大的网络管理策略🛠️。这种策略不仅增加了网络的灵活性和效率,还能满足特定的网络需求和应用场景🎯。让我们一探究竟,看看在哪些情况下,为什么一个网卡会需要配置多个IP地址,并探讨不配置多个IP地址的后果。
为何一个网卡需要配置多个IP地址?🌐
|
Java Android开发
Android 12 自定义底部导航栏
Android 12 自定义底部导航栏
490 4
|
机器学习/深度学习 传感器 编解码
Hy-Tracker来啦 | 带有YOLO的跟踪算法家族再添新成员,尺度变化和遮挡都不是问题!
Hy-Tracker来啦 | 带有YOLO的跟踪算法家族再添新成员,尺度变化和遮挡都不是问题!
468 1