Thinking in Spring
[从兴公司 - 陈百平]
时日如飞,才写了几篇有关架构的文章,生命又不经意地走过了一个年轮。所写的文章基本都是些概念上的介绍,热诚的同事建议我写一些架构应用方面的文章,如“Spring实战”、“Hibernate入门”等。但对于这些架构,我都是了解些皮毛,没有实际使用过,更遑论实战经验了。
正因为没有使用过这些架构,所以知道自己所写的文章水平不会高,也不能给大家的工作带来实质性的指导作用。对于这些架构,我更多的只是给予关注。可能是自己老了的缘故吧:年过三十,已不适合编程序了。
发现自己变老,我却是从与计算机下棋时知道的。自小,父亲就教我下棋。出师后,我自然是打遍周边小朋友无敌手了。到了初中、高中,仍然可以将同学、老师杀个人仰马翻。工作后,童心未抿的我在电脑上装了个象棋软件,与计算机对弈起来。自然,每局必输。自己也认为理所当然:计算机嘛,算计得当然是厉害的了。直到某天,我才诚惶诚恐起来。因为我发现自己下棋时不再思考,而是凭经验去走棋,凭着小时候所积累的经验去走棋。而计算机里早有一堆的棋谱来对付我这种走法了,岂能不输?
于是,当我不再思考时,我已老了;当我只凭经验去生活时,我已老了;当我教训小侄子说“我吃过的盐比你吃过的饭多”、“我走过的桥比你走过的路长”时,我已老了!
我不希望大家的心态如我一样易老,而更希望大家在学会了Copy & Paste之外,还将思考得更多。所以本文的内容主要还是写我对Spring架构的个人想法而非实战经验的描述。套用时下流行的《Thinking
in Java》、《Thinking in C++》等名著的标题,将题目命名为《Thinking in Spring》。
下面介绍Spring的两个重要的技术:一是控制反转(Inversion of Control,IoC);二是面向方面的编程(Aspect
Oriented Programming ,AOP)。
控制反转是Spring的技术核心。一开始我接触到这个概念时,光从字面上我不理解是什么意思。再看其解释,当看到所谓的好莱坞准则:别找我,我来找你(Don’t
call me, I will call you.)时,我还是不太理解,猜想可能与Window操作系统中的钩子函数或者回调函数差不多吧。最后我看到依赖注入(Dependency
Injection)这个概念时,我才有点明白什么是“控制反转”。在Spring中,“依赖注入”与“控制反转”两个概念基本是表达同一个含义。所谓“
依赖”,大概就是一堆的类,其互相有着关联关系,一个类需要调用其它的类,如果失去了其它的类的协助,这个类就没法正常工作了。在Spring中,这种“
依赖”关系是通过spring的配置文件来描述的,而并非是在写代码时指定的。所谓“注入”,大概就是所依赖的类是何时生成实例,并通过何种途径绑定到引用类中。
于是,我们不免要问:为什么需要“依赖注入”,它有什么优势?我们先回过头来看看以前的程序是如何编写的。假设有一天,领导拍拍你的肩膀说:“小伙子,我们做一个清单查询系统吧,清单可以保存在数据库中或文件系统中。最终保存在哪,可以让用户选。”这种软件开发对你来说自然是小case了。只要做个配置文件,在里面做一个存储标记,主控类大概就会如下:
//采集入库程序
If ( flag == ‘数据库’ )
{采集清单数据,并将数据写入数据库;}
Else if ( flag == ‘文件系统’ )
{采集清单数据,并将数据写入文件系统;}
//查询程序
If ( flag == ‘数据库’ )
{根据查询条件从数据库中检索清单数据;}
Else if ( flag == ‘文件系统’ )
{根据查询条件从文件系统中检索清单数据;}
如果程序中还有其他地方需要这种开关的,也按照以上写法就是了。很快,你的小团队完成了开发,领导也很满意。过了大半年,领导再次拍拍你的肩膀说:“小伙子,客户有新需求,需要将清单存储到网格里。下周就要给客户演示了,你赶紧改改程序吧。”你看着已几经变迁的小团队,面露难色:最熟知这个程序架构的人已不在了,如果让其他人改,涉及的地方又挺多,不知会不会改错。因为改了源代码,所有的功能(包括以前的)又要花时间重测一遍。除非这个是一个独立的版本,专门给这个客户用。这样的话又得维护多个版本,也麻烦。
从上面的这个例子可知,造成麻烦的主要原因是一些可变的东西写死在了程序中,而这些可变的东西又遍布了整个程序。
看看Spring是如何处理这种问题的。在Spring中,提倡使用接口编程。比如,以上的主控类在Spring中大概是这样写的:
//采集入库程序
StorageInterface s = getStorageBean();
采集清单数据data;
s.save(data);
//查询程序
StorageInterface s = getStorageBean();
Data = s.find(condiction);
你会发现那些分支语句不见了,程序变得更加容易理解与维护。可能你会问,主控类如何知道它到底是依赖与哪个存储类进行存储呢?其实,在编程时你是无需关心的。主控类到底使用了哪个存储类,是由Spring的配置文件指定的。你在配置中指定了使用数据库存储类,它就会自动地生成数据库存储类的实例,并加以调用。当然,你必须让数据库存储类和文件系统存储类都实现StorageInterface接口。总之,使用了接口编程,类与类之间的耦合度将大大地降低。另一个你感兴趣的问题是:既然主控类不知道它所依赖的具体的存储类,那存储类的实例是何时生成的呢?在Spring
中,这其实就是如何注入的问题。你可以在主控类的构造函数中注入,也可以通过反射机制注入,即setter设值注入。所有这些都是在配置文件中进行配置的。
如你使用了Spring架构,对于上述的例子,你所要开发的仅仅是一个网格存储类,对于主控类等都不必更改。开发完后,所要测试的,也仅仅是新的类,旧的存储类不必再测试,因为所增加的代码必定与旧的存储类无关。过了一周,当开发工作有条不紊地完成了,领导会再次拍拍你的肩膀说:“小伙子干得不错……”
Spring的另外一个重要技术是面向方面的编程(AOP)。光看这个名词,我似乎有点理解。后来google了一把,看了一些使用日志举例的文章,我反而糊涂了。文章说使用了AOP,可以减少了写日志的代码,并且可以规范日志的输出格式等。我想了很久都没有想明白:为什么业务程序写日志可以使用AOP?这在我看来是不可能的。因为不同的业务类型应该有着不同的业务日志,是不可以做到统一的。比如说“登陆系统”日志,我需要记录“帐号”和“IP”等信息;而“改密码”日志,我是不用记录“IP”的。在AOP中,我不知道它是如何处理的。后来再琢磨了很久,才发现原来例子所说的日志是指“系统”日志,而非业务日志。系统日志一般记录一些简单的信息,如系统何时启动、服务何时被调用、何时调用完毕等信息,故能使用AOP进行统一处理。
图1. AOP与业务服务的关系
AOP最为强大的功能是对全局资源及事件的管理和控制。它是通过所谓的“拦截”技术来实现的。在执行某个函数前(before)或后(after),AOP可以执行另外一段你预先定义好的代码,从而达到“拦截”目的。图1显示了AOP与业务服务之间的关系。
比如现在要开发一个新的boss系统,里面有很多服务:修改密码、开通主叫显示、查帐单等等服务。作为架构的规划者,你不想每个开发人员都去实现资源的控制,如数据库连接等;而作为开发人员,我也不想去处理与具体业务无关的事情,如权限控制、事务管理等。以前开发系统时,虽然一再叮嘱:获取数据库连接使用完后必须关闭。结果总是有人忘了关,造成系统运行一段时间后就不正常了。以前的代码大概如下:
Connection conn=null;
Try{
conn = getConnection();
Do business; //业务逻辑
}catch( Exception e ){
}
Finally{
If( conn != null )
conn.close();
}
如果使用了AOP,你需要写一个公用的Advice类(通知类),这个Advice类在调用你的业务前先获取连接,在调用完毕后自动关闭连接。这时,业务代码将简化成:
Do business; //业务逻辑
这样不但代码量少了,并且还减少了因为忘记关连接所造成的错误。在Spring中这个Advice类与业务类的关联当然也是通过配置文件指定的,你不必为此写额外的代码。
另外,如果我们不使用其他的交易中间件,自己进行事务管理的话,也可以考虑采用AOP技术。事务管理是一个比较复杂的技术(这也是EJB容器能卖钱的主要原因之一),具体如何实现,大家可以尝试思考一下。
Spring架构是一个较为复杂的架构,本文也就将自己的对Spring的想法蜻蜓点水似地描述了一下,内容难免肤浅和凌乱。如想对Spring有个整体的了解,可阅读参考资料中的书籍。
参考资料
1.《Spring 开发指南》作者 夏昕。
2.《Spring in Action》作者 CRAIG WALLS,RYAN BREIDENBACH。
注:以上文章版权归立信集团《沟通》所有,不得转载、引用。
|