前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >控制反转 依赖注入 基本概念 与 Spring IOC 源码学习

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

作者头像
大鹅
发布2021-06-16 17:19:16
6770
发布2021-06-16 17:19:16
举报

文章目录

控制反转 依赖注入 基本概念 与 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(人)每天都要吃早餐(食物)。我们可以用如下程序表示

代码语言:javascript
复制
public class Person {
    public void eat() {
        Food food = new food();
        System.out.println("I eat food:{}", food.toString());
    }
}

在我们吃饭之前必须先new food()(做饭),要不然就吃不上。

Ioc 会怎么样做呢

代码语言:javascript
复制
public class Person {
    private Food food;
 public void eat() {
        System.out.println("I eat food:{}", food.toString());
 }
}

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

2. IOC实现方式

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

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 接口定义:

代码语言:javascript
复制
public interface BeanPostProcessor {

   /**
    * Apply this BeanPostProcessor to the given new bean instance before 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 after 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.
    * 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.
    * 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也提供几个读取配置文件的方式:
代码语言:txt
复制
-  `FileSystemXmlApplicationContext`:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径
代码语言:txt
复制
-  `ClassPathXmlApplicationContext`:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
代码语言:txt
复制
-  `WebXmlApplicationContext`:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
代码语言:txt
复制
-  `AnnotationConfigApplicationContext`
代码语言:txt
复制
-  `ConfigurableWebApplicationContext`
  1. 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 注解扫描

代码语言:javascript
复制
<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注解

代码语言:javascript
复制
@Component
public class Person {
    @Resource
    private Food food;

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

4.3 bean的前置后置

代码语言:javascript
复制
@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事件
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-07-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 控制反转 依赖注入 基本概念 与 Spring IOC 源码学习
    • 1. Background
      • 2. IOC实现方式
        • 3. Spring IOC
          • 3.1 BeanFactory
          • 3.2 ApplicationContext
        • 4. 举个栗子
          • 4.1 注解扫描
          • 4.2 component/service/controller注解
          • 4.3 bean的前置后置
        • Ref
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档