学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,本文系统分析下Spring IOC核心原理。

文章目录

  • IOC基本概念
    • IoC是什么
    • IoC能做什么
    • IoC和DI
  • IOC容器的原理
  • IOC容器实现
    • BeanFactory框架基础设施
    • ApplicationContext面向开发应用
    • WebApplicationContext体系架构
  • Spring Bean作用域
  • Spring Bean生命周期
  • Spring依赖注入五种方式
  • 5种不同方式的自动装配
  • 本文小结

IOC基本概念

首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂。


IoC是什么

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了

用图例说明一下,传统程序设计如下图所示,都是主动去创建相关对象然后再组合起来:

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图所示:


IoC能做什么

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了

IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找


IoC和DI

DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入明确描述了“被注入对象依赖IoC容器配置依赖对象”。

看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。


IOC容器的原理

Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。其中Bean缓存池为HashMap实现。

1.根据Bean配置信息在容器内部创建Bean定义注册表
2.根据注册表加载、实例化bean、建立Bean与Bean之间的依赖关系
3.将这些准备就绪的Bean放到Map缓存池中,等待应用程序调用


IOC容器实现

BeanFactory框架基础设施

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;ApplicationContext 面向使用Spring 框架的开发者,几乎所有的应用场合我们都直接使用 ApplicationContext 而非底层的 BeanFactory。

BeanDefinitionRegistry 注册表

Spring 配置文件中每一个节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册BeanDefinition 对象的方法。

BeanFactory 顶层接口

位于类结构树的顶端 ,它最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的 Bean,BeanFactory 的功能通过其他的接口得到不断扩展:

ListableBeanFactory

该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数、获取某一类型Bean 的配置名、查看容器中是否包括某一 Bean 等方法;

HierarchicalBeanFactory 父子级联

父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。

ConfigurableBeanFactory

是一个重要的接口,增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;

AutowireCapableBeanFactory 自动装配

定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;

SingletonBeanRegistry 运行期间注册单例 Bean

定义了允许在运行期间向容器注册单实例 Bean 的方法;对于单实例( singleton)的 Bean 来说,BeanFactory 会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从IoC 容器的缓存中获取 Bean 实例。Spring DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用 HashMap 实现的缓存器,单实例的 Bean 以beanName 为键保存在这个 HashMap 中。

依赖日志框架

在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用 Log4J, 即在类路径下提供 Log4J 配置文件,这样启动Spring 容器才不会报错。


ApplicationContext面向开发应用

ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能 。ApplicationContext 继承了HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基础上,还通过多个其他的接口扩展了 BeanFactory 的功能。

  1. ClassPathXmlApplicationContext:默认从类路径加载配置文件
  2. FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
  3. ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
  4. MessageSource:为应用提供 i18n 国际化消息访问的功能;
  5. ResourcePatternResolver : 所 有 ApplicationContext 实现类都实现了类似于PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
  6. LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。
  7. ConfigurableApplicationContext 扩展于 ApplicationContext,它新增加了两个主要的方法: refresh()和 close(),让ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用 refresh()即可启动应用上下文,在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,而调用 close()则可关闭应用上下文。

ApplicationContext和BeanFactory不同之处在于

1.ApplicationContext会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcesso 和BeanFactoryPostProcessor后置器,并自动将它们注册到应用上下文中。而BeanFactory需要在代码中通过手工调用addBeanPostProcessor()方法进行注册。

2.ApplicationContext在初始化应用上下文的时候就实例化所有单实例的Bean。而BeanFactory在初始化容器的时候并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean。


WebApplicationContext体系架构

WebApplicationContext 是专门为 Web 应用准备的,它允许从相对于 Web 根目录的路径中装载配置文件完成初始化工作。从 WebApplicationContext 中可以获得ServletContext 的引用,整个 Web 应用上下文对象将作为属性放置到 ServletContext 中,以便 Web 应用环境可以访问 Spring 应用上下文。


Spring Bean作用域

Spring 3 中为 Bean 定义了 5 中作用域,分别为 singleton(单例)、prototype(原型)、request、session 和 global session,5 种作用域说明如下:

singleton:单例模式(多线程下不安全)

singleton:单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式。

prototype:原型模式每次使用时创建

prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。

Request:一次 request 一个实例

request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。

session

session:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁。

global Session

global Session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。


Spring Bean生命周期

1.实例化

实例化一个 Bean,也就是我们常说的 new。

2.IOC 依赖注入

按照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入。

3.setBeanName 实现

如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值

4.BeanFactoryAware 实现

如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory,setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean,只需在 Spring 配置文件中配置一个普通的 Bean 就可以)。

5.ApplicationContextAware 实现

如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更多的实现方法)

6.postProcessBeforeInitialization 接口实现-初始化预处理

如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应
用于内存或缓存技术。

7.init-method

如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法。

8.postProcessAfterInitialization

如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。
注:以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,所以一般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring 配置文件中也可以配置非 Singleton。

9.Destroy 过期自动清理阶段

当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用那个其实现的 destroy()方法;

10.destroy-method 自配置清理

最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。

11.@PostConstruct 和@PreDestroy

bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。


Spring依赖注入五种方式

依赖注入的方式有4种方式

属性注入–>通过setter()方法注入

 public class Id { private int id; public int getId() { return id;} public void setId(int id) { this.id = id; }
}

构造函数注入

 /*带参数,方便利用构造器进行注入*/ public CatDaoImpl(String message){ this. message = message; }

静态工厂方法注入

静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让 spring 管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过 spring 注入的形式获取。

实例工厂

实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先 new 工厂类,再调用普通的实例方法:

基于注解

如果应用中 Bean 的数量较多,基于 XML 配置文件实现 Bean 的装配会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。基于 Annotation(注解)的依赖注入就可以解决这个问题。

总的来说使用属性注入是比较灵活和方便的,这是大多数人的选择!在现在的spring boot项目中,xml已经很少使用,基本都被替换成了注解来进行注入


5种不同方式的自动装配

Spring 装配包括手动装配和自动装配,手动装配是有基于 xml 装配、构造方法、setter 方法等自动装配有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入。

1.no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。

2.byName:通过参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。

3.byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。

4.constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。

5.autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。

只用注解的方式时,注解默认是使用byType的!


本文小结

本文详细描述了Spring IOC相关的核心知识,帮你更好的掌握IOC知识。

Spring IOC核心原理分析相关推荐

  1. 还不懂spring IOC核心原理?200行代码带你手撸一个

    Spring做为Java企业级应用的开源开发框架,早已成为Java后端开发事实上的行业标准,无数的公司选择Spring作为基础的开发框架. 使用Spring框架的人一定都听过Spring的IoC(控制 ...

  2. Spring AOP核心原理分析

    "横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面.所谓"切面" ...

  3. Java程序员进阶——Spring依赖注入原理分析

    Spring依赖注入原理分析 下面谈谈Spring是如何实现反转模式IOC或依赖注入模式DI: 平时,我们需要生成一个对象,使用new语法,如一个类为A public class A{public v ...

  4. Spring Boot 核心原理与源码解析 - 目录

    准备重新写 SpringBoot 配置文件解析原理 , 先在这里把要写的内容记下来 Spring Boot 核心原理与源码解析 - 目录 1\何时解析\如何解析 application.propert ...

  5. RPC 实战与核心原理分析

    RPC 实战与核心原理分析 RPCX是一个分布式的Go语言的 RPC 框架,支持Zookepper.etcd.consul多种服务发现方式,多种服务路由方式, 例子 服务端 package maini ...

  6. 10. Spring IOC 底层原理

    Spring IOC 底层原理 如何通过 IOC 容器来创建对象: 创建 Maven 工程,在 pom.xml 中添加 Spring 框架相关的依赖: 新建实体类: 在 resources 目录下创建 ...

  7. Spring Boot(四):Spring Boot启动原理分析

    文章目录 Spring Boot启动原理分析 一.依赖导入原理 二.Spring Boot包扫描原理 三.Spring Boot自动配置原理 Spring Boot启动原理分析 一.依赖导入原理 父项 ...

  8. spring ioc di 原理解析

    spring ioc原理(看完后大家可以自己写一个spring) 控制反转/依赖注入 其实这个Spring架构核心的概念没有这么复杂,更不像有些书上描述的那样晦涩.Java程序员都知道:java程序中 ...

  9. Spring Boot核心原理-自动配置

    作者简介:朱清,毕业于电子科技大学,现任职冰鉴科技高级研发经理,主导冰鉴风控系统架构设计和研发. 之前在公司内部推行spring boot时,有同事跟我提到过,感觉换到spring boot这个框架后 ...

最新文章

  1. 计算机c1 c语言答题,全国计算机级考试二级C语言上机答题技巧.doc
  2. Docker(八):Docker Compose
  3. VMware 使用 OVF 模版部署虚拟机
  4. 【C/C++】内存分配函数:malloc,calloc,realloc,_alloca
  5. .Net 4.0 (2)
  6. ORACLE同义词源库锁表导致目标库删除操作报ora 02055 02049 02063 06512
  7. 为Web程序员解毒:9个IE常见Bug的解决方案
  8. Android 8.0 targetsdkversion升级到26填坑
  9. android图灵机器人教程,简单的调用图灵机器人
  10. 关于DX中纹理平移的一个小问题
  11. opsforlist 存在贼覆盖_RedisTemplate集合使用说明-opsForList(二)
  12. 【Android】1.开发环境搭建
  13. devc运行窗口不显示_编写Qt多窗口程序
  14. 使用点滴字幕在线生成字幕文件
  15. 人工智能——单层感知器
  16. Nginx学习笔记(Docker版)-2
  17. 多项式展开的逆过程的MATLAB实现
  18. 实验: 写一个.msstyles皮肤测试程序
  19. 【面试题】【ES6】let和const命令 (面试必看)
  20. Paradigm Shifts in Kernel Programming 内核编程的范式转移

热门文章

  1. 阿里云再降价 数据库产品降20%
  2. 黑马程序员-Java-面向对象篇上《二》
  3. [ImportNew]Java线程面试题
  4. 【转载】移植FFMpeg到VC环境心得
  5. 浪潮服务器 NF 8460M4 的PM8060 RAID卡设置添加热备方法
  6. Vue实战:音乐播放器(一) 页面效果
  7. CentOS7 搭建Ambari-Server,安装Hadoop集群(一)
  8. 关于在不同版本和平台之间进行还原或复制的常见问题
  9. SIEM比以往更重要的5个原因
  10. OpenStack SFC 深入剖析