第一次了解到控制反转(Inversion of Control)这个概念,是在学习Spring框架的时候。IOCAOP作为Spring的两大特征,自然是要去好好学学的。而依赖注入(Dependency Injection,简称DI)却使得我困惑了挺久,一直想不明白他们之间的联系。

控制反转

控制反转顾名思义,就是要去反转控制权,那么到底是哪些控制被反转了?在2004年 Martin fowler 大神就提出了

“哪些方面的控制被反转了?”

这个问题,他总结出是依赖对象的获得被反转了。

在单一职责原则的设计下,很少有单独一个对象就能完成的任务。大多数任务都需要复数的对象来协作完成,这样对象与对象之间就有了依赖。一开始对象之间的依赖关系是自己解决的,需要什么对象了就New一个出来用,控制权是在对象本身。但是这样耦合度就非常高,可能某个对象的一点小修改就会引起连锁反应,需要把依赖的对象一路修改过去。

如果依赖对象的获得被反转,具体生成什么依赖对象和什么时候生成都由对象之外的IOC容器来决定。对象只要在用到依赖对象的时候能获取到就可以了,常用的方式有依赖注入和依赖查找(Dependency Lookup)。这样对象与对象之间的耦合就被移除到了对象之外,后续即使有依赖修改也不需要去修改原代码了。

总结一下,控制反转是指把对象的依赖管理从内部转移至外部。

依赖注入

控制反转是把对象之间的依赖关系提到外部去管理,可依赖是提到对象外面了,对象本身还是要用到依赖对象的,这时候就要用到依赖注入了。顾名思义,应用需要把对象所需要的依赖从外部注入进来。可以是通过对象的构造函数传参注入,这种叫做构造器注入(Constructor Injection)。如果是通过JavaBean的属性方法传参注入,就叫做设值方法注入(Setter Injection)

不管是通过什么方式注入的,如果是我们手动注入的话还是显得太麻烦了。这时候就需要一个容器来帮我们实现这个功能,自动的将对象所需的依赖注入进去,这个容器就是前面提到的IOC容器了。

控制反转和依赖注入的关系也已经清晰了,它们本质上可以说是一样的,只是具体的关注点不同。控制反转的关注点是控制权的转移,而依赖注入则内含了控制反转的意义,明确的描述了依赖对象在外部被管理然后注入到对象中。实现了依赖注入,控制也就反转了。

例子

  • 首先是传统的方式,耦合非常严重。
public class Main {

    public static void main(String[] args) {
OrderService service = new OrderService();
service.test();
} }
public class OrderService {

    private OrderDao dao = new OrderDao();

    public void test() {
dao.doSomeThing();
} }
public class OrderDao {

    public void doSomeThing() {
System.out.println("test");
} }
  • 接下来是没有使用容器的方式,松耦合了,但是手动注入非常的麻烦。
public class Main {

    public static void main(String[] args) {
Dao dao = new OrderDao();
OrderService service = new OrderService(dao);
service.test();
} }
public interface Dao {

    void doSomeThing();

}
public class OrderDao implements Dao {

    @Override
public void doSomeThing() {
System.out.println("test");
} }
public class OrderService {

    private Dao dao;

    public OrderService(Dao dao) {
this.dao = dao;
} public void test() {
dao.doSomeThing();
} }
  • 接下来使用容器造福人类。
// 引导类要放在项目根目录下,也就是在 src 下面
public class Main { public static void main(String[] args) {
// 生成容器
Container container = new Container(Main.class);
// 获取Bean
OrderService service = container.getBean(OrderService.class);
// 调用
service.test();
} }
@Component
public class OrderService { @Autowired
private Dao dao; public void test() {
dao.doSomeThing();
} public Dao getDao() {
return dao;
} public void setDao(Dao dao) {
this.dao = dao;
}
}
@Component
public class OrderDao implements Dao { @Override
public void doSomeThing() {
System.out.println("test");
} }
public interface Dao {

    void doSomeThing();

}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Component {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface Autowired {
}
public class Container {

    private List<String> classPaths = new ArrayList<>();

    private String separator;

    private Map<Class, Object> components = new HashMap<>();

    public Container(Class cls) {
File file = new File(cls.getResource("").getFile());
separator = file.getName();
renderClassPaths(new File(this.getClass().getResource("").getFile()));
make();
di();
} private void make() {
classPaths.forEach(classPath -> {
try {
Class c = Class.forName(classPath);
// 找到有 @ioc.Component 注解的类并实例化
if (c.isAnnotationPresent(Component.class)) {
components.put(c, c.newInstance());
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
});
} /**
* 注入依赖
*/
private void di() {
components.forEach((aClass, o) -> Arrays.stream(aClass.getDeclaredFields()).forEach(field -> {
if (field.isAnnotationPresent(Autowired.class)) {
try {
String methodName = "set" + field.getType().getName().substring(field.getType().getName().lastIndexOf(".") + 1);
Method method = aClass.getMethod(methodName, field.getType());
if (field.getType().isInterface()) {
components.keySet().forEach(aClass1 -> {
if (Arrays.stream(aClass1.getInterfaces()).anyMatch(aClass2 -> aClass2.equals(field.getType()))) {
try {
method.invoke(o, components.get(aClass1));
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
} else {
method.invoke(o, components.get(field.getType()));
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}));
} /**
* 该方法会得到所有的类,将类的全类名写入到classPaths中
*
* @param file 包
*/
private void renderClassPaths(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
Arrays.stream(Objects.requireNonNull(files)).forEach(this::renderClassPaths);
} else {
if (file.getName().endsWith(".class")) {
String classPath = file.getPath()
.substring(file.getPath().lastIndexOf(separator) + separator.length() + 1)
.replace('\\', '.')
.replace(".class", "");
classPaths.add(classPath);
}
}
} public <T> T getBean(Class c) {
return (T) components.get(c);
} }

后记

一些概念在脑海里总以为是清晰的,等实际用到或者是写成文字的时候就发现有很多不理解的地方。本文的目的就是梳理下概念,做些记录。这次自己尝试实现了下IOC容器,一开始写就知道自己之前的理解有问题了。好歹是写出了个能用的版本,用来应付文章中的例子。后面可以去参考下Spring的实现,估计能学到不少东西。

我的博客地址

参考资料

Spring理论基础-控制反转和依赖注入的更多相关文章

  1. 简单了解Spring的控制反转和依赖注入

    浅谈控制反转(Inversion of Control,IOC) 我们首先先来了解一下控制二字,也就是在控制"正"转的情况下,在任何一个有请求作用的系统当中,至少需要有两个类互相配 ...

  2. Spring的控制反转和依赖注入

    Spring的官网:https://spring.io/ Struts与Hibernate可以做什么事? Struts, Mvc中控制层解决方案 可以进行请求数据自动封装.类型转换.文件上传.效验… ...

  3. spring(3)------控制反转(IOC)/依赖注入(DI)

    一.spring核心概念理解 控制反转: 控制反转即IoC (Inversion of Control).它把传统上由程序代码直接操控的对象的调用权交给容器.通过容器来实现对象组件的装配和管理. 所谓 ...

  4. Spring、控制反转与依赖注入(概念)

    Spring 一个开源的控制反转(Inversion of Control ,Ioc)和面向切面(AOP)的容器框架. 主要目的:简化开发 控制反转(Inversion of Control ,Ioc ...

  5. 如何通俗的理解spring的控制反转、依赖注入、面向切面编程等等

    之前一直不理解spring的一些基础特性是什么意思,虽然网上的解释也很多,但是由于我比较笨,就是看不懂,知道最近才稍微了解,下面就以通俗讲解的方式记录下来. 前言 假设我是一个没有开店经验的小老板,准 ...

  6. 【SSH】——spring的控制反转和依赖注入

    spring是一个轻量级的容器框架,主要是为了使企业的开发变得简单.高效.无论是从大小还是开销来讲,他都可以算是轻量级的,也是非侵入性的. 下图是spring的框架示意图,说到spring,就不得不提 ...

  7. Spring 之 控制反转(IoC), 依赖注入(DI)和面向切面(AOP)

    关于依赖注入, 这篇博文写的非常简单易懂. https://github.com/android-cn/blog/tree/master/java/dependency-injection 此外, 博 ...

  8. Spring 1 控制反转、依赖注入

    1.1 Spring的核心是控制反转(IoC)和面向切面(AOP) 学习spring之前的开发中通过new创建一个对象,有了spring之后,spring创建对象实例-IoC控制反转,之后需要实例对象 ...

  9. spring IOC --- 控制反转(依赖注入)----简单的实例

    IoC(Inversion of Control)控制反转,对象创建责任的反转,在spring中BeanFacotory是IoC容器的核心接口,负责实例化,定位,配置应用程序中的对象及建立这些对象间的 ...

随机推荐

  1. 【Docker 教程】- Docker 架构

    1.Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器. 2.Docker 容器通过 Docker 镜像来创建. 3.容器与镜像的关系类似于面向对象编程 ...

  2. 什么是Oracle的分区表 (转 作者 陈字文)

    假设我们现在正在酝酿经营一家图书馆,最初,我们只有十本书提供给大家来阅读和购买.对于十本书而言,我们可能只需要一个书架格子将其作为保存这十本书的容器就足够了,因为任何一个人都可以很轻松的扫一眼就可以将 ...

  3. 【bzoj1877】[SDOI2009]晨跑 费用流

    题目描述 Elaxia最近迷恋上了空手道,他为自己设定了一套健身计划,比如俯卧撑.仰卧起坐等 等,不过到目前为止,他坚持下来的只有晨跑. 现在给出一张学校附近的地图,这张地图中包含N个十字路口和M条街 ...

  4. dashboard and reporting Interface analysis

    dashboard and reporting Interface analysis > show system show system backup show system counters ...

  5. 在git 服务器挂载、创建新的项目、克隆新的项目

     流程,服务器创建项目名-->客户端克隆-->客户端提交并且推送-->完成   详细步骤   1.在git服务器路径文件夹下创建空文件夹,名字为新的项目名,如在  F:\git   ...

  6. BZOJ3771:Triple——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=3771 大意:给n把不同价值的斧子,从中选一把/两把/三把,所构成的每种价值和的可能情况有多少. 生 ...

  7. HDU4757:Tree——题解

    http://acm.hdu.edu.cn/showproblem.php?pid=4757 给一棵有点值的树,每次询问u~v的最短路当中的一个点的点权异或z最大值. 前置技能:HDU4825 前置技 ...

  8. 关于Javac编译器的那点事(一)

    Javac是什么? 它是一种编译器,将Java对人非常友好的语言,编译转化对所有机器都非常友好的语言,即:JVM能够识别的语言,也就是Java字节码.而Java字节码,说白了就是一连串二进制数字. J ...

  9. bzoj3232圈地游戏——0/1分数规划+差分建模+判环

    Description DZY家的后院有一块地,由N行M列的方格组成,格子内种的菜有一定的价值,并且每一条单位长度的格线有一定的费用. DZY喜欢在地里散步.他总是从任意一个格点出发,沿着格线行走直到 ...

  10. 【DP】【P2734】游戏 A Game

    传送门 Description 有如下一个双人游戏:N个正整数的序列放在一个游戏平台上,游戏由玩家1开始,两人轮流从序列的任意一端取一个数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结 ...