想理解Spring IoC的好处,不妨去读点技术历史

36 阅读4分钟

所以说呀,程序员也得去读一点「技术历史」,才能比较好的去理解新的知识点。像我这种写过「EJB 2.0」的老程序员,对Spring里的IoC,还是很有实际的感触的。

在本文正式开始之前,先介绍一下两个蛮实用的学习方法:

  • 读技术历史,了解来龙去脉,非常有助于理解后续的新知识点;
  • 尽量靠近源头去找资料,很多你要知道的知识点,人家一早就写的很完整很清晰了;

常有人问Spring IoC有什么好处,回答多是解耦、可测试、生命周期等。这些说法都对。但是写过EJB2这种的人,是知道整个演变的过程的,是更能理解好IOC的。

用EJB2.0编程时,你会有一种强烈的感觉,就是50%的操作,不是在写业务代码,如下:

  • 业务代码里要查JNDI;
  • SessionBean还要走Home、Remote那套接口;明明本地调用,也得按远程套路写;
  • 部署描述符和Java代码两套维护,改一个DAO,有时还得动ejb-jar.xml;

对象谁创建、谁查找、谁组装,真的是满天飞,维护代价也跟着水涨船高。当时项目里的局面,大概像下面这样。

Spring作者Rod Johnson在2002年《Expert One-on-One J2EE Design and Development》第6章里写过类似处境:「不用EJB,也得有办法管业务对象」;否则项目里会混用Singleton、手写Factory,配置各写各的,很难维护。Spring IoC是在这条背景下萌生的。

EJB2里IoC要替代什么

EJB容器能管SessionBean、EntityBean那一层,但DAO这类细粒度对象,容器管得并不好。团队往往在EJB外面再补静态工厂、Singleton,各写各的。

Spring IoC针对的是这段空白:把对象创建和依赖组装「收到一个地方,业务类只声明需要什么」。

当年最烦的项目里常怎么写Spring IoC改了什么
用个Service要先查JNDI要么自己new,要么写个getXXX()工厂启动时配好Bean,业务代码里直接注入用
调Bean要走Home、Remote、create()有的走EJB,有的偷偷写成普通Java类,两套混着写普通Java类就行,谁创建谁交给容器
改DAO还得动ejb-jar.xml配置散在XML、properties、甚至数据库里,各管各的Bean定义收在一处,改依赖改配置,少动业务类
写单测绕不开容器测试里再抄一遍JNDI,或者手工把依赖一个个new进去测试里换一份配置,把实现换成Mock就行
DAO、工具类EJB管的不好有人用Singleton,有人静态工厂,各写各的Service、DAO都是Bean,同一套容器一起配

左半边是EJB2常见写法:Client查JNDI,拿Home,再create SessionBean,Bean里可能还要自己管DAO,侵入了业务代码。右半边是Spring:ApplicationContext启动时把Service、DAO装配好,业务方法跑起来时,依赖已经在那里了。控制反转,用大白话说,就是谁new、谁组装,从业务代码挪到了容器启动阶段。

EJB容器里也有inversion of control,比如SessionBean的setSessionContext,让容器回调你的代码。但关键是,不是你有IoC就行了,还得从更轻量级、更统一、也更方便单测的维度来考虑,Spring做到了。

Rod Johnson介绍Spring时还提过Hollywood Principle(好莱坞原则),大意是别打电话给我,我会打给你:框架在合适时机调你的代码,而不是你的代码到处去调框架。

前面两张图、一张表,其实都在说同一件事:EJB2里装配散在各处。那Spring IoC有什么好处?

把创建、查找、组装收到容器里,业务代码主要写业务。

或者我像下面这么描述,可能更加容易理解:

Spring IOC的核心贡献,是将‌对象的创建、查找、组装‌这一「控制权」,从‌业务代码‌(开发者手动new、JNDI lookup、工厂调用)‌「反转」‌至‌容器‌(ApplicationContext)统一管理。

原本EJB2.0的设计,则是「控制权错位了」。

参考的内容

  • 《Expert One-on-One J2EE Design and Development》第六章