研究一些复杂java开源软件代码的体会(转)
原文地址:http://herman-liu76.iteye.com/blog/2349026
平时做J2EE项目中,一直都是以做业务为主,如果用框架,那更多的是写 bean, dao, service, action,功能上也是增删改查为主。这样的代码必然索然无味,不过之前分析过几个开源的系统代码,发现研究那些代码非常有趣的一件事,而且有些设计很自然的在生活中找到原型,或者感觉就是自己设计一个工厂在加工产品,或者感觉是设计一个游乐场服务公众。看这些代码最多的体会是以下方面:
[深]-代码中的设计思想,体现着作者思考深度;
[规]-代码的风格规范体现着作者是工作态度,以及领会规范的意义;
[综]-体现了模块化风格,把东西综合在一起,体现了高聚合低耦合的特征;
[博]-广泛的新技术使用,体现作者深入研究过同类代码或者技术。
本文就以之前研究过的阿里的druid分析为主(druid是阿里巴巴的连接池产品,号称为监控而生),谈谈体会,以及如果是自己面对这样的功能需求,如何应该一步步做出这样的产品来。面向对象的三个基本特征是:封装、继承、多态,但在设计复杂软件的时候,体会最多的是下面几点(写的有点啰嗦,不过有些过程要细细体会,省了回头看的过程):
一、组合(或持有、引用)是最重要的技术之一
人的本质是各种社会关系的组合,人类社会如些复杂,也是因为各种人体、组织关系交织在一起。
1.长期组合
无论是一个车,一个飞,甚至一个人,都是由无数的子系统以及无数的零件组合而成的,所以组合是实现复杂软件的重要技术。
组合主要是一个类作为另一个类的引用属性,可以简单的说,知道对方在哪里,只有引用对方才能使用对方的功能。很多时候更是相互引用,我知道对方,对方也知道我,常见的代码就是我引用对方时,把自已this再传给对方。
更复杂一点的情况就是已经组合出一个复杂的对象,这个复杂的对方与另一对方建立了引用关系,那另一对象就可以使用复杂对象中的对象。如果在一个大的软件系统中,对象之间的引用是十分复杂的...只有抽象出核心对象的关系动态静态模型才能做出复杂的软件。
还有一个对象内部持有的对象是线程对象,一直为自己服务。
比如:DruidDataSource是一个数据源对象,它必然有很多属性外,持有的重要对象有:
DruidConnectionHolder[],暂且认为数据源有一个数组对象,放的都是这个数据源的连接,所谓连接池吧。
CreateConnectionThread,DestroyConnectionThread,这两个是创建连接和销毁连接的线程对象。如同在一个大的房间,如果人多就会动态增加日光灯,如果人少就减少日光灯数量,动态的改变连接池的大小,算是节能吧
ReentrantLock,这是一把共享锁,上面的线程都在为一个数据源服务,打架的话就要用锁了。
List<Filter>,这个一看就是过滤器的列表,既然一个数据源持有过滤器。那必然过滤器是独立配置给不同中的数据源,为何不统一配置呢?当然是灵活性,为何不配置给更小的对象上呢?也许没必要那么细吧,这也是一个使用经验的权衡。为何不给连接对象配置呢,连接对象是不断产生和消亡的,不稳定。为何不配置建一个对象,比如把ReentrantLock和List<Filter>放进去,让数据源持有这个新建对象呢?很简单,一个公司不会把一个业务部门与职能部门组合后,上面成一个新部门吧。为何不分别直接持有每一个过滤器呢?首先个数不定,这样比较灵活配置给数据源,另外同样的工具当然是直接编组比较好了,将军不会直接管理一个个小兵的。
平时代码里的serivce与dao,通过spring实现了长期组合的简单关系,而且是单方向的持有。以前没有spring的时候,有些人是通过构造方法传入,有的是使用时传入,有的是直接设置属性,比较乱。
2.用时组合(持有)
用时组合一般是一个比较稳定的对象,处理一个变化的对象,这个过程可能比上面要复杂多了。类似于提供服务,比如医生与医院是一人比较稳定的组合,但医生与病人就是一个临时组合。又类似于打印机与纸张关系,进去是空的,出来是有图案文字的。
druid中的每一个连接就是一个变化的对象,有点象兵营里的士兵,有点象学校里的学生,铁打的营盘流水的兵。如果少了要补充,如果多了要退伍。DruidConnectionHolder[]放置着连接,两个线程不时清点人数。
druid中还有一个重要的变化对象就是过滤链FilterChain,前面提到过滤器Filter,它由数据源持有DruidDataSource。那这三者关系如何呢?举个例子吧,如果你去体检,那每个人手中的体检单就是过滤链,每个科室(医生)就是过滤器,而医院就是数据源。每次新来一个体检者,都产生一个体检单,体检单持有医院这个对象,你不能中途跨医院体检。或者说,有一个工厂,里面有数台加工设备,那每一个加工委托单就是一个过滤链。感觉的出过滤链实际是一个很轻的临时对象,过滤器却是很重的永久对象。
实际的加工过程是怎样的呢?首先一个数据源持有一组过滤器(比如统计过滤器,比较日志过滤器,安全过滤器)每产生一个要监控的对象,比如getgetConnection时,如果这个数据源配置了filter,就生成一个过滤链filterChain(每个过滤链都持有同一个数据源,持有数据源就找的到过滤器),过滤链负责对真正执行功能的前后进行过滤操作。过滤链里面核心的是一个计数器,如同体检完成一个项目打个勾一样。过滤链的最后一个操作一定是直正执行最后的功能。而在这之前,都交给过滤链持有的数据源里的过滤器来一个个过滤,过滤时标记位置。
暂时汇总一下,执行一个功能,先生成过滤链,过滤链上一个个找过滤器来过滤,最后才执行正式的功能。
如果真和体检一样,一个个过滤了,再执行核心功能,那是比较简单的了。但我们发现更复杂一点的是,过滤链条调用过滤器时,把自己,还有数据源都传给了过滤器?干嘛把自己传给过滤器?为什么把体检表交给医生?为什么我还要告诉医生这是哪个医院?我不给医生体检表,我自己做过一个体检我自己标一下,再做下一个为何不可以?
实际上考虑的是,过滤器并不一定在核心功能前做过滤,也可以在核心功能完成后做过滤啊。这之中存在递归调用的问题。就是你到我这里体检,但我这个医生(过滤器)要求先做其它的体检和核心功能后我再做我的步骤(过滤),你的东西要压在我这里,所以你要给我体检单和医院,我安排下一个医生(过滤器)先工作,下一个工作的时候需要你的单据和其它的医生(过滤器--由数据源持有,所以传入数据源)信息,因为也可能下一个医生也这个干,把以传给我单据与医院,我转手让单据进行下一个步骤,产生一个调用栈。
以从DruidDataSource获取连接getConnection的过程为例回顾下整个过程以及为何传递的一些参数:
1.如果数据源配置有filter的时候,需要new一个过滤链filterChain,这时传递了一个this表示本数据源。否则直接获取连接。
2.如果需要filterChain时,那获取连接的任务就交给它了。为何不是让它只做过滤呢?完成后返回给自己来获取呢?原因就是前面说的,过滤是核心功能前后,存在递归。
3.既然把核心功能让filterChain做了,那它也要有条件来做这件事情,虽然真正还是要DruidDataSource来做,那就需要把DruidDataSource作为参数传递给filterChain,或者说把自己this传给它,是让它适当的时候通知自己来做。类似的模式如监听器,回调都类似。注意到1中new的时候传了this(长期组合),现在做事时又传了this(用时组合),后面说明。
4.filterChain的方法是过滤与核心功能的发起者,看看它的dataSource_connect方法。如果计数器表示还有过滤器,那就由过滤器产生connection来返回;如果计数器表示已经完成了,那就直接产生connection返回。是否感觉这里已经有点递归调用的意思了?
5.filterChain现在调用filter来做事情,我们可以猜测的出来,过滤器是不会直正做核心事情的,那让filter叙事的时候到底传什么参数呢?首先filterChain要把自己传进去,因为filter做好后,让filterChain接着做,filterChain让下一个filter做,下一个再回调filterChain,如果还有再安排下一个filter做...
6.传自己外,另外还传递了DruidDataSourcec参数给filter,filterChain要做核心工作,那需要这个参数,filter要这个干嘛?实际上是filter再回调filterChain时还给它。现实场景比如我拿着碗准备吃饭,想起去WC,就把自己告诉别人,把碗也让他拿着,一会他回调我时,把碗还给我。这里我也有一点不清楚,比如1中new出来的时候,我已经一直持有这个碗了,我要吃的时候还要告诉我一下这个碗在哪里,去WC的时候,其实我一直持有这个碗不用交给别人,别人还要还给我,不用这么麻烦吧?
7.filterChain把自己和数据源传给filter后,filter会做自己的事情,还会再调用filterChain,并把碗还给它。这两件事先后可以根据需要设计,也许调用之前做自己的记录,也许调用后做自己的记录。
8.最后提一下后面介绍的代理,filterchian最后是做核心工作,比如产生connection,或者产生resultset,这些对象都是原生对象被wrap后的对象。
总结:
看的出数据源(医院)、过滤器(医生)、每个过滤链(单据)就这三个核心对象之间,调用与持有关系都是比较复杂的,实际上想清楚动态过程就容易理解了。其实J2EE中web.xml中配置的filter,有一个dofilter方法,核心也是这么搞的,这不是直接抄代码,而是抄思路。
说起递归,对象与对象之间相互递归相对方法递归略显复杂,既然是相互调用,那必须相互引用,这里就是过滤器与过滤链相互引用,我调用你,并把我传给你,你再调用我,把你传给我...什么?传的不是过滤器,是数据源,不是正好数据源持有过滤器啊。
引伸:
我还看过一个代码是使用freemarker的,传对象给模板,模板里再使用对象,对象再调用模板....也是比较少见的代码。
另外,有一次网上看到一个代码,是三个线程要依次打印自己的内容,原代码是需要一个锁,而且线程获取锁后还要判断是不是自己可以运行,运行后通知其它wait()的线程,但可能唤醒的还是自己,效率有问题。但是我突然想到,是不是可以用递推把三个线程对象串起来,三个对象应该由一个协调器对象持有,每个对象只与它关联引用。这样虽然还是多线程关系了,但没有效率问题。又可以设想一个现实中的场景:一个大人指挥三个小朋友吃东西,让第一个吃并吃好后告诉自己(大人把自己传给小朋友),真的吃好后告诉大人时需要小朋友把自己告诉大人(小朋友把自己传给大人),这样大人可以判断下一个是谁,并检测东西还剩下多少。依次调用真到吃完为止。
再说一个问题,我之前看过一些分析,看着类图就有点晕,看着泳道又太简单,我目前不知道有什么UML图可以清楚的表达这个动静结合的设计。也许是个类初始变化,也许是类调用变化过程的一个flash动图一样的东西,也许常见的对象递归调用可以是UML中的模板。
二、代理proxy(或包装wrap、适配器adapter)是重要的技术之一
与上面提到了组合有一定的关系,如果组合的部分是人家已经开发好的完善的模块,而你要使用,那就要注意这个技术的使用了。
在druid的设计中最明显的是几乎所有的JDBC操作相关已有对象都变成了Proxy对象了。当然了,为了监控每个JDBC对象的操作,硬要每一步中插入自己要做的事情,那每个对象都要安装一个代理Proxy了。每件事都交给代理来做,代理把中间增加的事情(过滤)做好了,再让核心对象来做原来的事情。
java.sql.Connection被代理成ConnectionProxy,代理对象必然持有被代理对象,当然也有继承的,继承的方法中,自己做的事情做好后,就做super的正式的工作。
还是回到为监控而生druid,就是说做任何一个操作时,都要被监控,换句话说,就是要被过滤器过滤一遍,实际上就要产生一个过滤链,这个链在执行这个操作的过程中产生,并消亡,生命周期很短。可以看出过滤器应该是单例的,不持有过程数据,而这个过滤链是每个动作产生的,持有计数功能,他们之间还要不断的递归调用。
以ConnectionProxyImpl中的createBlob()为例:
1.createChain()---应该是每一个操作产生一个,源码几乎也是new一下,但为何那么写?
2.Blob value = chain.connection_createBlob(this);---递归调用filter的起点必然是filterchain的方法发起,最终的功能也必然在filterchain里面。
3.recycleFilterChain(chain);---ConnectionProxyImpl的每个方法调用完都重置计数为0,事实上不用重新new一个,只要置计数0就是新的了。说明这个ConnectionProxyImpl中的所有方法不可能并发调用的,否则就出问题了,所以那么写。
4.产生connection的时候有一个fillterchain,而connection本身又持有一个fillterchain,connection每做一个事情都重置计数,核心功能还是靠所持有的原生对象来完成的。
这个技术从根本上说,调用方根本不知道真正调用的是什么,是原始对象,代理对象,适配器,适配器也许自己也不知道适配谁,要由调用方的参数决定。说到adapter,看的最多的还是阿里的dubbo中,用的非常多,比如用适配器来适配不同的通讯方式。
三、复杂软件的核心功能的理解却不是很复杂,实现却相当的有难度
要做的好,知识要非常全面,即有深度,又有广度,还有规范,而每一个地方的开发,都要即了解全局,要又向上面一样细节上考量。比如这么多知识点:mbean,spi,mock,nio,protocal.zookeeper,classloader,redis,factory,serilize,anotation,multicase,netty,invoke,threadpool,reentrantLock,handler,holder,LoadBalance,Cluster,ConsistentHash,md5,sha1,LRU..还要和其它已有产品配合,比如配合spring的parser,init...
看到这么多技术,与我们平时做项目用到的比较,实在一个天上,一下地下。如果学习java,那think in java也只是入门的书籍。只有读几块砖头厚的书,紧跟最新的技术,站在前人的基础上才能做出完美的产品。
向阿里巴巴的牛人致敬!
研究一些复杂java开源软件代码的体会(转)的更多相关文章
- JAVA开源软件的技术选型--开源软件诞生2
技术准备--第2篇 用日志记录“开源软件”的诞生 赤龙ERP开源地址,点亮星标,支持一下,万分感谢 码云:https://gitee.com/redragon/redragon-erp github: ...
- 2018年7月份JAVA开源软件TOP3
微信开发 Java SDK Weixin Java Tools 评分: 9.6 介绍: 信开发 Java 开发工具包(SDK),支持包括微信支付.微信开放平台.小程序.企业号/企业微信.公众号(包括服 ...
- 【转】44款Java 网络爬虫开源软件
原帖地址 http://www.oschina.net/project/lang/19?tag=64&sort=time 极简网络爬虫组件 WebFetch WebFetch 是无依赖极简网页 ...
- 【Java&Android开源库代码分析】のandroid-async-http の开盘
在<[Java&Android开源库代码剖析]のandroid-smart-image-view>一文中我们提到了android-async-http这个开源库,本文正 ...
- GIS开源软件大全
3 - F 3map:行星地球项目由3map驱动,这是一个自由软件,由Telstra宽带基金会创建并支持,提供客户端与服务器的能力以在线再现虚拟地球. Amein!:其界面介于ArcMap和UMN M ...
- Java开源项目(备查)
转自:http://www.blogjava.net/Carter0618/archive/2008/08/11/221222.html Spring Framework [Java开源 J2EE框 ...
- 最受IT公司欢迎的50款开源软件
文章来自:云头条编译 本文介绍了多款知名的开源应用软件,科技公司可以用它们来管理自己的 IT 基础设施.开发产品. 过去十年间,许多科技公司已开始畅怀拥抱开源.许多公司使用开源工具来运行自己的 IT ...
- 用开源软件建垂直搜索引擎 转载 http://news.cnblogs.com/n/60041/
用Solr.Nutch等开源软件来构建电子元器件垂直搜索引擎涉及很多实现细节,本文结合实际应用系统对数据采集.中文搜索.结果输出.分页处理.整合数据库等重点问题提出了切实可行的解决方法. 用开源软件建 ...
- Spring Cloud (十四):Spring Cloud 开源软件都有哪些?
学习一门新的技术如果有优秀的开源项目,对初学者的学习将会是事半功倍,通过研究和学习优秀的开源项目,可以快速的了解此技术的相关应用场景和应用示例,参考优秀开源项目会降低将此技术引入到项目中的成本.为此抽 ...
随机推荐
- mysql-用正则表达式进行搜索
正则表达式的作用是匹配文本,将一个模式(正则表达式)与一个文本串进行比较,mysql允许你指定正则表达式,过滤select检索出的数据.但是mysql仅仅支持正则表达式的一个子集. 1.基本字符匹配: ...
- 整合大量开源库项目(五)跳动的TextView JumpingBeans,良好体验的滚动条ConvenientBanner
转载请注明出处:王亟亟的大牛之路 时间过得非常快,这一系列已经写了第五篇了(感觉还要写好久).今天又引入了2个非常好用的库JumpingBeans,ConvenientBanner.首先.先看一下效果 ...
- 有关Java基础的一些笔试题总结
针对近期腾讯.京东.网易等公司的笔试.遇到一些有关Java基础的问题,在此总结.希望能通过这几道经典问题题发散,举一反三.借此打牢基础! 自己总结,望提出宝贵意见! 一.关于null的一道小题 先开开 ...
- Linux下PHP开启Oracle支持(oci8)
使用php的常见问题是:编译php时忘记加入某扩展,后来想加入扩展,可是由于安装php后又装了一些东西如PEAR等,不想删除文件夹重装,那么此时就须要自己又一次添加某模块支持了,Linux操作系统下能 ...
- 关于Android真机调測Profiler
u3d中的Profile也是能够直接在链接安卓设备执行游戏下查看的,导出真机链接U3D的Profile看数据,这样能更好的測试详细原因. 大概看了下官方的做法.看了几张帖子顺带把做法记录下来. 參考: ...
- 5.IntellijIDEA常用快捷键总结
转自:https://blog.csdn.net/qq_17586821/article/details/52554731下面的这些常用快捷键需要在实际操作中不断地体会才能真正感受到它们的方便之处. ...
- Asp.Net中使用水晶报表(下)
Asp.Net中使用水晶报表(下) 使用PUSH模式 我们采用下面的几步使用Push模式执行水晶报表: 1. 设计一个DataSet 2. 创建一个.rpt文件同时将其指定给上一步建立的DataS ...
- Swift学习笔记(2):错误处理
目录: Error do-catch 断言 Error 在 Swift 中,错误用符合 Error 协议的类型的值来表示.这个空协议表明该类型可以用于错误处理异常. Swift 的枚举类型尤为适合构建 ...
- AngularJs轻松入门(四)模块化
在前面几节教程中,代码比较少,为了方便说明问题笔者將控制器代码都写在了HTML页面中,实际上这并不是什么好的编程习惯,而且可维护性差.通常的做法都是將处理业务逻辑的代码写在一个单独的JS文件中,然后在 ...
- PL/SQL恢复默认窗口样式
对于初学者来讲,使用PL/SQL时会不小心将窗体关闭,如下图 2. 怎么恢复呢?其实很简单 3. 搞定 转自:http://blog.csdn.net/hello_word2/article/deta ...