微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。
写在之前
IOC (控制反转)作为 Spring
中最核心的功能,之前学习一直不得要领,最近在看 《Spring 揭秘》忽有一种茅塞顿开的感觉。
觉得应该好好分享一下自己的理解和思考,供大家参考。
虽然 《Spring 揭秘》年代久远,但确实是一本好书,豆瓣评分高达9.1,所有的细节娓娓道来,不故弄悬殊,强烈推荐大家去看。
让我为你服务
抛开 IOC 的控制反转这个抽象的概念,我们先来看一个场景。
"让我为你服务",请记住这句话,这句话可以说解决了学习的根本问题,为什么需要 Spring IOC
。
首先说一个简单的场景
外卖服务出现以前,订餐是如何预订的呢?
"老板吗?给我来一份农家小炒肉,我稍后来取。"
"没有问题,要什么口味?超辣还是微辣、还是不辣?"
"微辣就行。"
好的,稍后你来取就行。
看,这就是我们以前点外卖的方式,想要吃什么菜,需要亲自给餐馆打电话,然后亲自上门取餐。
点一份餐,我需要直接跟张老板沟通,我依赖张老板;如果突然有一天张老板休假一天,岂不是吃不着小炒肉了,也就是说,我和张老板存在一个依赖关系。
同时如果有时间还好。如果没有时间,没有时间给餐馆打电话点餐怎么办?点完了菜没有时间取,岂不是要饿肚子了?
可是回头一想,我们每次需要什么对象的时候都需要自己去主动取,真的有这个必要吗?
有没有人能够帮忙解决这个问题呢?
有需求,必然有市场。美团、饿了么应运而生。刚好满足我所有的诉求(帮忙点餐、帮忙拿外卖)。
其实我们的诉求只是想吃一顿农家小炒肉而已,至于是谁家的不要紧,张老板或者李老板都可以,是骑自行车还是骑摩托车给我送过来,都不要紧,只需要到12点给我来一份农家小炒肉即可。
我还需要为了一顿饭大费周章的自己打电话点餐、自己去店铺取餐吗?如此折腾。
真是有这样的诉求,才出现了如此蓬勃的外卖行业。
我和张老板之间不存在直接的依赖关系,张老板关门了,我还是可以吃到一份小炒肉。
这种依赖依赖关系交给了外卖商(容器)来进行管理,商家将自己有哪些东西放到外卖商平台(容器)。从被注入对象(小炒肉)的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了容器那里。
所谓的控制反转,甲乙双方不相互依赖,交易活动的进行不依赖于甲乙任何一方,整个活动的进行由第三方负责管理。
美团:让我为你服务!
重谈什么是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 作用,让容器为你服务,避免任何事情都亲自出马,同时帮助我们解耦各业 务对象间依赖关系的对象绑定方式。
参考文章
全部评论
(1) 回帖