简单定义

Wiki

从 wiki 上看的话,以下内容:

In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code.——Inversio of Control

简单翻译下:
IoC 是一种编程原则。IoC 相对于传统的控制流(简单理解为程序执行顺序)来说,将控制流进行了反转。在 IoC 中,咱们自己写的代码是从一个通用框架中获得了控制流。用了这个原则的软件框架相对于传统的过程式编程来说,反转了控制。在传统的编程中,咱们写的代码通过调用可重用的库来处理任务,在 IoC 中,是框架调用了一些自定义或特定任务的代码。

例子

来个简单的例子:

以找对象为例,你如果是主动找的话,你看到了一个中意的,需要你自己去问人要联系方式;而如果你通过婚姻介绍所(容器)呢,说了你的标准后,有关人员就会将符合你意愿的另一个人的联系方式给你(由「主动拉取」的模式变成「被推」的都可以这么认为:比如「被动收到消息、接收事件」) 。——Spring IoC 原理及实现

loC 发展简介

image.png
好莱坞原则:「Don’t call us,we’ll call you」 -> 不要打电话给我,我会打电话给你(演员不要联系导演,导演会联系演员) -> us(需要的资源),you(系统、模块)-> 编程中的相关依赖不是自己读取,而是其它方来提供。

相关资料:

  1. 《The Mesa Programming Evviroment》
  2. 《Designing Reusable Classess》
  3. 《Object-Oriented Frameworks,A survey of methodological issues》
  4. 《Inversion of Control Containers and the Dependency Injection pattern》
  5. 《InversionOfControl》
  • 待阅读

loC 主要实现策略

wiki

从 wiki 上看,以下内容:

In object-oriented programming, there are several basic techniques to implement inversion of control. These are:

In an original article by Martin Fowler,[9] the first three different techniques are discussed. In a description about inversion of control types,[10] the last one is mentioned. Often the contextualized lookup will be accomplished using a service locator.——Inversio of Control

简单翻译下:
在面向对象编程中,有以下方式实现了 IoC:

  1. 服务定位模式(将服务调用过程进行了封装,形成一个抽象层)
  2. 依赖注入
    1. 构造器注入
    2. 参数注入
    3. setter 方法注入
    4. 接口注入
  3. 上下文依赖查询(Java Beans 中的 beancontext)
  4. 模板模式
  5. 策略模式

    Expert One-on-One J2EE Development without EJB

    在 《Expert One-on-One J2EE Development without EJB》中提到的主要实现策略:

  6. 依赖查找:容器提供了组件的回调和上下文的查找。组件使用容器的 API 来查找资源

  7. 依赖注入:组件不去查找,让容器去找。

    image.png

    后续有两者的使用案例和对比

loC 容器的职责

从 wiki 上看,以下内容:

Inversion of control serves the following design purposes:

  • To decouple the execution of a task from implementation.
  • To focus a module on the task it is designed for.
  • To free modules from assumptions about how other systems do what they do and instead rely on contracts.
  • To prevent side effects) when replacing a module.

Inversion of control is sometimes facetiously referred to as the “Hollywood Principle: Don’t call us, we’ll call you”.——Inversio of Control

简单翻译下:
IoC 有以下设计目的:

  • 将任务实现和执行解耦
  • 让模块只关注自己的设计的目的
  • 将模块从对其他系统如何工作的假设中解放出来,而不是依赖于契约(1.and 用在这里,是要表达「依赖契约」还是「不依赖契约」2. 契约:我理解为规范
  • 降低替换模块带来的副作用???

IoC 被戏称为「好莱坞原则」。

好莱坞原则:「Don’t call us,we’ll call you」 -> 不要打电话给我,我会打电话给你(演员不要联系导演,导演会联系演员) -> us(需要的资源),you(系统、模块)-> 编程中的相关依赖不是自己读取,而是其它方来提供。

image.png

loC 容器的实现

image.png

传统 loC 容器实现

image.png

相关代码:
Person:

  1. public class Person {
  2. String name;
  3. Integer age;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public Integer getAge() {
  11. return age;
  12. }
  13. public void setAge(Integer age) {
  14. this.age = age;
  15. }
  16. }

BeanInfoDemo:

  1. /**
  2. * {@link java.beans.BeanInfo} 演示示例
  3. *
  4. * @author mindartisan.blog.csdn.net
  5. * @date 2021/12/17
  6. */
  7. public class BeanInfoDemo {
  8. public static void main(String[] args) throws IntrospectionException {
  9. // BeanInfo 接口可以看到自己的类的一些方法、属性、事件等信息
  10. System.out.println("不使用 stopclass");
  11. // 1. 此处的方法会输出以下内容:「name=class」和 Object 类有关,Object 有个 getClass 方法
  12. // java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.mindartisan.ioc.java.beans.Person.getAge(); writeMethod=public void com.mindartisan.ioc.java.beans.Person.setAge(java.lang.Integer)]
  13. // java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
  14. // java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.mindartisan.ioc.java.beans.Person.getName(); writeMethod=public void com.mindartisan.ioc.java.beans.Person.setName(java.lang.String)]
  15. BeanInfo beanInfo1 = Introspector.getBeanInfo(Person.class);
  16. Arrays.stream(beanInfo1.getPropertyDescriptors())
  17. .forEach(propertyDescriptor -> {
  18. System.out.println(propertyDescriptor);
  19. });
  20. System.out.println("使用 stopclass");
  21. // 2. 此处的方法会输出以下内容:原因和 Object 类是所有类父类,
  22. // java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.mindartisan.ioc.java.beans.Person.getAge(); writeMethod=public void com.mindartisan.ioc.java.beans.Person.setAge(java.lang.Integer)]
  23. // java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.mindartisan.ioc.java.beans.Person.getName(); writeMethod=public void com.mindartisan.ioc.java.beans.Person.setName(java.lang.String)]
  24. BeanInfo beanInfo2 = Introspector.getBeanInfo(Person.class,Object.class);
  25. Arrays.stream(beanInfo2.getPropertyDescriptors())
  26. .forEach(propertyDescriptor -> {
  27. System.out.println(propertyDescriptor);
  28. });
  29. // 3. PropertyDescriptor 可以添加属性编辑器 PropertyEditor
  30. BeanInfo beanInfo3 = Introspector.getBeanInfo(Person.class,Object.class);
  31. Arrays.stream(beanInfo3.getPropertyDescriptors())
  32. .forEach(propertyDescriptor -> {
  33. Class<?> propertyType = propertyDescriptor.getPropertyType();
  34. String propertyTypeName = propertyType.getName();
  35. if ("age".equals(propertyTypeName)) {
  36. // 用自定义的属性编辑器
  37. propertyDescriptor.setPropertyEditorClass(StringToIntegerPropertyEditor.class);
  38. // 构建一个属性编辑器,TODO:视频中有了相关内容进行完善
  39. // propertyDescriptor.createPropertyEditor();
  40. }
  41. });
  42. }
  43. static class StringToIntegerPropertyEditor extends PropertyEditorSupport {
  44. @Override
  45. public void setAsText(String text) {
  46. Integer value = Integer.valueOf(text);
  47. setValue(value);
  48. }
  49. }
  50. }

轻量级 loC 容器

特性

A container that can manage application code. -> 轻量级容器可以管理我的应用代码(不是 SVN 或者 Git 的版本管理) A container that is quick to start up. -> 能快速启动 A container that doesn’t require any special deployment steps to deploy objects within it. -> 不需要任何特殊部署步骤就可以在其中部署对象。 A container that has such a light footprint and minimal API dependencies that it can be run in a variety of environments. -> 占用空间少、对 API 依赖少、可以在任何环境运行 A container that sets the bar for adding a managed object so low in terms of deployment effort and performance overhead that it’s possible to deploy and manage fine-grained objects, as well as coarse-grained components. -> 在部署和性能方面设置的标准低,能够管理部署粗粒度和细粒度的组件。 ——《Expert One-on-One J2EE Development without EJB》 Chapter 6. Lightweight Containers and Inversion of Control

好处

Escaping the monolithic container -> 摆脱了大型的单体应用 Maximizing code reusability -> 最大程度的代码复用 Greater object orientation -> 更加面向对象 Greater productivity -> 效率高 Better testability -> 可测性更强 ——《Expert One-on-One J2EE Development without EJB》 Chapter 6. Lightweight Containers and Inversion of Control

依赖查找 VS 依赖注入

类型 依赖处理 实现便利性 代码侵入性 API 依赖性 可读性
依赖查找 主动获取 繁琐 侵入业务逻辑 依赖容器 API 良好
依赖注入 被动提供 简单 低侵入 不依赖容器 API 一般

构造器注入 VS Setter 注入

样例

构造器

beans-constructor-injection

  1. public class SimpleMovieLister {
  2. // the SimpleMovieLister has a dependency on a MovieFinder
  3. private final MovieFinder movieFinder;
  4. // a constructor so that the Spring container can inject a MovieFinder
  5. public SimpleMovieLister(MovieFinder movieFinder) {
  6. this.movieFinder = movieFinder;
  7. }
  8. // business logic that actually uses the injected MovieFinder is omitted...
  9. }

Setter

beans-setter-injection

  1. public class SimpleMovieLister {
  2. // the SimpleMovieLister has a dependency on the MovieFinder
  3. private MovieFinder movieFinder;
  4. // a setter method so that the Spring container can inject a MovieFinder
  5. public void setMovieFinder(MovieFinder movieFinder) {
  6. this.movieFinder = movieFinder;
  7. }
  8. // business logic that actually uses the injected MovieFinder is omitted...
  9. }

对比

对比一:Spring Doc

The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.

——Constructor-based or setter-based DI?

简单翻译下:
Spring 团队推荐用构造器注入,因为可以将你的组件实现为不可变的对象而且保证需要依赖不为空。此外,如果构造器的参数过多是不太好的。暗示类有太多的职责,也应该被重构。
Setter 注入应该应用于「可以被设置合理默认值的可选依赖」注入的场景,否则必须在使用依赖的地方进行非空的检查。Setter 注入的一个好处是:可以是该类的对象重新配置或者重新注入。

对比二:《Expert One-on-One J2EE Development without EJB》

来源:Chapter 6. Lightweight Containers and Inversion of Control 章节

Setter 注入优点

  1. JavaBean properties are well supported in IDEs. -> JavaBean 的 properties 被 IDEs 支持
  2. JavaBean properties are self-documenting. -> 见名知意,不用太多的文档解释
  3. JavaBean properties are inherited by subclasses without the need for any code. -> 属性能够被子类继承
  4. It’s possible to use the standard JavaBeans property-editor machinery for type conversions if necessary. -> 能用标准的 property-editor 完成类型转换
  5. Many existing JavaBeans can be used within a JavaBean-oriented IoC container without modification. -> 很多 JavaBean 不用更改就可以使用 JavaBeans 的容器(BeanContext)
  6. If there is a corresponding getter for each setter (making the property readable, as well as writable), it is possible to ask the coponent for its current configuration state.This is particularly useful if we want to persist that state: for example, in an XML form or in a database. With Constructor Injection, there’s no way to find the current state. -> 由于 getter 和 setter 想关联,所以我们能够知道当前容器的状态。这个特性在我们想将状态持久化的时候很有用。比如说在数据库中有个 XML 表单,没法用构造器注入发现当前状态。
  7. Setter Injection works well for objects that have default values, meaning that not all properties need to be supplied at runtime. -> 可以方便的设置默认值,意味着不是所有的属性都要在运行时提供默认值。

Setter 注入缺点

  1. The order in which setters are called is not expressed in any contract. Thus, we sometimes need to invoke a method after the last setter has been called to initialize the component. Spring provides the org. springframework. beans. factory. InitializingBean interface for this; it also provides the ability to invoke an arbitrary init method. However, this contract must be documented to ensure correct use outside a container. -> 为属性赋值(set)的顺序没法确定,没有约束。有时候,我们是需要有顺序的。Spring 提供了 InitializingBean 接口来让我们达成「有序 set」的目的,也提供了调用任意一个 init 方法的能力。然而,这个约束必须在文档中说明才能保证正常使用。
  2. Not all the necessary setters may have been called before use. The obiect can thus be left partially configured. -> 不是所有的 setter 方法都有必要。

构造器注入优点

  1. Each managed object is guaranteed to be in a consistent state-fully configured-before it can be invoked in any business methods. This is the primary motivation of Constructor Iniection. However, it is possible to achieve the same result with JavaBeans via dependency checking, as Spring can optionally perform.)There’s no need for initialization methods. -> 在被业务方法调用前能保证每个被管理的对象有一致的状态。这是构造器注入的主要目的(然而,可以通过 JavaBeans 的依赖检查能实现相同的效果,因为 Spring 可以选择执行)。没必要用初始化方法。
  2. There may be slishtly less code than results from the use of multiple JavaBean methods, although will be no difference in couplexity. -> 看起来比使用多个 JavaBean 方法来实现的代码少。复杂度也差不多。

构造器注入缺点

  1. Although also a Java-language feature, multi-argument constructors are probably less common in existing code than use of JavaBean properties. -> 尽管构造器也是 Java 语言特性,但是多参数构造函数不如 JavaBean 属性使用常见
  2. Java constructor arsuments don’t have names visible by introspection. -> 构造器参数没名字,只有类型(Java 8 具体类的方法参数名称会保留了)
  3. Constructor arsument lists are less well supported by IDEs than JavaBean setter methods. -> IDEs 对 JavaBean 的 setter 方法支持更好
  4. Long constructor argument lists and large constructor bodies can become unwieldy. -> 构造器参数太多看起来笨
  5. Concrete inheritance can become problematic. -> 具体的继承会成为问题
  6. Poor support for optional properties, compared to JavaBeans Unit testing can be slisghtly more difficult -> 对可选属性支持差,相对于 JavaBeans 来说,单元测试会更难
  7. When collaborators are passed in on object construction, it becomes inpossible to change the reference held in the obiect. -> 直译:当协作者被传入到对象的构造器中后,就不能更改对象中保存的引用了。