Spring IOC 概述

Posted endv

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IOC 概述相关的知识,希望对你有一定的参考价值。

Spring IOC 概述

IOC(Inversion of Control) 控制反转,也叫 DI(D_ependency injection_) 依赖注入。是一种设计思想。不过我并不同意所谓反转的说法,因为没有谁规定什么方式就是“标准”的,如果我把IOC作为“标准”,IOC就是“标准”自身,何来反转?不过我行文也是沿用官方的说法,使用IOC描述这个技术

IOC其实是一种组装的思想,最简单理解 IOC 的方法,就是我们的组装电脑

  1. 主板,硬盘,显卡,CPU厂商们先定义好一个个插口。

  2. 然后主板厂商就在他的主板上面预留位置。比如 A 插口是 留给硬盘的,B插口是留给 CPU的。注意,主板生产完成时,硬盘和CPU并没有在主板上,而是空的,不过主板已经写好好了给 CPU 和 硬盘 供电的 功能。

  3. 我们的工作就是根据主板的插口来找件,装配,而不是先买CPU、内存、硬盘再去选主板,这就是IOC ,主板需要插什么接口的,我们就去获得相关的零件,这样整个电脑就可以工作。

首先,我们必须要知道,没有 Spring ,我们照样可以做所谓的 IOC,就是 主物预留插口 -> 按插口找件,组装。其实这在我们生活中随处可见。

那么,我们为什么要使用 Spring 呢,我们组装电脑,就要获得关于这个电脑的所有部件,比如主板、电源、内存等等,我们自己当然不可能凭空造出来,但是我们可以上商城购买,我们只需要关心接口对不对,而不用关心他是怎么生产的。那么,Spring 的角色就是一个商城。

我们可以把 Spring IOC 看成一个全产业链的商城,他会把产业链中的第一层原料开始,每一层都登记进入商城。在这个商城里面,我们可以买到任何层级的零件。之后,我们就可以自由的控制一切:如果我们是厂商,我们可以买到我们想要的零件。如果我们是消费者,想组装电脑,就可以买到主板,硬盘和CPU。如果我们有了机房,就可以直接买到若干个电脑。甚至,我们有一个国家,就可以在上面买到关于这个国家的一切。我们再不用关心东西是怎么来的,我们只管买即可。东西生产由其他人负责。而Spring IOC做的就是整个产业链从头到尾的组装,上架工作。

Spring IOC的好处也是众说纷纭,我也没有找到很有说服力的解释,姑且写一些在下面:

  1. 轻松实现面向接口编程,中心管理,按需取用,各个环节完全解耦,比如,网站环境和测试环境差异巨大,也可以轻松切换。

  2. 可以监听和控制所管理对象的生命周期,并且执行相关的操作

  3. 因为可以控制对象的生命周期,所以可以轻松通过 AOP 进行对象增强

  4. 轻松整合整个 Java 生态链,对于开发者来说,整合是轻松友好的。

Spring IOC 用法解析

随着 SpringBoot 的潮流, 我们坚持只使用注解配置 Spring IOC

SpringIOC使用例子

maven 配置依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>

 

spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来。

定义一个接口 (类比的话,就是主物上面的一个接口)

1
2
3
public interface MessageService 
String getMessage();

 

定义接口实现类(类比的话,就是零件自身),并且使用注解注册到 Spring 商城

1
2
3
4
5
6
7
@Component("messageService")
public class MessageServiceImpl implements MessageService

public String getMessage()
return "hello world";


使用: (类比的话,就是从商城买东西) (这里没有使用依赖注入(自动装配),下面会介绍)

1
2
3
4
5
6
7
8
9
10
// Main.java
@ComponentScan // 非常重要,会扫描包下所有注解
public class Main
public static void main(String[] args)
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
MessageService messageService = (MessageService) context.getBean("messageService");
System.out.println(messageService.getMessage());
// 输出 Hello, World


 

SpringIOC 登记方式

Spring中管理的对象都叫 Bean,就像商城里面的一种商品。Spring 管理的对象默认是单例的,也就是一种商品只有一件,不过可以重复购买(注入)。下面,我们说一下如何上架这些商品。

普通类的对象登记

  • @Component 通用的登记方式,以下的注解都包含这个注解的功能,并且可能有额外的含义

  • @Service 通常用于服务层

  • @Controller 通常用于控制层

  • @Repository 通常用于数据库仓库层

  • 后面加(“”) 定义 bean 的名字,也可以不定义自动由 Spring 生成。

例子:

1
2
3
4
5
6
7
@Component("messageService")
public class MessageServiceImpl implements MessageService

public String getMessage()
return "hello world";


 

工厂创建的对象登记

我们有时候想通过一个工厂的方式,根据传入不同对象,生成不同的对象,并登记到 Spring,我们要这么做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class DataConfig
@Bean("aSource") // 配置文件来源,通常用 properties 文件定义
public DataSource source()
DataSource source = new RedisProperties();
source.setHost("localhost");
source.setPassword("123");
return source;

@Bean(name = "redisTemplate")
 public RedisTemplate redisTemplate(@Qualifier("aSource") DataSource source)
return super.stringRedisTemplate(source);
 

 

我们现在就注册到了一个 redisTemplate 的 Bean,是通过我们的配置文件生产的。
其中有这些注解

  • @Configuration 说明这是个配置类

  • @Bean 说明这个函数是用来创建 Bean 的

  • @Qualifier 说明我们引入哪一个 Bean 作为 传入参数。

  • 我们可以写多组这样的函数,就会创造出不同的 Bean

另外还有可能用到的是 @Lazy ,可以让 bean 在用的时候再加载。

SpringIOC 注入方式

当然,除了上面的 getBean 外,Spring还给我们封装了许多方法方便我们买东西:
其中,@Autowired 注解 最常用,意思是按类型装配,如果这个类型的零件只有一个,那么就默认选这一个。如果是有多个,那么还需要我们指定具体是哪一个。

setter

1
2
3
4
5
6
7
8
@Component
public class Customer
private Person person;
@Autowired
public void setPerson (Person person)
this.person=person;


filed

1
2
3
4
5
6
@Component
public class Customer
@Autowired
private Person person;
private int type;

构造函数

1
2
3
4
5
6
7
8
@Component
public class Customer
private Person person;
@Autowired // 甚至可以直接不写
public Customer (Person person)
this.person=person;


如果一个类型对应多个 Bean,使用 @Qualifier 指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class BeanB1 implements BeanInterface
//

@Component
public class BeanB2 implements BeanInterface
//

@Component
public class BeanA
@Autowired
@Qualifier("beanB2")
private BeanInterface dependency;
...

 

Spring IOC 生命周期与扩展点

Spring IOC生命周期:

  1. 初始化 BeanFactory

    1. 创建 BeanFactory

    2. 读取 BeanDefinition

  2. 通过BeanDefinition,初始化 Bean

  3. 供外部调用

  4. BeanFactory销毁

我们可以在 Spring IOC 的生命周期中置入各种我们自定义的逻辑。

  1. 单独类实现接口,一般是应用于全局的

  2. Bean 继承 Aware 类,一般是用于该 Bean 获取环境变量

  3. Bean 实现 接口,一般是用于该 Bean

  4. Bean 上 注解,一般是用于该 Bean

初始化 BeanFactory 之后

实现 BeanFactoryPostProcessor 在BeanFactory初始化之后(已经读取了配置但还没初始化Bean),做一些操作,可以读取并修改BeanDefinition

1
2
3
4
5
6
7
@Component
public class LifeCycleControl implements BeanFactoryPostProcessor
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
//.. doSomeThing


 

初始化 Bean 时

顺序如下:
技术图片

  • 开始几步分别是:实例化、构造函数、设置属性函数,并且,在这里,其实Spring早就已经把所有属性都注入好了,下面的过程都是 Spring 预留给用户扩展的。

  • BeanNameAware抽象类 可以 获取到 BeanName,其他几个 Aware 类似

1
2
3
4
5
6
7
@Component
public class TestB extends BeanNameAware
@Override
public void setBeanName(String name)
// 会告诉 TestB 他的 BeanName 是什么


  • BeanPostProcessor接口 对每一个 Bean 初始化前后进行置入,有多少个 Bean 就会执行多少次

    1. postProcessBeforeInitialization(Object bean, String beanName)

    2. postProcessAfterInitialization(Object bean, String beanName)

  • @PostConstruct 注解(在Bean上),用户自定义的该Bean的初始化操作

  • InitializingBean接口的afterPropertiesSet()方法会被调用

  • init-method 用注解的话,这个一般不用了,一定要用的话,可以用 @Bean(initMethod = “initMethodName”),在配置中心配置,不能在 Bean 上配置。

Spring IOC 构成

Bean

Bean 是 Spring 里面最基本的单位,如果把 Spring 管理的对象看成一群人,那么 Bean 就是每一个人。
在用户看来,bean 就是一个实际的对象,比如

1
MessageService messageService = context.getBean(MessageService.class);

 

在Spring内部,Bean的本质是一个 BeanName -> BeanDefinition 的键值对 ,即用于搜索的名字,和他的实际定义内容。
比如:

1
<bean id="messageService" class="ma.luan.example.MessageServiceImpl"/>

 

就定义了一个 Bean,Name 是 messageService ,BeanDefinition 的内容之一是 ma.luan.example.MessageServiceImpl

ApplicationContext:

Spring IOC 的门面,供用户调用,起到统筹全局的作用。背后它从源(Resource)读取配置 ,为每个 bean 生成配置对象(BeanDefinition) 到工厂(BeanFactory)的注册中心(Registry) ,控制工厂管理 Bean (Autowire),代理工厂的 getBean 操作。我们可以使用多种输入方式 Context。

技术图片

BeanFactory

Spring 的核心,他管理着一个注册中心 Registry,并且负责管理 Bean(加载类,实例化,注入属性等),并且提供 getBean 等操作
技术图片

  • BeanFactory: 可以获取一个 Bean 的能力的接口,有getBean方法

  • ListableBeanFactory,有一次获取多个 Bean 能力的接口,有getBeans方法

  • HierarchicalBeanFactory,有继承能力的工厂接口,有一个 getParentBeanFactory 方法,可以获取父工厂,先不用管

  • AutowireCapableBeanFactory 可以自动装配的工厂接口,继承它让我们的工厂可以对 Bean 自动创建,属性进行自动插入的能力。autowireBean、createBean、initializeBean、applyBeanPostProcessorsBeforeInitialization 等等,是工厂最核心的能力

  • DefaultListableBeanFactory,继承了所有接口,是我们最常使用的标准工厂。他继承并实现了所有的能力

    1. getBean

    2. getBeans

    3. getParentBeanFactory

    4. AutowireCapable

    5. Config (修改设置)

  • ApplicationContext虽然也继承了 BeanFactory,但是实际上是复用了他的 getBean() 等接口,实际逻辑代码并没有什么继承关系

BeanRegistry

注册中心,维护着多个 Map,用于登记 Bean 的信息,包括 BeanDefinition 的 Map,还有 存放单例的 singletonObjects 的 Map 等等。

BeanDefinition

BeanDefinition存放我们在配置文件中对某个 Bean 的所有注册信息,和存放该对象的实际单例对象。
比如,
我们在 xml 文件中配置

1
<bean id="messageService" class="ma.luan.example.MessageServiceImpl"/>

 

那么, BeanRegistry 中 会有
messageService -> BeanDefinition
BeanDefinition里面有关于这个 Bean 的所有配置

ResourceLoader

负责从多个配置源(Resource)读取配置文件,源包括 XML、注解等等,XML有可能来自本地或者网络。

DefinitionReader

从 ResourceLoader 加载到配置资源后,把配置转成(read) Definition 的形式

启动过程分析

简单起见,我们可能还是要用回 XML 配置启动的方法(ClassPathXmlApplicationContext)来分析 (真香),不过其实内部大同小异。

1
2
3
4
5
6
public static void main(String[] args) 
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
MessageService messageService = context.getBean(MessageService.class);
System.out.println(messageService.getMessage());
// 输出 hello world

ClassPathXmlApplicationContext 构造方法

1
2
3
4
5
6
7
8
9
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException

super(parent);
setConfigLocations(configLocations); // 保存XML路径
if (refresh)
refresh(); // 第一次执行会到这里,初始化


核心启动器:refresh 方法

refresh方法是启动的核心方法,执行了启动的所有操作,后面还会提到余下的部分

1
2
3
4
5
6
7
8
9
10
11
12
public void refresh() throws BeansException, IllegalStateException 
synchronized (this.startupShutdownMonitor)
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
prepareRefresh();

// 创建工厂,读取 XML,把 BeanName -> BeanDefinition 在 BeanFactory 的 Registry 里 注册
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

 // ....



创建工厂并在 Context 中保存引用

obtainFreshBeanFactory 方法

这个方法干了下面几件事

  1. 创建了 beanFactory (DefaultListableBeanFactory),并且把 beanFactory 赋值给 Context 保存

  2. 读取 Context 的配置,设置该工厂 是否允许 Bean 覆盖、是否允许循环引用 等

  3. Context 读取加载 BeanDefinition,把 BeanDefinition 注册到 BeanFactory 的 Map 中

    1. 配置 DefinitionReader,读取 Resource

    2. 从XML读取配置树,转换成 Definition,触发监听事件

创建好之后的 beanFactory 的一部分的截图
技术图片

创建工厂后的维护操作

主要是在 BeanFactory 里面注册实现了各种接口的Bean,Factory会为每一个特殊的接口类型维护一个列表,以后到达特定的位置,就会遍历这个列表。
比如 实现了 BeanPostProcessors 的接口的 Bean 有 A,B,C,那么到时候初始化 Bean 之前,就会遍历调用A,B,C的 postProcessBeforeInitialization方法,初始化 Bean 之后,就会调用 A,B,C 的postProcessAfterInitialization 方法,具体什么时候调用什么方法,请查看后面写的 Spring Bean 生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public void refresh() throws BeansException, IllegalStateException
synchronized (this.startupShutdownMonitor)

prepareRefresh();

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 主要是设置 BeanFactory 的 ClassLoader
prepareBeanFactory(beanFactory);

try
// 注册实现了 BeanFactory 的 Bean
postProcessBeanFactory(beanFactory);
// 调用上面注册的 Bean 的相关方法 (用于 BeanFactory 读取 Definition 之后,初始化之前)
invokeBeanFactoryPostProcessors(beanFactory);
// 这个比较会用到,检测 注册好的 Bean 里面,实现了 BeanPostProcessors 接口的 Bean
// 等下初始化 Bean 的前后,会调用这些所有 Bean 的 相关方法
registerBeanPostProcessors(beanFactory);
// 国际化的,用不到
initMessageSource();
// 注册ApplicationEvent接口的Bean,初始化事件广播器
initApplicationEventMulticaster();
// 给子类的钩子,会再注册一些内置 Bean
onRefresh();
// 注册实现了 ApplicationListener 接口的 Bean
registerListeners();

// 初始化所有的 singleton beans (lazy-init 的除外):下面讲
finishBeanFactoryInitialization(beanFactory);

// ...

//...

初始化 Bean

Spring 默认 初始化的 Bean对象 都是单例的,采用的是单例注册表的方法。
我们重点关注单例如何实现,怎么解决循环引用

首先,初始化入口在 finishBeanFactoryInitialization(beanFactory),就是在 refresh() 方法的尾部。这个方法会进行马上初始化的 bean 进行马上初始化。

因为要兼容 延迟初始化(getBean时候加载) 和 马上初始化,所以最合适的方式就是把加载的逻辑写在 getBean 里边,需要马上加载的时候,提前调用 getBean 即可。

finishBeanFactoryInitialization

核心方法是 preInstantiateSinletons():
对符合条件的所有 beandefinition 里面 的 bean 执行了初始化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void preInstantiateSingletons() throws BeansException 
// this.beanDefinitionNames 保存了所有的 beanNames
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

// 触发所有的非懒加载的 singleton beans 的初始化操作
for (String beanName : beanNames)

// 非抽象、非懒加载的 singletons。如果配置了 ‘abstract = true‘,那是不需要初始化的
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit())

if (isFactoryBean(beanName))
// FactoryBean 的话,在 beanName 前面加上 ‘&’ 符号。再调用 getBean
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);

else
// 对于普通的 Bean,只要调用 getBean(beanName) 这个方法就可以进行初始化了
getBean(beanName);





 

getBean

我去掉了部分无关紧要的代码,如果有兴趣可以去看原文件 AbstractBeanFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public Object getBean(String name) throws BeansException
return doGetBean(name, null, null, false);


@SuppressWarnings("unchecked")
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException
// 获取 BeanName
final String beanName = transformedBeanName(name);

// 这个是返回值
Object bean;

// 检查单例是否已经初始化,单例全部注册在 regisrty 的 singletonObjects,多例不会在这里注册
Object sharedInstance = getSingleton(beanName);
// 如果注册表中没有这个单例,会返回 null,后面还会讲到这个方法

// 开始创建 bean
if (sharedInstance != null)
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
// 默认非 BeanFactor 时,等同于 bean = sharedInstance
else
// 如果上面为 null,则说明单例未初始化,或者是多例

final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 这里,实际执行了:this.beanDefinitionMap.get(beanName)
// 就是把 BeanDefinition 拿出来了

// 这里有一百多行代码,除了插入各种钩子和特殊情况,其实我们只执行了一行代码
bean = createBean(beanName, mbd, args)

return (T) bean;

 

所以,getBean 就是 配套钩子 + 执行 createBean 方法
创建好的对象会放在 单例注册表 singletonObjects 中,下次再取的时候就从表里取,而不重新创建,从而实现单例模式。

createBean方法

如果单例还未创建,会在此创建 Bean
createBean 方法 主要做了些前置的工作,包括给 AOP 预留的拦截器 (AOP时,返回 Proxy 对象而不是真正的对象)
然后委托给 doCreateBean 方法,主要做了下面这些事:

  1. 实例化对象

  2. 装配属性

  3. 执行 InitializingBean 接口,BeanPostProcessor 接口钩子, init 方法钩子 (生命周期钩子)

我们主要关心一下怎么解决循环引用的问题:

1
2
3
4
5
6
7
8
9
<bean id="circleA" class="entity.CircleA" >
<property name="circleB" ref="circleB"/>
</bean>
<bean id="circleB" class="entity.CircleB" >
<property name="circleC" ref="circleC"/>
</bean>
<bean id="circleC" class="entity.CircleC" >
<property name="circleA" ref="circleA"/>
</bean>

 

这样就构成了一个循环依赖,而我们默认是单例的,那如何一次性创建三个对象呢?
首先,在实例化对象的时候,先在 singletonFactories 注册一个工厂

1
2
3
4
5
6
7
addSingletonFactory(beanName, new ObjectFactory<Object>() 
@Override
public Object getObject() throws BeansException
return getEarlyBeanReference(beanName, mbd, bean);

);
// this.singletonFactories.put(beanName, singletonFactory);

 

circleA 属性注入时,到了 circleB ,会 getBean(circleB) ,然后又会 getBean(circleC)
getBean(circleC) 时,又看到了A ,会调用回 getBean(circleA),在getBean的时候,会调用getSingleton,他会先从工厂取,这个时候A已经在工厂列表了,然后用getObject,就可以拿到A的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected Object getSingleton(String beanName, boolean allowEarlyReference) 
Object singletonObject = this.singletonObjects.get(beanName);
// getBean 默认走这里,从单例注册表拿已经创建好的单例,但是现在A还没创建好
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
// 单例注册表里面没有,对象正在初始化,符合循环引用的条件
synchronized (this.singletonObjects)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null)
singletonObject = singletonFactory.getObject();
// getObject,就是把A的引用拉过来了,A其实还没建好,不过他的引用已经有了
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);




// 返回A的引用给C
return (singletonObject != NULL_OBJECT ? singletonObject : null);

这样就可以解决循环引用的问题

拾遗

Spring中的监听器用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 自定义事件
public class MyEvent extends ApplicationEvent
public MyEvent(Object source)
super(source);


// 自定义 Bean 实现 ApplicationContextAware 接口
@Component
public class HelloBean implements ApplicationContextAware
private ApplicationContext applicationContext;
private String name;
public void setApplicationContext(ApplicationContext context)
this.applicationContext = context;

// 当调用 setName 时, 触发事件
public void setName(String name)
this.name = name;
applicationContext.publishEvent(new MyEvent(this)); // 这行代码执行完会立即被监听到

public String getName()
return name;


// 自定义监听器, 监听上面的事件
@Component
public class MyApplicationListener implements ApplicationListener
@Override
public void onApplicationEvent(ApplicationEvent event)
if (event instanceof MyEvent)
System.out.println(((HelloBean)event.getSource()).getName());



Spring IOC中的主要设计模式

  1. 单例模式

  2. 观察者模式 (Listener)

参考资料

Spring IOC 容器源码分析
Tiny-spring: A tiny IoC container refer to Spring.
Spring4参考手册中文版
Spring Framework Reference Documentation
Spring的扩展点
Spring Bean Life Cycle Tutorial

以上是关于Spring IOC 概述的主要内容,如果未能解决你的问题,请参考以下文章

SSM框架Spring笔记 --- Spring概述;IOC控制反转详解

Spring学习--spring框架概述搭建和IOC

Spring框架学习-Spring和IOC概述

Spring 框架的概述以及Spring中基于XML的IOC配置

Spring学习笔记1:Spring概述( IOC与DI )

Spring IOC 概述