Spring IOC容器

一、IOC,inversion of control,控制反转

1. 如何理解IOC

以前对象的创建由程序员控制,什么时候创建,用哪个构造函数;控制反转把控制权交给容器。Spring通过DI实现控制反转。把Spring IoC容器比作一间餐馆,来到餐馆时直接招呼服务员:点菜!并不关心菜的原料是什么、如何把菜做出来。IoC容器也一样,你只需要告诉它需要某个bean,它就把对应的实例扔给你,无需关心bean是否依赖其他组件、如何初始化。

就像做菜需要菜谱和原材料,容器也需要记录各个对象以及依赖关系;BeanDefinition记录该信息;每个bean都有对应的BeanDefinition,保存包括class类型、是否是抽象类、构造方法和参数、其它属性等信息。向容器请求对象时,容器基于beanDefinition利用反射机制构建bean实例并返回。

BeanFactory和BeanDefinitionRegistry接口基于BeanDefinition完成bean的创建和管理,就像餐馆上菜的过程。BeanFactory包含getBean等管理bean的方法,BeanDefinitionRegistry包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition等管理BeanDefinition的方法。简单来说,bean管理就是创建beanDefinition并放入beanRegistry,使用bean的时候则调用beanFactory的getBean方法获得bean实例。

容器的工作分为容器启动阶段与bean实例化阶段;前者通过xml、注解加载bean的定义,构造为beanDefinition并放到BeanRegistry。后者在调用getBean或由于bean的依赖关系需要隐式调用getBean时触发,容器检查该bean是否实例化完成,完成则返回bean,否则根据beanDefinition进行初始化并返回。

我们通常使用的容器ApplicationContext,构建在BeanFactory之上,除了具有BeanFactory的能力外,还提供其他支持等。它在容器启动时全部完成所有bean的初始化和依赖注入操作。

2. 依赖注入DI

DI已经成为一个设计模式理念,让代码变得容易理解和测试。传统的做法是每个对象负责管理与自己协作的对象引用。DI的做法是由第三方(容器)负责管理对象的依赖关系,对象无需自行创建或者管理他们的依赖关系。

DI带来的最大收益就是松耦合。如果一个对象只通过接口来表明依赖关系,这种依赖就能在对象本身毫不知情的情况下,用不同的具体实现进行替换。这是是面向接口编程的好处。对依赖进行替换的最常用方法就是在测试的时候是用mock来实现。

3. 特殊情形

日常编码中可能会用到beanFactory的情形:BeanFactory可以动态的往spring容器中注入bean;假设我一个bean最开始不要求被spring实例化,在某个时机需要自己实例化,实例化好之后通过BeanFactory添加到spring容器当中:applicationContext.getBeanFactory().registerSingleton("bname",你的对象);

如何在工具类中使用bean?首先spring的DI限于在bean中进行依赖注入,进行bean的装配;其次,工具类通常不是bean,我们通常会使用其静态方法,而无需对工具类进行实例化;第三,非bean的类中使用其他bean可以通过applicationContext.getBean("beanName")的方式。

如果有多个bean可以注入如何选择,autowired首先选择类型一致的bean,如果有多个类型一致的bean则进一步使用qualifier通过bean的名字进行选择。但是现在好像已经不流行autowired了。(Q)

二、bean

1. bean的作用域scope

spring支持6种作用域,并支持自定义作用域,默认为singleton。prototype;request表示每个请求创建一个实例;session是在一个会话周期内有一个实例;application是指在一个servletContext中只有一个实例;此外还有websocket。prototype为多例模式,即每次从beanFactory获取bean都会创建一个新的bean。

1.1 单例

在Spring出现之前,我们需要用到某个实例就需要自己去调用构造函数把它构造出来,如果我们需要在多个地方使用该实例,要么多次构造,要么通过参数传递。造成大量的实例化对象,并且他们的生命周期可能就是从方法的调用开始到方法的调用结束为止,加大了GC回收的压力。了解设计模式的会想到使用单例模式的方式来解决问题。

采用单例模式的问题,第一是大量的代码需要改造、大量的重复单例模版代码;其次是单例模式需要把单例模版代码和业务代码放在一起。

解决方案类似于连接池,我们把连接放在一个池子里,需要建立连接则直接在池子中找空闲连接资源。这个池子的思想就是spring容器的原型。

2. bean的生命周期与扩展点

IoC 容器管理bean的生命周期,在不同阶段,Spring 提供了不同的扩展点来改变bean的命运。

1)容器启动阶段,BeanFactoryPostProcessor可以在实例化对象前,对注册到容器的BeanDefinition进行操作,如修改bean的属性等。有自定义需求需要实现BeanFactoryPostProcessor接口;另外,如果容器中有多个BeanFactoryPostProcessor,需实现Ordered接口,以保证BeanFactoryPostProcessor按序执行。

🌰:spring支持把配置属性统一放在properties文件,bean中用${}引用,从而方便配置属性的统一管理、及运维人员维护不同环境的配置,该功能通过PropertyPlaceholderConfigurer实现,该类实现BeanFactoryPostProcessor接口。容器启动阶段完成后,BeanDefinition保存的对象属性是占位符;执行PropertyPlaceholderConfigurer将占位符替换为properties文件中的属性值。第二阶段实例化bean时,bean定义中的属性值已完成替换。

2)对象实例化阶段,BeanPostProcessor处理容器内所有符合条件且已经实例化的对象。BeanFactoryPostProcessor处理beanDefinition;BeanPostProcessor处理bean实例。该接口提供两个方法:postProcessBefore/AfterInitialization。注解、AOP等功能的实现大量使用BeanPostProcessor,比如我可以自定义一个注解,同时实现BeanPostProcessor接口,在实现类中判断bean实例是否有该注解,如果有,则对bean实例执行响应操作。

Spring中有很多Aware接口,其作用就是在对象实例化完成后将Aware接口中规定的依赖注入到当前实例中。比如最常见的ApplicationContextAware接口,实现了这个接口的类都可以获取到一个ApplicationContext对象。当容器中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,容器会检测到之前注册到容器的ApplicationContextAwareProcessor,然后就会调用其postProcessBeforeInitialization()方法,检查并设置Aware相关依赖。(Q)

Bean的生命周期

三、JavaConfig与注解

最初Spring使用XML配置bean的定义与依赖关系,缺点是大量的XML文件使项目变得复杂且难以管理。后来,基于纯Java Annotation依赖注入框架 Guice出世,其性能明显优于采用XML方式的Spring,甚至有人认为, Guice可以取代Spring。正是这样的危机感,促使Spring推出JavaConfig项目,基于Java代码和Annotation注解来描述bean之间的依赖绑定关系。

@ComponentScan表示Spring会自动扫描所有通过注解配置的bean,并注册到IOC容器中。basePackages属性可指定扫描范围,不指定则默认从声明 @ComponentScan所在类的package进行扫描。正因为如此,SpringBoot的启动类都默认在 src/main/java下。

@Conditional表示仅当指定条件成立时才会创建对应bean,否则忽略bean的声明。

四、自动配置原理

Spring Boot根据依赖、自定义的bean、classpath下有没有某个类等因素来猜测你需要的bean,然后注册到IOC容器中。猜测的动作由EnableAutoConfigurationImportSelector进行,这个bean通过@EnableAutoConfiguration注入到容器。

SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,this.beanClassLoader)));

上述代码中,SpringFactoriesLoader搜索Jar包中的META-INF/spring.factories文件如下,找到指定名称的@Configuration类, 然后通过反射实例化这些类并注入到IOC容器。

spring.factories

以 DataSourceAutoConfiguration为例:

DataSourceAutoConfiguration.java

@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})表示仅当Classpath中存在DataSource或者EmbeddedDatabaseType类时才启用这个配置。@EnableConfigurationProperties(DataSourceProperties.class)将DataSource的默认配置类注入到IOC容器中.

DataSourceProperties.java

回顾一下,@EnableAutoConfiguration中导入了EnableAutoConfigurationImportSelector类,而这个类的selectImports()通过SpringFactoriesLoader得到了大量的配置类,而每一个配置类则根据条件化配置来做出决策,以实现自动配置。

五、启动引导:spring boot应用启动的秘密

1. SpringBoot整个启动流程的两个步骤

1)初始化一个SpringApplication对象;通过SpringFactoriesLoader找到 spring.factories文件中配置的 ApplicationContextInitializer和 ApplicationListener两个接口的实现类名称,以便后期构造相应的实例。 ApplicationContextInitializer的主要目的是在 ConfigurableApplicationContext做refresh之前,对ConfigurableApplicationContext实例做进一步的设置或处理。ConfigurableApplicationContext继承自ApplicationContext,其主要提供了对ApplicationContext进行设置的能力。

2)执行该对象的run方法

(1)通过SpringFactoriesLoader查找并加载所有的 SpringApplicationRunListeners;SpringApplicationRunListeners本质上是事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。

(2)创建并配置当前应用将要使用的Environment,如果是web项目就创建 StandardServletEnvironment,描述应用程序当前的运行环境,包括profile、properties,不同的环境(eg:生产环境、预发布环境)使用不同的配置文件,属性则从配置文件、环境变量、命令行参数获取。Environment准备好后,调用SpringApplicationRunListener的 environmentPrepared()方法,通知事件监听者:在整个应用的任何时候,都可以从Environment中获取资源。

(3)打印spring banner

(4)初始化ApplicationContext,将准备好的Environment设置给ApplicationContext;遍历所有ApplicationContextInitializer对已经创建好的ApplicationContext进一步处理;调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者ApplicationContext已经准备完毕;将所有的bean加载到容器中;调用SpringApplicationRunListener的 contextLoaded()方法,通知所有的监听者ApplicationContext已经装载完毕。

(5)调用ApplicationContext的 refresh()方法,完成IoC容器可用的最后一道工序:获取所有BeanFactoryPostProcessor在容器实例化对象前,对BeanDefinition进行额外操作。

这就是Spring Boot的整个启动流程,其核心就是在Spring容器初始化并启动的基础上加入各种扩展点,这些扩展点包括:ApplicationContextInitializer、ApplicationListener以及各种BeanFactoryPostProcessor等等。

2. Spring的事件监听机制

过去,事件监听机制多用于图形界面编程,比如点击按钮等操作被称为事件,而当事件触发时,应用程序作出一定的响应则表示应用监听了这个事件。服务器端,事件的监听机制更多的用于异步通知以及监控和异常处理。Java提供了实现事件监听机制的两个基础类:自定义事件类型EventObject、事件的监听器EventListener。EventListener有两个方法:onMethodBegin/End。事件监听中涉及事件发布者、事件、事件监听者。

Spring的ApplicationContext容器内部中的所有事件类型均继承自 AppliationEvent(EventObject子类),所有监听器都实现ApplicationListener接口(EventListener)。一旦在容器内发布ApplicationEvent,注册到容器的ApplicationListener就会对这些事件进行处理。Spring提供默认实现如: ContextClosedEvent表示容器在即将关闭时发布的事件, ContextRefreshedEvent表示容器在初始化或者刷新的时候发布的事件。

六、DispatchServlet

DispatcherServlet的init方法里面load了springmvc的配置信息,然后初始化了spring容器(调用了refresh方法),把controller的信息缓存了,比如映射信息;然后DispatcherServlet会拦截所有的请求,根据用户的请求信息通过缓存的映射信息找到对应的controller的对应方法,然后反射调用(其实底层的源码就是反射调用controller的方法),然后视图裁决、解析等等工作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • 2.1 我们的理念是:让别人为你服务 IoC是随着近年来轻量级容器(Lightweight Container)的...
    好好学习Sun阅读 2,696评论 0 11
  • 如果有看过SpringApplication.run()方法的源码,Spring Boot冗长无比的启动流程一定会...
    codingKeeper阅读 295评论 0 0
  • 概述 关于Spring Spring作为目前Java领域最热门的开发框架,是一个Java程序员最常用的框架,并且在...
    emi1997阅读 2,104评论 1 20
  • 1- IOC的概念 IOC:也即控制反转,DI即依赖注入,控制反转IOC和依赖注入DI其实就是同个概念的两个不同...
    zhanglbjames阅读 2,993评论 1 3
  • 月亮在我的头上 你在我的心上
    涤高阅读 60评论 0 3