美文网首页
Spring IOC容器

Spring IOC容器

作者: 陈菲TW | 来源:发表于2020-04-21 13:10 被阅读0次

一、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的方法),然后视图裁决、解析等等工作。

相关文章

网友评论

      本文标题:Spring IOC容器

      本文链接:https://www.haomeiwen.com/subject/oztlihtx.html