Spring IoC Container 原理解析

IoC、DI基础概念

关于IoC和DI大家都不陌生,我们直接上martin fowler的原文,里面已经有DI的例子和spring的使用示例

《Inversion of Control Containers and the Dependency Injection pattern》

https://www.martinfowler.com/articles/injection.html

我们这里只关注一些重点概念做为思考,摘一部分原文:

“As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.”

“我想我们需要给这个模式起一个更能说明其特点的名字。控制反转太宽泛了,因而常常让人迷惑。通过和一些IoC爱好者商讨之后我们把该模式叫做依赖注入”

所以说IoC是抽象的概念,常用于形容一个框架,DI则是具体实现的模式,其实可以去spring官网,看到在使用这两个词时也很讲究。

“When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.”

“当这些容器的设计者说这些容器是如此的有用,因为他们实现了“控制反转”。而我却深感迷惑,控制反转是IoC框架的共有特征,如果说一个框架以实现了控制反转为特点相当于说我的汽车有轮子。”

如果说依赖注入其实只是框架最基本的功能,那么什么才是spring高级、核心功能呢?

我们去看看spring官网:

https://docs.spring.io/spring-framework/docs/current/reference/html/overview.html#overview-philosophy

关于设计理念的第一点:

“Provide choice at every level. Spring lets you defer design decisions as late as possible. For example, you can switch persistence providers through configuration without changing your code. The same is true for many other infrastructure concerns and integration with third-party APIs”

“在各个级别提供选择。Spring允许您尽可能推迟设计决策。例如,您可以通过修改配置切换持久层提供程序,而无需更改代码。许多其他基础设施以及与第三方API的集成也是如此。”

所以说spring的核心在于基于IoC为基础理念来做各种基础设施的提供者,这里的“基础设施”也可以理解为对各种中间件的抽象整合。

DI是最基础的一个功能,所有其他功能模块的地基。所以说基于DI的 各式各样的应用全家桶才是spring的核心竞争力。

IoC在平时可能看不到什么作用,但是在关键性的对接或者架构层面作用就大了:

例如事务的管理,不管jdbc还是oracle的事务实现代码如何,我们统一使用spring transaction(当然也结合了AOP),只需要修改相关的数据库配置就可以。

例如注册中心依赖的eureka,想要切换到nacos或者consul,代码同样不用改,修改相关的包引用,修改配置文件相关的配置就好了,代码都不用动。

结论:看完上面的描述还是感觉很虚,就像martin fowler说的IoC容器实现依赖注入并没什么特殊的,spring通过依赖注入为我们提供哪些支撑,以及我们如何运用依赖注入将各种服务组装成我们的系统才是更关键的,这也正是本文的关注点。

为了让下文更好的被理解,我们这里还是简单的贴一些martin fowler文章里的代码

没有IoC框架时:

需要自己进行对象的初始化,依赖对象的设置

class MovieLister...
    private MovieFinder finder;
    public MovieLister()
    {
        finder = new ColonDelimitedMovieFinder("movies1.txt");
    }

如果需要修改finder实现,则需要直接修改MovieLister(高层)的代码,这样其实就是我们常说的高层依赖底层,底层实现换了,高层代码就要变。

有IoC框架时:

我们直接站在巨人肩膀,例如服务定位器一样可以实现DI,但是我们这里不去关心

我们举例习以为常的依赖注入的编码步骤:

1、 编写配置(xml文件、注解、java config)

2、 加载配置,通过配置生产对象(即时或者懒加载都可以)

3、 获取注入好的对象

Java config 配置类:

@Configuration
public class MovieConfig{
    @Bean
    MovieLister movieLister(MovieFinder movieFinder){
        return new MovieLister(movieFinder);
    }

    @Bean
    MovieFinder movieFinder(){
        return new ColonDelimitedMovieFinder (“movies1.txt”)
    }
}

代码:

public void testWithSpring() throws Exception {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MovieConfig.class);
    MovieLister lister = (MovieLister) ctx.getBean("movieLister");
}

关键对象

为了更好的方便理解,我们尝试着将现实世界的对象一一映射到虚拟世界

现实世界

这里我们以饭店举例

几个关键的对象:

海鲜加工饭店

有自己的招牌和特色,不过注意该饭店所有食材均需要客户自带。

出单系统

客户点菜下单(到前台或者找服务员都行,因为自己带的菜还要交给饭店嘛)后自动将客户的订单打印出来,打印出来的订单除了菜名还会加上:序号、桌位号、菜品的口味等。职责就是简洁清晰的打印出订单信息,供厨师或其他人使用。

订单复核员

我们这个餐厅必须要做一个步骤,就是订单出单后再去找客户确认订单,订单确认了才能交给厨房去做,这样做的目的一来是为了避免客户误点或沟通失误,二来通过确认的沟通也提升了用户体验。

为什么我们这里这个角色叫订单复核员,而不叫服务员呢,因为我们的印象里服务员能干很多其他的事情,这样的话反而弱化了订单确认的这个关键动作。

后厨

根据送过来的食材和订单做菜,后厨关注的是如何根据订单和食材来把菜做好。

虚拟世界

直接看这个图可能会有一点懵,我们后面再详细一一进行说明和讲解,请注意,我们本章节后文都是围绕该图进行讲解,此图非常重要。

现实世界

虚拟世界

说明

海鲜加工饭店

ApplicationContext

对标海鲜加工饭店,厨房里的厨子都是他的打工仔,他制定出精美的菜单来吸引食客,@Component、@Configuration、@Bean都是它的金字招牌菜,其实就是对厨子进行了包装,且有门面吸引客源。

海鲜菜单

Java config

Spring IoC独有的招牌菜:@Component

@ComponentScan @Bean @Import等美味

后厨

BeanFactory

根据食材和订单做菜;没有饭店实体店则基本只能做点路边摊小买卖。

出单系统

BeanDefinitionRegistryPostProcessor

相当于出单系统,把客户想要的菜给转化到订单上,例如
ConfigurationClassPostProcessor将@Component @Configuration注解类转化为BeanDefinition;基于java config就离不开他

也是BeanFactoryPostProcessor的子类

订单复核员

BeanFactoryPostProcessor

确认订单信息时菜还没做,例如可以允许客户对订单做一些信息修改

订单

BeanDefinition

即订单信息,后厨要看着订单来做菜

成品

Bean

最终的产物

从上面的例子我们大致能够区分出了BeanFactory和ApplicationContext的区别。

Spring可以让我们参与到任意一个角色中:客户、海鲜加工饭店老板、出单系统、订单复核员、后厨,可以参与到其中任意环节中。

那我们可以做些什么有趣事情呢?

例如可以制定我们特色的菜单,像mybatis的@Mapper特色菜。

确认订单时给所有订单信息里加赠饮料(xml声明的bean的属性里${xx}占位符的替换)等

初步理解了这些关键对象之后,我们再深入到各个环节,看看各个环节都是怎么干的

BeanFactory

给我提供订单信息和原材料我就做,订单和食材缺一不可

让我们先聚焦后厨,因为后厨是饭店的核心。

在spring framework中,Bean的生命周期在Beanfactory里就已经闭环了

ApplicationContext只是加一些料,例如扫描java config转义成BeanDefinition给到BeanFactory,然后再添加一些BeanPostProcessor等。

注意本文重点关注的是基于主流java config配置的实现,其实xml文件的配置原理也类似,不是本文重点不做探讨。

BeanFactory的生命周期是什么,其实就是用
BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor 这两个接口的实现类,往BeanFactory注册BeanDefinition和修改BeanFactory里的BeanDefinition及其他信息。

BeanDefinition

订单信息在虚拟世界长什么样的?它起到了承上启下非常关键的作用。

为什么一开始要先讲BeanDefinition呢,不管基于什么配置方式都需要生成BeanDefinition,在Bean的生命周期中,第一步也是getMergedBeanDefinition。

我们先来看一下BeanDefinition的类图继承关系

BeanDefinition实现类与各个场景的对应,这里我们只关注java config场景的

配置场景

BeanDefinition实现类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值