控制反转 依赖注入 基本概念 与 Spring IOC 源码学习

控制反转 依赖注入 基本概念 与 Spring IOC 源码学习

1. Background

1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”,还有些书籍翻译成为“控制反向”或者“控制倒置”。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦

2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。

举个简单例子:
Person(人)每天都要吃早餐(食物)。我们可以用如下程序表示

public class Person {
    public void eat() {
        Food food = new food();
        System.out.println("I eat food:{}", food.toString());
    }
}

在我们吃饭之前必须先new food()(做饭),要不然就吃不上。
Ioc 会怎么样做呢

public class Person {
    private Food food;
 public void eat() {
        System.out.println("I eat food:{}", food.toString());
 }
}

我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

2. IOC实现方式

接下来的问题是如何将依赖的对象准备好呢(依赖注入),常用的有两种方式:构造方法注入setter注入

  • 构造器注入

    public Person(Food food) {
        this.food = food;
    }
    
  • setter注入

    public void setFood(Food food) {
        this.food = food;
    }
    

3. Spring IOC

Spring IOC的初始化过程:

IOC要实现却并不那么容易。它需要一系列技术要实现。首先它需要知道服务的对象是谁,以及需要为服务对象提供什么样的服务。提供的服务指:要完成对象的构建(即把饭做好),将其送到服务对象即完成对象的绑定(即把饭端到我面前)。

IOC需要实现两个技术:

  • 对象的构建
  • 对象的绑定

对于这两个方面技术的实现具有很多的方式:硬编码(Ioc 框架都支持),配置文件(重点),注解(简洁)。但无论哪种方式都是在Ioc容器里面实现的(我们可以理解为一个大池子,里面躺着各种各样的对象,并能通过一定的方式将它们联系起来)spring提供了两种类型的容器,一个是BeanFactory,一个是ApplicationContext(可以认为是BeanFactory的扩展),下面我们将介绍这两种容器如何实现对对象的管理。

3.1 BeanFactory

如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的 IoC容器选择。

我们先来看一下BeanFactory类的关系图(如下所示)

有三个很重要的部分:

  • BeanDefinition 实现Bean的定义(即对象的定义),且完成了对依赖的定义

  • BeanDefinitionRegistry,将定义好的bean,注册到容器中(此时会生成一个注册码)

  • BeanFactory 是一个bean工厂类,从中可以取到任意定义过的bean

最重要的部分就是BeanDefinition,它完成了Bean的生成过程。一般情况下我们都是通过配置文件(xml,properties)的方式对bean进行配置,每种文件都需要实现BeanDefinitionReader,因此是reader本身现了配置文字到bean对象的转换过程。

Bean 生命周期

Bean的生成大致可以分为两个阶段:容器启动阶段bean实例化阶段

容器启动阶段

  • 加载配置文件(通常是xml文件)

  • 通过reader生成beandefinition

  • beanDefinition注册到beanDefinitionRegistry

bean实例化阶段

当某个bean 被 getBean()调用时, bean需要完成初时化,以及其依赖对象的初始化如果bean本身有回调,还需要调用其相应的回调函数。从上面我们也可以知道,beanDefinition(容器启动阶段)只完成bean的定义,并未完成初始化。初始是通过beanFactory的getBean()时才进行的。

Spring IOC在初始化完成之后,给了我们提供一些方法,让我们来改变一些bean的定义

  1. org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:使我们可能通过配置文件的形式,配置一些参数
  2. PropertyOverrideConfigurer :则可以覆盖原本的bean参数
  3. CustomEditorConfigurer:则提供类型转换支持(配置文件都是string,它需要知道转换成何种类型)

Bean的初始化过程:

如果你认为实例化的对象就是通过我们定义的类new出来的就错了,其实这里用到了AOP机制,生成了其代理对象(通过反射机制生成接口对象,或者是通过CGLIB生成子对象),具体可以参见在上一篇博客

bean的具体装载过程是由beanWrapper实现的,它继承了PropertyAccessor (可以对属性进行访问)、PropertyEditorRegistryTypeConverter接口 (实现类型转换,就上前面说的)。

完成设置对象属性之后,则会检查是否实现了Aware类型的接口,如ApplicationContextAware;如果实现了,则主动加载。

BeanPostprocessor 可以帮助完成在初始化bean之前或之后 帮我们完成一些必要工作,比如我们在连接数据库之前将密码存放在一个加密文件,当我们连接数据库之前,需要将密码进行加载解密。只要实现相应的接口即可, 下面是BeanPostprocessor 接口定义:

public interface BeanPostProcessor {

   /**
    * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
    * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
    * or a custom init-method). The bean will already be populated with property values.
    * The returned bean instance may be a wrapper around the original.
    * @param bean the new bean instance
    * @param beanName the name of the bean
    * @return the bean instance to use, either the original or a wrapped one; if
    * {@code null}, no subsequent BeanPostProcessors will be invoked
    * @throws org.springframework.beans.BeansException in case of errors
    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
    */
   Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

   /**
    * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
    * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
    * or a custom init-method). The bean will already be populated with property values.
    * The returned bean instance may be a wrapper around the original.
    * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
    * instance and the objects created by the FactoryBean (as of Spring 2.0). The
    * post-processor can decide whether to apply to either the FactoryBean or created
    * objects or both through corresponding {@code bean instanceof FactoryBean} checks.
    * <p>This callback will also be invoked after a short-circuiting triggered by a
    * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
    * in contrast to all other BeanPostProcessor callbacks.
    * @param bean the new bean instance
    * @param beanName the name of the bean
    * @return the bean instance to use, either the original or a wrapped one; if
    * {@code null}, no subsequent BeanPostProcessors will be invoked
    * @throws org.springframework.beans.BeansException in case of errors
    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
    * @see org.springframework.beans.factory.FactoryBean
    */
   Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

在完成postProcessor之后,则会看对象是否定义了InitializingBean 接口,如果是,则会调用其afterProper-tiesSet()方法进一步调整对象实例的状态,这种方式并不常见。spring还提供了另外一种指定初始化的方式,即在bean定义中指定init-method 。

当这一切完成之后,还可以指定对象销毁的一些回调,比如数据库的连接池的配置,则销毁前需要关闭连接等。相应的可以实现DisposableBean 接口或指定destroy-method

3.2 ApplicationContext

ApplicationContextBeanFactory 的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等。

ApplicationContext 所管理 的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于 BeanFactory来说,ApplicationContext 要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之 BeanFactory 也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext 类型的容器是比较合适的选择。

具体差异

  1. bean的加载方式
    BeanFactory提供BeanReader来从配置文件中读取bean配置。相应的ApplicationContext也提供几个读取配置文件的方式:

    • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径

    • ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。

    • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

    • AnnotationConfigApplicationContext

    • ConfigurableWebApplicationContext

  2. ApplicationContext采用的非懒加载方式。

    它会在启动阶段完成所有的初始化,并不会等到getBean()才执行。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

  1. ApplicationContext 还额外增加了三个功能:ApplicationEventPublisher, ResourceLoader, MessageResource,以下为具体解释:
3.2.1 ResourceLoader

ResourceLoader并不能将其看成是Spring独有的功能,spring IOC只是借助于ResourceLoader来实现资源加载。也提供了各种各样的资源加载方式:

  • DefaultResourceLoader 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类 型资源并返回。否则, 尝试通过URL,根据资源路径来定位资源

  • FileSystemResourceLoader 它继承自Default-ResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以 FileSystemResource类型返回

  • ResourcePatternResolver
    批量查找的ResourceLoader

spring与ResourceLoader之间的关系

所有ApplicationContext的具体实现类都会直接或者间接地实现AbstractApplicationContext, AbstactApplicationContext 依赖了DeffaultResourceLoader, ApplicationContext继承了ResourcePatternResolver,所到头来ApplicationContext的具体实现类都会具有DefaultResourceLoaderPathMatchingResourcePatterResolver的功能。这也就是会什么ApplicationContext可以实现统一资源定位。

3.2.2 ApplicationEventPublisher Spring事件

Spring的文档对Event的支持翻译之后描述如下:

ApplicationContext通过ApplicationEvent类和ApplicationListener接口进行事件处理。 如果将实现ApplicationListener接口的bean注入到上下文中,则每次使用ApplicationContext发布ApplicationEvent时,都会通知该bean。 本质上,这是标准的观察者设计模式。

  1. ApplicationEvent:继承自EventObject,同时是spring的application中事件的父类,需要被自定义的事件继承。

  2. ApplicationListener:继承自EventListener,spring的application中的监听器必须实现的接口,需要被自定义的监听器实现其onApplicationEvent方法

  3. ApplicationEventPublisherAware:在spring的context中希望能发布事件的类必须实现的接口,该接口中定义了设置ApplicationEventPublisher的方法,由ApplicationContext调用并设置。在自己实现的ApplicationEventPublisherAware子类中,需要有ApplicationEventPublisher属性的定义。

  4. ApplicationEventPublisher:spring的事件发布者接口,定义了发布事件的接口方法publishEvent。因为ApplicationContext实现了该接口,因此spring的ApplicationContext实例具有发布事件的功能(publishEvent方法在AbstractApplicationContext中有实现)。在使用的时候,只需要把ApplicationEventPublisher的引用定义到ApplicationEventPublisherAware的实现中,spring容器会完成对ApplicationEventPublisher的注入。

3.2.3 MessageSource

ApplicationContext接口扩展了MessageSource接口,因而提供了消息处理的功能(i18n或者国际化)。与HierarchicalMessageSource一起使用,它还能够处理嵌套的消息。

当一个ApplicationContext被加载时,它会自动在context中查找已定义为MessageSource类型的bean。此bean的名称须为messageSource。如果找到,那么所有对上述方法的调用将被委托给该bean。否则ApplicationContext会在其父类中查找是否含有同名的bean。如果有,就把它作为MessageSource。如果它最终没有找到任何的消息源,一个空的StaticMessageSource将会被实例化,使它能够接受上述方法的调用。

Spring目前提供了两个MessageSource的实现:ResourceBundleMessageSourceStaticMessageSource。它们都继承NestingMessageSource以便能够处理嵌套的消息。StaticMessageSource很少被使用,但能以编程的方式向消息源添加消息。ResourceBundleMessageSource会用得更多一些.

4. 举个栗子

4.1 注解扫描

<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
</beans>

4.2 component/service/controller注解

@Component
public class Person {
    @Resource
    private Food food;

    public void setFood(Food food) {
        this.food = food;
    }
}

4.3 bean的前置后置

@Component
public class Person {
    @Resource
    private Food food;

    public setFood(Food food) {
        this.food = food;
    }

    @PostConstruct
    public void wash() {
        System.out.println("饭前洗手");
    }

    @PreDestroy
    public void brush() {
        System.out.println("饭后刷牙");
    }
}

Ref

  1. https://www.cnblogs.com/wang-meng/p/5597490.html
  2. https://javadoop.com/post/spring-ioc
  3. https://yikun.github.io/2015/05/29/Spring-IOC核心源码学习/
  4. https://juejin.im/post/593386ca2f301e00584f8036
  5. http://blog.sina.com.cn/s/blog_85d71fb70101cyp5.html MessageSource
  6. https://www.cnkirito.moe/event-1/ Spring事件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值