`
herman_liu76
  • 浏览: 99556 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

研究一些复杂java开源软件代码的体会

阅读更多
    有时候看源代码是非常有趣的事情,象是思考游戏,象是思考棋局...
    平时做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设计模式是软件开发者的必备知识,它们提供了解决常见问题的标准方法,提高了代码质量和可维护性。通过学习和应用设计模式,开发者可以更好地应对复杂系统的设计,同时也能提升个人的编程素养。

    java学习心得体会

    通过分析开源项目或Java标准库,可以了解实际应用中的最佳实践和设计模式。比如观察ArrayList和HashMap的实现,可以学习如何优化数据结构和算法。 理论与实践相结合的同时,还要注重工具的使用。IntelliJ IDEA、...

    SSH项目源码及心得体会

    SSH框架,全称为Struts2、Spring和Hibernate的组合,是Java Web开发中常见的三大开源框架。这个"SSH项目源码及心得体会"的资源对于初学者来说尤其宝贵,因为它不仅包含了实际项目的源代码,还记录了开发者在整合和...

    2022年Java工程师培训心得体会范文.docx

    根据给定文件的信息,我们可以提炼出关于Java工程师培训的一些关键知识点和建议,这些内容对于希望从事Java开发工作的人来说非常有用。 ### 培养兴趣 兴趣是最好的老师,尤其是在技术领域。学习Java或者其他编程...

    Java源码阅读的真实体会.精选.doc

    从仅关注工程本身,到主动探索技术细节,再到深入研究开源项目,这种不断提升的过程需要持久的热情。阅读源码不仅仅是理解代码实现,更是理解设计模式、架构决策和性能优化的过程。 对于初学者,建议从JDK源码开始...

    java观后感3

    对于Java源码的学习,通常是为了深入理解其工作原理,提升编程技巧,或者研究开源项目的实现。 在学习Java源码时,开发者会关注类的结构、设计模式、异常处理、多线程、网络编程、I/O操作、集合框架等方面。Java的...

    软件测试实验教程朱少民版实验报告七

    PMD是一个强大的开源代码分析器,能够帮助开发者找出并修复代码中的常见编程缺陷,提高代码质量和可维护性。 1. PMD介绍: PMD的主要功能是对Java代码进行静态分析,检测出潜在的问题,如未使用的变量、空的catch...

    java-ee-实验一.docx

    - **经验教训**:通过本次实验,可以深刻认识到实践的重要性,同时也能体会到软件开发中的常见挑战,如数据验证、异常处理等。 总之,通过这些实验的学习,不仅可以让学生深入了解Java EE的基础知识和技术栈,还能...

    Java夜未眠.程序员的心声-1

    开发者可能会分享他们在解决复杂问题时的心得,如何通过阅读源码、查阅文档,或者参与开源社区来提升自我。 最后,文档可能还会涉及持续集成/持续部署(CI/CD)流程,如Jenkins、GitLab CI/CD等,以及DevOps文化对...

    java报表jfreechart开发

    JFreeChart是一款免费且开源的Java图表库,它提供了多种类型的图表绘制功能,如饼图、条形图、折线图等。JFreeChart适用于各种Java应用程序中,包括但不限于桌面应用、Web应用以及移动应用。它不仅能够生成高质量的...

    java基础教程(初级)

    初学者可以通过GitHub等平台参与开源项目,或者自己动手实现一些小项目来加深对Java的理解。 3. **社区交流**:加入技术社区或论坛,如Stack Overflow、Reddit等,可以帮助解答学习过程中的疑问,并且可以与其他...

    javaEE总结

    DOM4J是一个开源的Java XML API,它在灵活性和性能上优于JDOM,同时提供了一个接口和抽象类的模型。尽管API相对复杂,但它的功能强大且易于使用。DOM4J广泛应用于如Hibernate和Sun的JAXM等项目。使用DOM4J,开发者...

    java7源码-ReviewJavaFoundation:重温Java基础系列源代码

    所以,近段时间开始,将重温Java技术的基础知识,主版本为Java8,中间可能也会穿插一些其它版本,如Java7。谨以此系列文章,来记录重温Java基础的历程。 为啥费劲写这些文章?和写SpringCloud进阶之路时一样,一是...

    y.rar_AGENT JAVA JADE_jade multi agent_multi agent_multi agent j

    Java Agent Development Framework (JADE) 是一个开源的、跨平台的MAS开发工具,它为构建和部署基于Java的智能体提供了强大的支持。 标题"y.rar_AGENT JAVA JADE_jade multi agent_multi agent_multi agent j"揭示...

    javaWeb 寝室管理系统

    Tomcat 7.x作为Web服务器,是Apache软件基金会的Jakarta项目的一部分,它轻量级、高效且开源,支持Servlet和JSP规范,是JavaWeb应用的理想选择。MyEclipse是Eclipse的一个扩展,提供了丰富的JavaEE开发工具,包括对...

    基于java的邮件系统.doc

    开发工具可能包括Eclipse、IntelliJ IDEA等,用于编写和调试Java代码。数据库管理系统选择MySQL,以满足稳定性和数据完整性的需求。服务器平台可能选择Windows XP,并采用Apache James作为邮件服务器,它是一个开源...

    国产java类cms v3.0.161109.zip

    通俗来说,其实就是帮助你以最便捷的方式将各种类型的数据规范入库,尽可能少写代码,越少越好,做到极致,功能及页面都靠配置来完成,且能够即时修改即时显示,再配合一些工具及技巧就极大地提升了效率。...

    Java学习路线.pdf

    通过以上六个阶段的学习,您将逐步建立起扎实的 Java 技能基础,并能够独立完成复杂的项目。不断实践和学习新技术,是成为优秀 Java 开发者的必经之路。希望这条学习路线能够帮助您顺利地踏上 Java 开发之旅。

    (完整版)java交友网站毕业设计论文.pdf

    根据给定的文件信息,本篇毕业设计论文主要讲述了基于JSP技术的交友网站的设计与实现。...通过这些内容的学习,可以加深对构建动态Web应用的理解,特别是对JSP技术在实际开发中的应用有更深刻的体会。

    C#和.Net的优点研究了一下C#和.Net,有很多体会,好的不好的都有。随便谈谈,供大家参考。

    相比之下,Java需要与各种数据库和应用服务器协同工作,其开发过程更为复杂,尤其是EJB的部署,需要考虑多种App Server,增加了项目的复杂性和开发时间。 综上所述,C#和.Net框架的结合提供了高效的底层访问能力、...

Global site tag (gtag.js) - Google Analytics