IoC 是一种设计原则,降低代码控制流之间的耦合,本文将细说 IoC 的思想与基本实现,以及在 Spring 中的实现原理。

IoC 控制反转是什么?

Wiki

In software engineering, inversion of control (IoC) is a programming principle(n. 原则). 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.

是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。

Inversion of control is used to increase modularity of the program and make it extensible

Traditional realize (面向对象)传统方式的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    // 当 Class A 需要使用到 Class B 的对象 b 时,一般需要在 A 的代码中显式的 new 一个 B 的对象。伪代码如下
    Class B {
        say() {
            printf("Hello World");
        }
    }

    Class A {
        doSometing() {
            // prev 
            B b = new B(); // 显示声明
            b.say();
            // after 
        }
    }

IoC 的实现

实现控制反转主要有两种方式:依赖注入和依赖查找 。两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。

  1. 依赖注入(Dependency Injection,简称DI) 简单示例:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    Class B {
        say() {
            printf("Hello World");
        }
    }
    
    Class A {
        B b;
    
        doSomething() {
            // prev
            b.say(); // 在 A 对象实例创建的时候 b 由框架帮助注入
        }
    
        // 基于构造函数实现
        A(B b) {
            this.b = b;
        }
    }

    依赖注入的常见实现方式

    • 基于接口。实现特定接口以供外部容器注入所依赖类型的对象。
    • 基于 set 方法。实现特定熟悉的 public set 方法,来让外部容器调用传入所依赖类型的对象。
    • 基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
    • 基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。

优缺点: 基于接口,set方法, 构造函数的(public)方式实现会暴露不该暴露的方式,让其他对象非希望的对象注入依赖对象。

  1. 依赖查找 依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态

Spring IoC

Spring IoC 容器

Spring IoC 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans

Spring container

所谓的 IoC 容器就是指的 Spring 中 Bean 工厂里面的 Map 存储结构(存储了 Bean 的实例)。

Spring Factory 有哪些?
  1. ApplicationContext 接口

    • 实现了 BeanFactory 接口
    • 实现 ApplicationContext 接口的工厂,可以获取到容器中具体的 Bean 对象
  2. BeanFactory 工厂(是 Spring 框架早期的创建 Bean 对象的工厂接口)

    • 实现 BeanFactory 接口的工厂,也可以获取到 Bean 对象

两者区别

简而言之,BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多的企业层级的功能。ApplicationContext 是 BeanFactory 的一个完整超集

Spring IoC 运行原理

1
2
3
4
5
6
 public static void main(String[] args) {   
        ApplicationContext context = new FileSystemXmlApplicationContext(   
                "applicationContext.xml");   
        Animal animal = (Animal) context.getBean("animal");   
        animal.say();   
    } 

以上代码究竟让 Animal 实例化对象的呢?

试着手写一个 Spring Factory

Spring 的本质是一个 Bean 工厂,其管理 Bean 对象的生命周期。那么手写一个 Spring 需要一下几个部分

  1. Bean 对象 Class MyBean { // Bean ID private String id; // Bean class private String type; // Bean Property private Map<Stringm Object> properties = new HashMap<Stirng, Object>(); } 一个 Bean 对象包括 id, type, 和 properties
  2. Factory 对象

    1
    2
    3
    4
    5
    6
    7
    
    // 实现一个 bean 对象的管理接口 例如 getBean(String beanId) 上述中的通过 beanId 获取 bean 实例的方法
    Class MyFactory imp BaenFactory { 
    private Map<String, Object> beans = new HashMap<String, Object>;
        
    @override
    public getBean(String beanId) { return beads.get(beanId); }
    }
  3. BaenFactory 接口用来规定 Factory 对 Beans 的方法

  4. 配置 Bean 之间的依赖关系 例如 Spring 可以通过 xml 配置文件、注解 等方式获取程序员设置的 Bean 依赖关系。

接下来Spring 就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean 的 Id ,HasMap 的value是这个Bean,只有这样我们才能通过context.getBean("animal")这个方法获得Animal这个类。

Spring 实现 Beans 初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 (beanProperty.element("map") != null) {   
                     Map<String, Object> propertiesMap = new HashMap<String, Object>();   
                     Element propertiesListMap = (Element) beanProperty   
                             .elements().get(0);   
                     Iterator<?> propertiesIterator = propertiesListMap   
                             .elements().iterator();   
                     while (propertiesIterator.hasNext()) {   
                         Element vet = (Element) propertiesIterator.next();   
                         if (vet.getName().equals("entry")) {   
                            String key = vet.attributeValue("key");   
                            Iterator<?> valuesIterator = vet.elements()   
                                    .iterator();   
                            while (valuesIterator.hasNext()) {   
                                Element value = (Element) valuesIterator.next();   
                                if (value.getName().equals("value")) {   
                                    propertiesMap.put(key, value.getText());   
                                }   
                                if (value.getName().equals("ref")) {   
                                    propertiesMap.put(key, new String[] { value   
                                            .attributeValue("bean") });   
                                }   
                            }   
                        }   
                    }   
                    bean.getProperties().put(name, propertiesMap);   
                } 
Spring 如何实现依赖注入

其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。

  • 实例化一个类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
   public static Object newInstance(String className) {   
         Class<?> cls = null;   
         Object obj = null;   
         try {   
             cls = Class.forName(className);   
             obj = cls.newInstance();   
         } catch (ClassNotFoundException e) {   
             throw new RuntimeException(e);   
         } catch (InstantiationException e) {   
            throw new RuntimeException(e);   
        } catch (IllegalAccessException e) {   
            throw new RuntimeException(e);   
        }   
        return obj;   
    }  
  • 将这个类的依赖注入进去
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   public static void setProperty(Object obj, String name, String value) {   
         Class<? extends Object> clazz = obj.getClass();   
         try {   
             String methodName = returnSetMthodName(name);   
             Method[] ms = clazz.getMethods();   
             for (Method m : ms) {   
                 if (m.getName().equals(methodName)) {   
                     if (m.getParameterTypes().length == 1) {   
                         Class<?> clazzParameterType = m.getParameterTypes()[0];   
                        setFieldValue(clazzParameterType.getName(), value, m,   
                                obj);   
                        break;   
                    }   
                }   
            }   
        } catch (SecurityException e) {   
            throw new RuntimeException(e);   
        } catch (IllegalArgumentException e) {   
            throw new RuntimeException(e);   
        } catch (IllegalAccessException e) {   
            throw new RuntimeException(e);   
        } catch (InvocationTargetException e) {   
            throw new RuntimeException(e);   
        }   
} 
  • 注入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  if (value instanceof Map) {   
                 Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()   
                         .iterator();   
                 Map<String, Object> map = new HashMap<String, Object>();   
                 while (entryIterator.hasNext()) {   
                     Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();   
                     if (entryMap.getValue() instanceof String[]) {   
                         map.put((String) entryMap.getKey(),   
                                getBean(((String[]) entryMap.getValue())[0]));   
                    }   
                }   
                BeanProcesser.setProperty(obj, property, map);   
            }  

Bean 的循环依赖

Spring 的核心是管理 Beans 的生命周期,从而提高相应服务。那么在上例中就不得不提到 Bean 的循环依赖问题

循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用 new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错。

三种循环依赖的情况
  1. 构造器循环依赖: 这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常
  2. 单例模式下的setter循环依赖: 通过“三级缓存”处理循环依赖。
  3. 非单例循环依赖:无法处理。

根据 【试着手写一个 Spring Factory 】我们了解到一个 Bean 对象的初始化大致分为三步

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. 将实例添加到 beansMap 中

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。 接下来,我们具体看看spring是如何处理三种循环依赖的。

构造器循环依赖
1
this.singletonsCurrentlylnCreation.add(beanName)将当前正要创建的bean 记录在缓存中。 Spring 容器将每一个正在创建的 bean 的 ID 放在一个 “当前创建 bean 池” 中,因此如果在创建 bean 过程中发现自己已经在 “当前创建bean 池” 里时,将抛出 BeanCurrentlylnCreationException 异常表示循环依赖;而对于创建完毕的bean 将从 “ 当前创建bean 池” 中清除掉。
setter 循环依赖

Spring为了解决单例的循环依赖问题,使用了三级缓存。

1
2
3
4
5
6
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
非单例循环依赖

对于“prototype”作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓 存“prototype”作用域的bean ,因此无法提前暴露一个创建中的bean 。

相关链接

Wiki 英文 Wiki 中文 知乎 Spring IoC原理分析 掘金 手写一个 Spring Spring 循环依赖的问题