我是大名鼎鼎的IOC

微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。

写在之前

IOC (控制反转)作为 Spring 中最核心的功能,之前学习一直不得要领,最近在看 《Spring 揭秘》忽有一种茅塞顿开的感觉。

觉得应该好好分享一下自己的理解和思考,供大家参考。

虽然 《Spring 揭秘》年代久远,但确实是一本好书,豆瓣评分高达9.1,所有的细节娓娓道来,不故弄悬殊,强烈推荐大家去看。

让我为你服务

抛开 IOC 的控制反转这个抽象的概念,我们先来看一个场景。

"让我为你服务",请记住这句话,这句话可以说解决了学习的根本问题,为什么需要 Spring IOC

首先说一个简单的场景

外卖服务出现以前,订餐是如何预订的呢?

"老板吗?给我来一份农家小炒肉,我稍后来取。"

"没有问题,要什么口味?超辣还是微辣、还是不辣?"

"微辣就行。"

好的,稍后你来取就行。

看,这就是我们以前点外卖的方式,想要吃什么菜,需要亲自给餐馆打电话,然后亲自上门取餐。

传统点餐

点一份餐,我需要直接跟张老板沟通,我依赖张老板;如果突然有一天张老板休假一天,岂不是吃不着小炒肉了,也就是说,我和张老板存在一个依赖关系

同时如果有时间还好。如果没有时间,没有时间给餐馆打电话点餐怎么办?点完了菜没有时间取,岂不是要饿肚子了?

可是回头一想,我们每次需要什么对象的时候都需要自己去主动取,真的有这个必要吗?

有没有人能够帮忙解决这个问题呢?

有需求,必然有市场。美团、饿了么应运而生。刚好满足我所有的诉求(帮忙点餐、帮忙拿外卖)。

被解放的大黄

其实我们的诉求只是想吃一顿农家小炒肉而已,至于是谁家的不要紧,张老板或者李老板都可以,是骑自行车还是骑摩托车给我送过来,都不要紧,只需要到12点给我来一份农家小炒肉即可。

我还需要为了一顿饭大费周章的自己打电话点餐、自己去店铺取餐吗?如此折腾。

真是有这样的诉求,才出现了如此蓬勃的外卖行业。

我和张老板之间不存在直接的依赖关系,张老板关门了,我还是可以吃到一份小炒肉。

这种依赖依赖关系交给了外卖商(容器)来进行管理,商家将自己有哪些东西放到外卖商平台(容器)。从被注入对象(小炒肉)的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了容器那里。

所谓的控制反转,甲乙双方不相互依赖,交易活动的进行不依赖于甲乙任何一方,整个活动的进行由第三方负责管理。

IOC角色

美团:让我为你服务!

重谈什么是IOC

外卖等服务商就类似于 IOC的容器概念,帮助我们大费周章的自己获取对象,而是提供了更加便捷的方式获取对象。与其去弄懂极度拗口的控制反转,不如去了解其本质。

外卖服务商就类似于一个大的中介,我想吃啥直接去里面点即可,总有一款小炒肉满足你的诉求。

其实 IOC 就是如此简单,原来需要什么餐自己去拿,现在呢?需要什么东西,点一下外卖即可,别人会准点给你送过来。

让我猜猜你的心思

当拿出外卖软件时,脑中想着"想吃一份汉堡",通过可以在软件中随意挑选汉堡。商家把汉堡作为对象注入到外卖软件这个大的容器中,而软件作为一个容器为客户提供服务。容器可以以多种方式将某个汉堡信息推送到用户的眼帘中。

1、如果你经常点肯德基的外卖,没准你一打开软件,自动给你推送一个肯德基奥尔良鸡肉套餐正在做活动;给你推荐一个实惠套餐,毕竟大数据下没得秘密。

2、如果你偶尔点击肯德基外卖,打开软件还需要搜索肯德基,系统会给你推荐经常点击的套餐;

3、可有可能你想换换口味,直接搜索汉堡,系统给你推荐一堆汉堡的店,自己慢慢挑选。

不管怎样,软件终究会有一种方式来向给你提供服务,以便得到你想要的美食。

IOC 模式中,被注入对象又是通过哪些方式来通知容器为其提供适当服务的呢?

最常见的三种注入方式:构造方法注入、Setter 方法注入、接口注入。

1. 构造方法注入

构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表, 让外部(通常是 IoC 容器)知道它需要哪些依赖对象。

比如以一个简单的商品服务为例,在 controller 层依赖 service 层的 GoodsService 服务,可以通过如下构造方法注入:

private GoodsService goodsService;

public GoodsController (GoodsService goodsService) {
   this.goodsService = goodsService;
}

容器 会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注 入相应的对象。同一个对象是不可能被构造两次的,因此,被注入对象的构造乃至其整个生命周期, 应该是由容器来管理。

构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。

就好比,你打开软件,美团就知道你想吃啥。

2. Setter注入

类似于类中某些属性设置的方式通过 setXX() 进行,这些方法统称为 setter 方法,通过setter方法,可以更改相应的对象属性,通 过getter方法,可以获得相应属性的状态。这也是面向对象编程中封装特性的表述方式。

当前对象只要为其依赖对象所对应的属性添加setter 方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。

还是以上面的 GoodService 为例

@Autowired
public void setGoodsService(GoodsService goodsService) {
    this.goodsService = goodsService;
}

同时需要利用传统的 xml 方式配置bean信息

<bean id="goodsService" class="com.jesper.seckill.service.GoodsService">
    <description>"用户商品"</description>
</bean>

<bean id="goodsController" class="com.jesper.seckill.controller.GoodsController">
    <property name="goodsService" ref="goodsService"/>
</bean>

setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些, 可以在对象构造完成后再注入。

这就好比点餐的时候搜索汉堡,然后自己慢慢挑选自己中意的品牌,是肯德基还是麦当劳都可以。

3. 接口注入

相对于前两种注入方式来说,接口注入没有那么简单明了。

被注入对象如果想要容器为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。容器最终通过这些接口来了解应该为被注入对象注入什么依赖对象。

GoodsController 为了让 容器 为其注入所依赖的 GoodsService ,首先需要实现 GoodsServiceCallable 接口,这个接口会声明一个 injectNewsGoods 方法(方法名随意), 该方法的参数,就是所依赖对象的类型。这样,容器就可以通过这个接口方法将依赖对象注入到被注入对象 GoodsController 当中。

通过接口注入

由于这种方式实现比较复杂,并且对于业务代码具有很强的侵入性,因此在实际开发和应用中不是很常见。

这就 好像你同样在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒杯式的帽子,看起来有点多此一举。^1

来一杯这样的啤酒

三种方式比较

注入方式 优点 缺点
构造方法 对象在构造完成之后,即已进入就绪状态,可以马上使用 1、当依赖对象比较多的时候,构造方法的参数列表会比较长
2、构造方法无法被继承,无法设置默认值
3、容易造成注入方的构造函数泛滥
setter setter方法可以被继承,允许设置默认值 对象无法在构造完成后马上进入就绪状态
接口注入 极度不提倡,对代码侵入强、代码复杂

总结

主要和大家探讨什么是IOC,为什么需要IOC,同时无意中还引出容器的概念,其实可以概括一下 IOC 作用,让容器为你服务,避免任何事情都亲自出马,同时帮助我们解耦各业 务对象间依赖关系的对象绑定方式。

参考文章

#学习路径#
全部评论
想请问一下,这三种注入方式跟采用注解 @ Autowired有什么区别呢
点赞 回复 分享
发布于 2021-04-28 10:00

相关推荐

程序员猪皮:看不到八股什么意思
点赞 评论 收藏
分享
10-21 23:48
蚌埠坦克学院
csgq:可能没hc了 昨天一面完秒挂
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-24 20:55
阿里国际 Java工程师 2.7k*16.0
程序员猪皮:没有超过3k的,不太好选。春招再看看
点赞 评论 收藏
分享
评论
点赞
9
分享
牛客网
牛客企业服务