IoC容器和依赖注入

IoC容器和依赖注入是Spring框架最为核心的功能,各种实现其他功能的框架都是在此基础上发展而来的。

核心概念

什么是IoC(Inverse of Control)容器和依赖注入呢?这两个概念,实际上是面向对象编程发展到比较高级的阶段后提出的,其本质就是将对象的组装和实例化通过声明的方式,将实例化的工作交给Spring来管理。

我们可以通过一个例子来理解:假设我们有一个MyTool组件,其中实现了一些功能,然而,为了保证代码可复用性,我们的MyTool必须依赖一个ToolConfig配置对象来启动,其中包含了MyTool启动的必要参数。

ToolConfig.java

package com.gacfox.demo;

public class ToolConfig {
    private String mode;

    public String getMode() {
        return mode;
    }

    public void setMode(String mode) {
        this.mode = mode;
    }
}

MyTool.java

package com.gacfox.demo;

public class MyTool {
    private ToolConfig toolConfig;

    public ToolConfig getToolConfig() {
        return toolConfig;
    }

    public void setToolConfig(ToolConfig toolConfig) {
        this.toolConfig = toolConfig;
    }

    public void handleWork() {
        System.out.println("I'm working on " + toolConfig.getMode() + "mode...");
    }
}

如果不使用Spring框架,我们可能会编写类似如下代码装配使用MyTool组件:

// 创建配置组件
ToolConfig toolConfig = new ToolConfig();
toolConfig.setMode("promiscuous");
// 创建组件并载入配置
MyTool myTool = new MyTool();
myTool.setToolConfig(toolConfig);
// 具体使用组件的功能
myTool.handleWork();

乍一看上去好像也没什么不妥,但一旦我们的项目复杂起来,情况就不一样了。手动管理这些组件对象,需要我们手动装配对象间的依赖关系,还要手动进行单例、多例模式的编写(上面编写的是多例模式,维护单例模式会更加复杂),随着项目的规模越来越庞大,经手的人越来越多,整个架构就会变得越来越难以维护。

这时,Ioc和依赖注入的概念自然就出现了。我们将对象统一交给IoC容器来管理,只需编写声明式配置告诉IoC容器,如何将这个对象和其依赖对象实例化出来,我们需要的时候,直接从容器中取出的就是装配好的对象,这就方便太多了!

下面是通过Spring的声明方式装配MyTool组件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="toolConfig" class="com.gacfox.demo.ToolConfig">
        <property name="mode" value="promiscuous" />
    </bean>

    <bean id="myTool" class="com.gacfox.demo.MyTool">
        <property name="toolConfig" ref="toolConfig" />
    </bean>
</beans>

取用MyTool组件:

// 启动Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过Bean名获取对象
MyTool myTool = context.getBean("myTool", MyTool.class);
// 具体使用组件的功能
myTool.handleWork();

其实我们可以发现,我们代码分为了两个部分:xml中编写的配置指定了如何装配SpringBean,而具体的装配操作则是交给Spring框架来执行了;我们实际Java代码中,直接从Spring的容器中取出对象,就可以直接使用了。

BeanFactory容器接口

BeanFactory即SpringBean工厂,我们通过声明式配置定义如何装配对象,那么实际上具体操作就是由BeanFactory完成的,从Spring容器中获取Bean,也是经由BeanFactory来实现。

BeanFactory对功能的接口划分十分详细直观,具体通过上图参考相关文档即可。

实际上,SpringBean定义在框架中抽象为了BeanDefinition对象,我们可以手动创建BeanDefinition并注册到BeanFactory中,这种用法在很多框架底层代码中十分常见。下面是例子代码:

package com.gacfox.demo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

/**
 * @author gacfox
 */
public class MyToolManager implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
        // 创建ToolConfig的BeanDefinition
        BeanDefinition toolConfigDef = BeanDefinitionBuilder
                .rootBeanDefinition(ToolConfig.class)
                .addPropertyValue("mode", "promiscuous")
                .getBeanDefinition();
        // 注册toolConfig
        beanFactory.registerBeanDefinition("toolConfig", toolConfigDef);
        // 创建myTool的BeanDefinition
        BeanDefinition myToolDef = BeanDefinitionBuilder
                .rootBeanDefinition(MyTool.class)
                .addPropertyReference("toolConfig", "toolConfig")
                .getBeanDefinition();
        // 注册myTool
        beanFactory.registerBeanDefinition("myTool", myToolDef);
    }
}

上面代码中,我们手动创建了BeanDefinition对象,并注册到了BeanFactory中。这和之前xml配置的效果相同。

注意这里获取BeanFactory的方式,MyToolManager实际也是一个SpringBean,由xml配置进行加载,该类实现了BeanFactoryPostProcessor接口,这个接口实际上是一个钩子函数,用于在BeanFactory初始化完成后进行一些处理。在底层框架功能开发中,我们会大量用到此类钩子函数。

这种写法在学习Spring的初级阶段大概不会遇到,然而,手动创建BeanDefinition实际上对底层功能开发非常有用,比如我们开发的一个框架需要通过注解方式加载Bean,那么我们就需要通过反射找到标注了注解的类,并手动创建BeanDefinition。

父子容器

BeanFactory容器可以具有父子关系,实际上我们日常开发中,SpringMVC的Bean就被创建在子容器中。父子容器具有如下关系:

  • 子容器可以访问父容器的Bean,反之则不行
  • 同一容器内不能有id相同的Bean,但子容器中可以有和父容器id相同的Bean

我们在进行一些底层功能开发时,可以创建一个单独的BeanFactory子容器,使得我们的Bean具有更加良好的封装性。

ApplicationContext应用上下文

ApplicationContext接口代表以BeanFactory容器为核心的当前整个Spring应用上下文,我们自己翻阅源码其实会发现ApplicationContext继承了包括BeanFactory在内的许多接口,它是一个更高级的抽象。

ApplicationContext是Spring框架的启动入口,我们之前使用过的ClassPathXmlApplicationContext就是用于从xml文件中启动Spring应用上下文。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。