本文发布于专栏Effective Java,如果您觉得看完之后对你有所帮助,欢迎订阅本专栏,也欢迎您将本专栏分享给您身边的工程师同学。

本集概要:

  • 构造器注入有什么缺点?
  • 如何使用setter注入?
  • setter注入为什么会导致空指针异常?

前情回顾:用小说的形式讲解Spring(1) —— 为什么需要依赖注入

大雄给项目引入了Spring框架,解决了代码过度耦合的问题,当然,这只是Spring强大功力的冰山一角,菜鸟大雄还仍然是菜鸟大雄……

越来越庞大的构造函数

一天,晨会过后,哆啦对大雄说,“大雄,我们的订单接口和支付接口都已经非常完善了,现在需要在支付完成时更新一下订单的状态,你看看这个需求如何实现。”
“这个好办,只需要给支付接口添加一个新的依赖IOrderDao,然后把OrderDao注入进去就可以了。”
“好小子,张嘴一个‘依赖’,闭嘴一个‘注入’,术语说的挺溜的呀”
“那是,你等着,马上搞定这个需求”,说完,大雄就火急火燎地写代码去了。

大雄给PaymentAction加了一个成员变量orderDao,然后新建了一个构造函数,把orderDao注入到PaymentAction里面去,接着写了一个updateOrderAfterPayment方法,调用orderDao的方法更新订单(本文使用的代码,可以到 SpringNovel 下载,欢迎加星):

public class PaymentAction {private ILogger logger;// new proprety !!!private IOrderDao orderDao;public PaymentAction(ILogger logger) {super();this.logger = logger;}// new constructor !!!public PaymentAction(ILogger logger, IOrderDao orderDao) {super();this.logger = logger;this.orderDao = orderDao;}public void pay(BigDecimal payValue) {logger.log("pay begin, payValue is " + payValue);// do otherthing// ...logger.log("pay end");}// new method !!!public void updateOrderAfterPayment(String orderId) {orderDao.updateOrderAfterPayment(orderId);}}

最后大雄修改了一下payment.xml,注入orderDao:

<bean id="paymentAction" class="com.springnovel.payment.springxml.PaymentAction"><constructor-arg ref="serverLogger"></constructor-arg><constructor-arg ref="orderDao"></constructor-arg>
</bean><bean id="serverLogger" class="com.springnovel.perfectlogger.ServerLogger" />
<bean id="orderDao" class="com.springnovel.dao.OrderDao" />

就这样,大雄很快实现了往支付接口添加订单更新功能的需求,兴冲冲地给哆啦提交了代码Review的请求…

很快,Review结果回来了:

  • 如果后面PaymentAction需要依赖更多的接口,比如短信发送接口、支付宝接口、微信支付接口等等,你还是往构造函数里面加吗?假如依赖了20个接口,那你的构造函数就会有20个参数,就像下面这段代码,你觉得这样的代码优雅吗?
public PaymentAction(ILogger logger, IOrderDao orderDao, ISMSUtil smsUtil, IPaybal paybal, IWechatPay wechatPay, ...) {super();this.logger = logger;this.orderDao = orderDao;this.smsUtil = smsUtil;this.paybal = paybal;this.wechatPay = wechatPay;...
}

哆啦的话再一次给大雄浇了一盘冷水,“为啥每次review都不能一次过……”

Setter注入

怎样解决构造函数越来越庞大的问题呢?大雄忽然想到之前在《Effective Java》的第一章看到的一个叫做Builder模式的例子,Builder模式把一个原本很庞大的构造函数,简化成一个小的的构造函数外加很多个set函数。
“啊,不一定要用构造器注入!还有setter注入!”,大雄这才想起来之前学习Spring时看到的另一种注入方式 —— setter注入。

接下来,就是用setter注入改造PaymentAction了,大雄把之前含有两个参数的构造函数去掉,然后加上了一个setOrderDao方法:

public class PaymentAction_SetInjection {private ILogger logger;private IOrderDao orderDao;public PaymentAction_SetInjection(ILogger logger) {super();this.logger = logger;}// setter injection !!!public void setOrderDao(IOrderDao orderDao) {this.orderDao = orderDao;}public void pay(BigDecimal payValue) {logger.log("pay begin, payValue is " + payValue);// do otherthing// ...logger.log("pay end");}public void updateOrderAfterPayment(String orderId) {orderDao.updateOrderAfterPayment(orderId);}}

接着再修改一下payment.xml,使用<property>标签,注入orderDao:

<bean id="paymentAction_setInjection" class="com.springnovel.payment.springxml.PaymentAction_SetInjection"><constructor-arg ref="serverLogger"></constructor-arg><property name="orderDao" ref="orderDao"></property>
</bean>

测试一下:

public void test_PaymentAction_UpdateOrder_XML_SetInjection() {ApplicationContext context = new ClassPathXmlApplicationContext("payment.xml");PaymentAction_SetInjection paymentAction = (PaymentAction_SetInjection) context.getBean("paymentAction_setInjection");paymentAction.updateOrderAfterPayment("123456");
}

Output:

real update order after payment, orderId is 123456

“完美!setter注入其实也没什么嘛!”,大雄大叫道,偷偷瞄了哆啦一眼,哆啦此时正专注地看着自己的屏幕,似乎没有觉察到这边厢亢奋的大雄。

空指针异常!

大雄再一次准备给哆啦提交review请求,在食指即将按下回车的那一刹那,他仿佛拥有了窥视未来的能力,他看到哆啦拿着装满冷水的脸盆,朝他洒过来…. “啊,不对劲,那这样构造器注入岂不是完败于setter注入了?不科学呀。。。setter注入肯定有什么局限是我还没发现的…..”

“Spring容器初始化对象时,会去调用对象的构造函数,此时如果采用构造器注入,并且xml里没有配置对应的<constructor>标签,那么由于没有与之匹配的构造函数,注入应该会失败”
“而setter注入,如果没有配置<property>,是会提示初始化失败呢,还是压根就不注入呢?”,大雄的脑袋飞快地翻转着。

“修改一下代码,验证一下不就知道了!”
于是大雄首先把<constructor>标签注释掉:

<bean id="paymentAction_setInjection" class="com.springnovel.payment.springxml.PaymentAction_SetInjection"><!--<constructor-arg ref="serverLogger">--><!--</constructor-arg>--><property name="orderDao" ref="orderDao"></property>
</bean>

执行测试用例,果然报错了:

org.springframework.beans.factory.BeanCreationException:
...
No default constructor found; 

提示“没有默认的构造函数”,可见由于没有配置<constructor>标签,Spring容器调用了空参数的构造函数,而PaymentAction类并没有空参的构造函数,因此报错了,这种错误会导致容器初始化失败,因此很容易发现

接着大雄撤销了操作,然后把<property>标签注释掉:

<bean id="paymentAction_setInjection" class="com.springnovel.payment.springxml.PaymentAction_SetInjection"><constructor-arg ref="serverLogger"></constructor-arg><!--<property name="orderDao" ref="orderDao"></property>-->
</bean>

重新执行测试用例,啊,报错了! 空指针异常!:

java.lang.NullPointerExceptionat com.springnovel.payment.springxml.PaymentAction_SetInjection.updateOrderAfterPayment(PaymentAction_SetInjection.java:34)at com.springnovel.test.PaymentTest.test_PaymentAction_UpdateOrder_XML_SetInjection(PaymentTest.java:46)

看来如果没有在xml里面指定要注入的对象,那么set注入不会失败,所依赖的对象没有被注入任何对象,因此默认为null。
“这可不太好,万一真的粗心大意忘了在xml里面指定要注入的对象呢,容器是可以成功启动,但是运行时可就挂了。。。
“有没有办法让setter注入的属性成为必填项呢?”

大雄决定上网搜索一下资料,打开Google,输入“spring setter bitian…”
“啊不。。什么鬼。。。必填英文怎么说来着。。。”
“噢噢,required嘛,HTML5的一个必填校验属性就叫Required”
噼里啪啦,大雄输入了“spring setter required”
很快,他发现Spring2.0提供了一个@Required注解……

Reuqired注解

“这就好办了!”,大雄对照着教程,修改起了代码。
首先要开启Spring注解的功能,给payment.xml加入这些配置:

...
<beans
       ...    xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="...http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd"><context:annotation-config/>
...

接着再给PaymentAction的setOrderDao方法加入@Required注解:

@Required
public void setOrderDao(IOrderDao orderDao) {this.orderDao = orderDao;
}

再次执行测试用例,结果当然还是报错,不过这次是在容器初始化就提示错误了:

org.springframework.beans.factory.BeanCreationException:
...
Property 'orderDao' is required for bean 'paymentAction_setInjection'

“这下好了,在Spring容器创建对象时就报错了,不会等到执行代码时再来抛个空指针异常,简直是粗心大意的程序员的救星啊!”

大雄仔细地对代码做了检查,最后敲了回车,给哆啦提交了Review请求。

“叮,pass!”,大概过了半个小时,屏幕弹出通知,大雄的代码终于通过了哆啦和小组成员的检视,成功提交到代码库了!

大雄的笔记

今天大雄学到了构造器注入之外的另外一种注入方式——setter注入,临睡前,大雄习惯性地对今天所学到的知识做了总结:

  • Constructor注入 vs Setter注入

    • Constructor注入能够强制要求调用者注入构造函数中的所有参数,否则在容器初始化时就会失败;但是如果要注入的对象过多,就会导致构造函数过于庞大
    • Setter注入,类似于Builder模式,将原本庞大的构造函数,拆解为了一个小的构造函数许多个set方法。setter注入不能保证对象一定会被注入,但是可以使用@Required注解,强制要求使用者注入对象,否则在容器初始化时就会报错。

总结完,大雄一跃而起,啪的一下蹦到了席梦思上,整个人成“大”字状,眼睛一闭,嘴巴一张,很快进入了梦乡……

大雄的梦

睡梦中,大雄看到了一只非常奇怪的“三眼乌鸦”,“三眼乌鸦”静静的木在树枝上,等大雄一靠近,就很快地飞到另一颗树上,大雄就这样追了好久….
突然,大雄看到一座桥,桥头立着一块牌子,上面写着“Bridge for You”
“桥为我?桥给我??”
一旁的“三眼乌鸦”实在看不下去了,骂道,“是给你准备的桥!笨蛋!”
“给我准备的?那我得走过去看看!”,说完,大雄走进了那座桥 – Bridge for You ……

桥的对面有一间茅草屋,门没关,大雄看了看,好像没人,走了进去,发现里屋有个少年,正在敲着键盘。大雄凑过去偷看,那少年好像在写小说,小说标题是“用小说的形式讲解Spring…”,大雄心想怎么会有人起这么挫的标题。那少年一边敲着键盘,一边还叨叨道,“大雄啊大雄,哥最近换了新部门,忙得很,一周只能过来看你一次啦……”

未完待续

第二天一大早,手机突然响了,睡梦中的大雄迷迷糊糊地接了电话…

“喂!大雄啊,还在睡觉啊??快起来,有个需求要改一下!”,原来是那个讨厌的项目经理胖虎…
“改..改需求?!”
“之前不是让你们把日志打印到日志服务器了吗?刚刚客户说了,要换,要打到控制台!今天早上就要改完!”
“哇靠….”大雄脱口而出,不过他很快就暗暗高兴,因为他知道由于采用了依赖注入,现在他只需要改一处配置,“哎呀,这客户咋那么多事,你等着啊,我现在改,要改的地方多着呢,改完你得请我吃饭”
“小兔崽子,项目上线了你要吃多少都行!”

大雄马上起身,打开便携,把payment.xml的ServerLogger改为了ConsoleLogger:

<bean id="serverLogger" class="com.springnovel.perfectlogger.CosoleLogger"/>

“要测试一下吗?哎,算了,测啥测,肯定没问题”,说完大雄提交了代码给胖虎,然后给胖虎发了条信息,让他审核一下。
胖虎很快将大雄的代码提交到代码库…

“真是的,搞得我觉都没睡好…”,大雄正准备睡个回笼觉….手机又响了…
“大雄,你怎么搞得!改了你的代码,现在服务器连启动都失败了!”
“啊???怎么可能…”,大雄一脸懵逼……

参考内容

  • 《Spring in Action》
  • Explain why constructor inject is better than other options
  • Setter injection versus constructor injection and the use of @Required
  • Spring dependency checking with @Required Annotation

用小说的形式讲解Spring(2) —— 注入方式哪家强相关推荐

  1. Spring(2) —— 注入方式哪家强

    本集概要: 构造器注入有什么缺点? 如何使用setter注入? setter注入为什么会导致空指针异常? 前情回顾:用小说的形式讲解Spring(1) -- 为什么需要依赖注入 大雄给项目引入了Spr ...

  2. 用小说的形式讲解Spring(4) —— 使用Spring Boot创建NoXml的Web应用

    本文发布于专栏Effective Java,如果您觉得看完之后对你有所帮助,欢迎订阅本专栏,也欢迎您将本专栏分享给您身边的工程师同学. 本文中的项目使用Github托管,已打Tag,执行git che ...

  3. 用小说的形式讲解Spring(3) —— xml、注解和Java Config到底选哪个

    本文发布于专栏Effective Java,如果您觉得看完之后对你有所帮助,欢迎订阅本专栏,也欢迎您将本专栏分享给您身边的工程师同学. 本集概要: 为什么说xml配置是类型不安全的配置方式? 如何使用 ...

  4. Spring 依赖注入方式详解

    平常的Java开发中,程序员在某个类中需要依赖其它类的方法. 通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理. Spring提出了依赖注入的思想,即依赖类不由 ...

  5. Spring的注入方式详解

    [html] view plaincopy Spring的注入方式详解 Spring有三个注入方式,type1,type2,type3 type1  接口依赖 type2  setter/getter ...

  6. spring的注入方式

    下面展示spring的注入方式,主要是: 两个接口: Axe.java: package com.jim.service; public interface Axe { public String c ...

  7. 最全的 Spring 依赖注入方式,你都会了吗?

    欢迎关注方志朋的博客,回复"666"获面试宝典 前言 Spring 正如其名字,给开发者带来了春天,Spring 是为解决企业级应用开发的复杂性而设计的一款框架,其设计理念就是:简 ...

  8. Spring依赖注入方式

    一.依赖注入(DI)简介     依赖注入背后的基本原理是对象之间的依赖关系,可以通过以下几种方式来实现:构造器的参数.工厂方法的参数,或给由构造函数或者工厂方法创建的对象设置属性.因此,容器的工作就 ...

  9. Spring bean注入方式

    版权声明:本文为博主原创文章,如需转载请标注转载地址. 博客地址:http://www.cnblogs.com/caoyc/p/5619525.html  Spring bean提供了3中注入方式:属 ...

最新文章

  1. python列表中enumerate和zip函数用法
  2. 小王利用计算机设计了一个计算程序,七年级数学上册5.3代数式的值巧求计算机里的代数式的值素材(新版)青岛版...
  3. Cisco路由器——Console线的接法
  4. 线上python课程一般多少钱-参加Python培训机构要花多少钱
  5. android登录操作代码,Android Studio实现第三方QQ登录操作代码
  6. [jQuery]计算年龄
  7. jetson nano 人体姿态识别
  8. 软件冒烟测试报告,冒烟测试方法及报告模板
  9. java saxreader_java解析XML文件---SAXReader
  10. 【电脑维修系列】电脑重启快捷键表 进入PE
  11. 汽车转向前后轮轨迹matlab程序,车前进后退方向的口诀,动画图解车前后轮转弯轨迹...
  12. 一文解读广告投放落地页
  13. 常用标点符号的英文名称
  14. 电脑右键没有“发送到”选项
  15. 软件测试 | 测试开发 | Git实战(四)| Git分支管理实操,搞定在线合并和本地合并
  16. writeline是什么意思_c语言console.WriteLine什么意思?
  17. 事件冒泡和事件捕获的区别
  18. [技术讨论]基本共射共集共基放大电路怎么工作,可以来看看
  19. 斯坦福NLP名课带学详解 | CS224n 第10讲 - NLP中的问答系统(NLP通关指南·完结)
  20. vs code上配置tcl/tk语言开发环境

热门文章

  1. 轻流无代码开发平台是什么产品?可以提供什么功能?
  2. 通过iwpriv命令判断WIFI client和Ralink AP之间是否连接
  3. 和迈危险废物处置单位信息管理系统
  4. Hrm-人力资源系统开发笔记05
  5. 用python字符画生日快乐_Python:图片转字符画(~情人节神器~)
  6. Revit剪贴板用法复制一样的楼层及构件一键剪切功能
  7. FLutter 官方推荐的二个动画插件lottie和Rive(flare)动画
  8. 利用python代码 之 使用谷歌浏览器打开百度
  9. CSS科技感四角边框
  10. Jmeter验证码注册接口压力测试实战