阅览了很多篇博文,对于Spring IOC和DI看到很多人介绍的感觉都很含糊,读完之后还是觉得混淆的不行,也有的人认为DI是IOC的另一种说法…emm,可能个人理解不同吧,我的观点并不然。但这种东西,见仁见智吧~下面来谈一谈Spring IOC的实现原理,当然会提及到DI

学习过Spring的都知道,Spring的核心是IOC和AOP,在大半年前我学习Spring时,其实对这两个概念并没有理解到位,只是觉得框架就是比葫芦画瓢,觉得不重要,其实并不是,无论是为了更好的理解和使用这个框架还是对于出去面试,都有重要的作用和意义。

什么是Spring IOC

Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了?为什么要反转”

1. 谁控制谁,控制什么:

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;
谁控制谁?
所以当然是IoC容器控制了对象;
控制什么?
那就是主要控制了外部资源获取(不只是对象 还可以文件等)。

2. 为何是反转,哪些方面反转了,为什么要反转?

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;
而反转则是由容器来帮忙创建及注入依赖对象;
为何是反转?
因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
哪些方面反转了?
依赖对象的获取被反转了。

这是摘取别人的话,我自己是理解了,这里我觉得说的通俗点就是:Class A 如果需要用到 Class B的方法,那我们一开始肯定会在Class A中new一个Class B的对象。 B b = new B(); 对吧,这就是他所谓的“正转”(因为我也不知道有没有这种说法,所以暂时打个引号)

那么反转就是,我将B这个对象交给容器,放进容器里面装着了,当你A需要我B的时候,容器就会像打针一样给你注入进去。所以就是反转。

我觉得可以用一句话概括,正转是“我要你”,反转是“我给你” 化主动为被动(这里是以A的角度)

为什么要反转?

在了解为什么要反转之前,我们要知道一个原则:依赖倒置原则。

什么是依赖倒置原则?

假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。但这种设计维护性很低。

那万一上司说,我觉得这个轮胎太丑了,我要换一个,那我是不是不能随便换啊,因为依赖我的有一连串的东西,你让我改轮子,那不直接改一辆车算了?

如果换一个思路

我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘,底盘依赖车身,车身依赖汽车。

这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘、车身、汽车的设计了。

这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。

控制反转(IoC)就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(DI)。

所以,有的人认为DI是IOC的另一种说法,我觉得IOC是一种思路,而DI是实现这个思路的一个方法,这二者还是有区别的,而不是换一种说法【个人理解 如有错误希望能得到指正】

所以关系图如下我觉得是比较准确的

为什么要用IoC

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

DI(Dependency Injection)即“依赖注入”

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

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”

谁依赖于谁?
应用程序依赖于IoC容器;
为什么需要依赖?
应用程序需要IoC容器来提供对象需要的外部资源
谁注入谁?
很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象
注入了什么?
就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

小案例

这里我用一个账户的业务实现来简要说明一下IOC

Account类,就是一个账户实体类,这里为了简约显示,就省略getter setter以及toString方法,当然,如果用lombok的话直接加注解就好咯~这里就不展开了。

package com.fym.domain;import java.io.Serializable;/*** @Author fym* @Date 2020/8/18 19:07*/
public class Account implements Serializable {private Integer id;private String name;private Float money;}

账户的业务层接口:

package com.fym.service;import com.fym.domain.Account;import java.util.List;/*** @Author fym* @Date 2020/8/18 19:06* 账户的业务层接口*/
public interface IAccountService {//    查询所有List<Account> findAllAccount();Account findAccountById(Integer accountId); //查询一个void saveAccount(Account account); //保存void updateAccount(Account account);//更新void deleteAccount(Integer accountId);
}

业务层实现类

package com.fym.service.impl;import com.fym.dao.IAccountDao;
import com.fym.domain.Account;
import com.fym.service.IAccountService;import java.util.List;/*** @Author fym* @Date 2020/8/18 19:11* 账户的业务层实现类*/
public class AccountServiceImpl implements IAccountService {private IAccountDao accountDao;public Account findAccountById(Integer accountId) {return accountDao.findAccountById(accountId);}
}

持久层

package com.fym.dao;import com.fym.domain.Account;import java.util.List;/*** @Author fym* @Date 2020/8/18 19:12* 账户的持久层接口*/
public interface IAccountDao {Account findAccountById(Integer accountId); //查询一个
}

持久层的实现类

package com.fym.dao.impl;import com.fym.dao.IAccountDao;
import com.fym.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;import java.sql.SQLException;
import java.util.List;/*** @Author fym* @Date 2020/8/18 19:14*/
public class AccountDaoImpl implements IAccountDao {private QueryRunner runner;public void setRunner(QueryRunner runner) {this.runner = runner;}public Account findAccountById(Integer accountId) {try {return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);} catch (Exception e) {throw new RuntimeException(e);}}}

这里我们需要看什么呢?主要看看业务层实现类,里面有一句

private IAccountDao accountDao;

为啥?因为它要用我持久层的查找账户的一条信息这个方法呀,所以我业务层就是依赖持久层,那一开始我们的写法应该是怎样的?我需要dao这个对象是不是,那我得new出来呀

private IAccountDao accountDao = new AccountDaoImpl();

上面代码什么意思?

业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种编译期依赖关系,应该在我们开发中杜绝。我们需要优化代码解决。

再来一个另外的例子

比如JDBC里的,注册驱动时,我们为什么不使用 DriverManager 的 register 方法,而是采用 Class.forName 的方式?

public class JdbcDemo1 {public static void main(String[] args) throws Exception {//1.注册驱动// DriverManager.registerDriver(new com.mysql.jdbc.Driver());Class.forName("com.mysql.jdbc.Driver"); //反射---------------下面会讲//2.获取连接//3.获取预处理 sql 语句对象//4.获取结果集//5.遍历结果集}
}

原因就是:

我们的类依赖了数据库的具体驱动类(MySQL),如果这时候更换了数据库品牌(比如 Oracle),需要修改源码来重新数据库驱动。这显然不是我们想要的。 我们的类中不再依赖具体的驱动类,此时就算删除 MySQL 的驱动 jar 包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。

这个就是我们需要解决的问题,我们都知道要高内聚,低耦合,那如果我直接这样写,耦合度就高了是不,那我们就是要解决这个问题

所以,知道我们IoC的作用了吗?

削减计算机程序的耦合(解除我们代码中的依赖关系)。

那么我们是怎么从第二句

private IAccountDao accountDao = new AccountDaoImpl();

改变成第一句

private IAccountDao accountDao;

的呢?那这就是接下来的内容,如何进行配置

配置

基于xml的配置

在resources目录下新建一个bean.xml配置文件

1. 给配置文件导入约束

找到spring框架手册,进入core后ctrl F找到关键词xmlns,导入到bean.xml即可

2. 配置service和dao

bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中

id 属性:对象的唯一标识。

class 属性:指定要创建对象的全限定类名

<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="com.fym.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.fym.dao.impl.AccountDaoImpl"></bean>

那我们要把dao注入到service里是不是 property里的ref属性就是reference,ref="accountDao"说明注入的是id为accountDao的bean

<!--业务层对象 Service-->
<bean id="accountService" class="com.fym.service.impl.AccountServiceImpl"><!--注入dao--><property name="accountDao" ref="accountDao"></property>
</bean>

那同样的呀,dao里面不是有个QueryRunner嘛?一样的道理,我们也把它给注进dao里面

<!--配置dao对象-->
<bean id="accountDao" class="com.fym.dao.impl.AccountDaoImpl"><!--注入QueryRunner--><property name="runner" ref="runner"></property>
</bean>

那QueryRunner你不得给它数据源呀?那我们也把数据源给注进QueryRunner,数据源就是我们连接数据库的那些东西。

<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"><!--注入数据源--><constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean><!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><!--连接数据库的必备信息--><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property><property name="user" value="root"></property><property name="password" value="123456"></property>
</bean>
3. 测试配置是否成功
/*** 模拟一个表现层,用于调用业务层*/
public class Client {//    获取spring容器的ioc核心容器,并根据id获取对象/*** 核心容器的两个接口:* ApplicationContext: 单例对象适用       实际开发中更多的是使用该接口*      它在构建核心容器时,创建对象采取的策略是立即加载的方式,只要一读取完配置文件就马上创建配置文件中配置的对象* BeanFactory: 多例对象适用*      它在构建核心容器时,创建对象采取的策略是延迟加载的方式,什么时候根据id获取对象了,什么时候才真正的创建对象~* */public static void main(String[] args) {//1.获取核心容器对象ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");//2.根据id获取bean对象的两种方法IAccountService as=(IAccountService)ac.getBean("accountService");IAccountDao adao=ac.getBean("accountDao",IAccountDao.class);System.out.println(as);System.out.println(adao);}}
4. 运行结果

说明

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

IOC底层实现原理——反射

我们在Spring的配置文件中经常能看到如上所写的那些bean标签

<bean id="accountDao" class="com.fym.dao.impl.AccountDaoImpl"></bean>

那么通过这样配置,Spring是怎么帮我们实例化对象,并且放到容器中去了了?对,就是通过反射

Spring通过配置进行实例化对象,并放到容器中的过程是怎样的呢?我们看看伪代码【为什么是伪代码呢…伪代码更好理解,源码好长,最近时间紧迫,还没来得及剖析源码,当然知道源码是超级加分的!】

//解析<bean .../>元素的id属性得到该字符串值为“accountDao”
String idStr = "accountDao";
//解析<bean .../>元素的class属性得到该字符串值为“com.fym.dao.impl.AccountDaoImpl”
String classStr = "com.fym.dao.impl.AccountDaoImpl";
//利用反射知识,通过classStr获取Class类对象
Class<?> cls = Class.forName(classStr);
//实例化对象
Object obj = cls.newInstance();
//container表示Spring容器
container.put(idStr, obj);

通过解析xml文件,获取到id属性和class属性里面的内容,利用反射原理获取到配置里面类的实例对象,存入到Spring的bean容器中。

当一个类里面需要应用另一类的对象时,Spring的配置如下所示:

<!--业务层对象 Service-->
<bean id="accountService" class="com.fym.service.impl.AccountServiceImpl"><!--注入dao--><!-- 控制调用findAccountById()方法,将容器中的courseDao bean作为传入参数 --><property name="accountDao" ref="accountDao"></property>
</bean>

我们继续用伪代码的形式来模拟实现一下Spring底层处理原理:

//解析<property .../>元素的name属性得到该字符串值为“accountDao”
String nameStr = "accountDao";
//解析<property .../>元素的ref属性得到该字符串值为“accountDao”
String refStr = "accountDao";
//生成将要调用setter方法名
String setterName = "set" + nameStr.substring(0, 1).toUpperCase()+ nameStr.substring(1);
//获取spring容器中名为refStr的Bean,该Bean将会作为传入参数
Object paramBean = container.get(refStr);
//获取setter方法的Method类,此处的cls是刚才反射代码得到的Class对象
Method setter = cls.getMethod(setterName, paramBean.getClass());
//调用invoke()方法,此处的obj是刚才反射代码得到的Object对象
setter.invoke(obj, paramBean);

只要在代码或配置文件中看到类的完整路径(包.类),其底层原理基本上使用的就是Java的反射机制

参考

汽车例子:https://blog.csdn.net/sinat_28007043/article/details/106111498

部分讲解:https://www.cnblogs.com/xdp-gacl/p/4249939.html

反射:https://blog.csdn.net/mlc1218559742/article/details/52774805

小案例代码是自己当时学习Spring的时候看着视频写的,现在也忘了看的是哪个视频啦哈哈哈

感谢能看到这里,不知道这篇文章对你有没有帮助,希望一起加油吧!

Spring IOC与DI、反射的理解 含小案例说明相关推荐

  1. spring IOC和DI 理解

    IOC(控制反转)和DI(依赖注入)是spring中的重要组成部分,下面是个人的一些理解,不代表官方. 1.IOC是什么? IOC:全名是Inversion of Controller 中文解释是控制 ...

  2. 手写实现Spring(IOC、DI),SpringMVC基础功能

    手写实现Spring(IOC.DI),SpringMVC功能 spring和springMVC的用法相信大家都不陌生,我简单讲下我实现的思路 spring阶段 事项 配置 配置web.xml: ini ...

  3. Spring IOC和DI

    Spring  IOC和DI 框架:  将一些公用的模块进行集成,通过框架的形式进行管理(事务控制,权限控制(Shiro),日志收集log) Spring框架  SSH: Struts2(配置重型)+ ...

  4. 关于RuoYi中Spring IOC、DI以及MVC不同注解的使用

    1.什么是Spring IOC.DI? IOC(inverse of control)即"控制反转",DI(Dependence Injection)即"依赖注入&quo ...

  5. spring IOC和DI

    spring IOC and DI 1.IOC和DI的区别: IOC:对象的管理权由spring容器掌握(管理权限包括:对象的创建时间点.创建方式以及对象属性的管理): DI:spring操作对象属性 ...

  6. 浅谈Spring IOC和DI及Spring工厂类

    浅谈Spring IOC和DI及Spring的工厂类 文章目录 浅谈Spring IOC和DI及Spring的工厂类 一. IOC 1.什么是IOC 2.为什么使用IOC 传统开发模式的弊端 3. 使 ...

  7. Spring:IoC和DI完成打印机打印详细说明过程及代码

    Spring:IoC和DI完成打印机 课后作业 使用Spring的IoC.DI 装配一台打印机 纸张接口 实现 有: A4 A5 墨盒接口 实现 有:黑白 彩色 注解方式和非注解方式都要 说明:1.首 ...

  8. 彻底理解Spring IOC和DI

    目录 前言 1. 分享Iteye的开涛对Ioc的精彩讲解 1.1 IoC是什么 1.2 IoC能做什么 1.3 IoC和DI 2. 分享Bromon的blog上对IoC与DI浅显易懂的讲解 2.1 I ...

  9. Spring IoC、DI、Bean和自动装配的理解

    文章目录 IoC创建对象 DI依赖注入 Bean的理解 自动装配 IoC创建对象 我们都知道IoC是控制反转的,也就是我们只需要把类注册到Spring容器中,他可以帮助我们创建对象,该创建的思想也就是 ...

最新文章

  1. 成本预算的四个步骤_干货!如何做好年度培训计划中的预算工作
  2. Beaker:一个基于Electron的点对点Web浏览器
  3. PHPRunner(网页制作工具)v10.3中文版
  4. Network 之一 国际标准组织介绍、互联网/因特网、以太网概念区分、协议标准
  5. 【读书笔记】【独立思考】2018-04-03(1)
  6. 真狠!10000mAh超大电池手机发布,真的神机...
  7. 四川高中计算机考试操作题,四川省学业水平考试VB程序设计操作题演示
  8. python集合_Python集合
  9. Entity Framework Core 7.0 未来规划
  10. 为什么这本搜索引擎营销的书畅销呢?
  11. MySQL可视化工具连不上或mysql启动不了
  12. OAuth 2.0 开放授权的那些事儿
  13. orcad 16.6 关闭startpage
  14. QlikView 笔记(二) 常用数字函数
  15. 实验部分小结:数据集处理部分
  16. MATLAB强化学习实战(七) 在Simulink中训练DDPG控制倒立摆系统
  17. Windows下使用pip时出现TSL/SSL错误解决方案
  18. AlphaGo挑战围棋九段高手李世石
  19. 2022年全国职业院校技能大赛试题3(中职组)
  20. 让 VS2008 崩溃 的 WinForm 用户控件

热门文章

  1. @Value 获取乱码 问题解决
  2. CGMM 基于上下文的马尔可夫模型
  3. Android原生OS风格ROM包,小米5 的LineageOS14.1刷机包 安卓7.1.1原生风格 20180203更新
  4. Location虚拟定位
  5. 河南大学计算机学院夏令营,河南大学数学与统计学院2020年优秀大学生国际夏令营...
  6. c语言巡线程序,小车巡线程序
  7. Android混淆那些事儿
  8. 微信消息记录导出并制作图云(安卓版)
  9. 虚幻4与现代C++:转移语义和右值引用
  10. Redis应用项目---抢红包功能(三)