1、前言

最近写一个Java处理工具,是一个springboot的非web项目,正好借这个机会总结一下自己的经验,当开发一个Java应用时,应该全局考虑哪些方面,包括如何划分功能包,如果建立对象关联,以及如果串起流程;另一方面在这个过程中,作为喜新厌旧的程序员,全面应用Jdk8的新特征,比如其中的语法糖还是蛮甜的。

2、类划分功能包

web项目是很多人接触最多的项目,与工具项目有相似点,也有不同点。一般的单体的web项目通常的包,表面上按这样划分:

多层次业务模块->controller->service->dao这么几层。而从实现了一个请求的单向处理过程看,服务端实际上完整过程是这样:通讯协议处理->filter->dispatchservlet->controller->service->dao。由于框架做了很多事情,开发人员很可能只需要固定的套路,而不需要深入研究,基本上技术是简单的CURD操作。

我这个工具过程功能比较少:解析excel->输出txt->合并txt三个步骤,虽然不多,但尽可能让结构合理,这样想增加功能也方便,也可以扩展出的很复杂功能。

  1. 为什么分层分包,而不是铁板一块?因为把不变的和可变的进行区分,有利于修改,有利于重用,有利于加塞…缺点嘛,要建立关联关系,把分散的东西有机结合起来,这个难度略大,不理解的人不方便定位功能点,更不好修改,易打乱结构。
  2. 为什么做软件强调抽象思维,这个过程就是有效的划分块。实现高聚合低耦合软件的也必要能力。
  3. 通常写web的,需要的包只是处理框架的一部分,所以不用不完整的过程。自己写工具就是全过程了,所以写web不利于提高java开发水平,重点是业务理解了。
  4. 看到过有代码,把web层的东西写到service中,比如httpServletRequest传了过去,是对分层缺少理解。危害是什么呢?如果换了通讯协议,比如http换grpc,service也要改,本来可以不改的。大多数情况下不会换,但可能service会组合,也是潜在问题。VO/DTO/BO这样的对象区别,这在于它们处于不同的层次。
  5. 同一层次的东西放在一个包里,包里面一般分两层,包中的根目录放接口,抽象类与参数对象等。包里的其它目录一般是接口的多种实现类。web项目的controller层中,接口在框架里了,都是实现类,所以一个业务模块只一层。
  6. 包里可以有包,类似组织架构,那调用层与被调用层是同级还是内部?service为何不放controller包里,dao为何也独立出来?一是因为service理论上可以被其它协议调用,不用controller这个http框架。dao可能因为调整多,放太深也不好。
  7. 包划分示例:常见的dubbo,划分了很多层,最核心的远程rpc功能只有rpc通讯层,remote传输层两个。其它是辅助或者是另外的功能块,比如serialize,monitor是公共的,registry是另一功能了。理解源码就要先有全局思路,比如理解功能与多层次目录的划分,就知道作者怎么想的,各个功能在点哪点。remote层里有多种传输,比如http/p2p/telnet,也有自己的。自己的长连接还要心跳机制,也有重连机制,所以里面还有head处理等更深的层次。一个大项目,排放的条理性,科学性很重要,写起来思路连贯,找起来一歩定位。
  8. 再比如rocketmq,模块有业务块与name块,最核心的业务功能是接收消息并存下来,所以有broker包与store包。其它还有remote块,通讯肯定是公共的。
  9. 包内组织示例:dubbo为例,它是微核心,几乎所有的东西都是接口化,甚至对象仓库也是,有两种仓库,一种是spring库,一种是自己写的。前面说了,包内分接口公共类与各个实现的子包,有些实现是自己的,有些是用第三方的。每种方式就一个目录,第三方功能所在的目录里一般都是对第三方接口类的包装类,包装过程也是对多种第三方实现的的抽象过程,因为第三方功能可能差别不少,要兼容适配。这里面可能用到代理、装饰,门面等设计模式。

3、类(包)之间关联

划包与包内组织好了,下面就是建立包之间调用关联了,主要是调用的对象之间,通过属性引用起来的。比如@autowired。还得不说spring容器的强大了。它可以按照你提供的配置蓝图与零件,给你组装生成出一个可以运转的机器。它的依赖注入,多个过程(比如前置,后置,生成,初始化,销毁等)中插入你的处理逻辑,比如factorybean产生代理对象,它的延时加载,条件加载,让你的这个配置蓝图可以非常的复杂。前面提到dubbo微核心,自己实现了一个小的容器,也有依赖注入,最有特色的是,用接口类型获取对象时,特殊注解的接口,它可以用代码动态生成接口的代理类对象给你,而这个代理对象根据请求参数不同,代理不同的实现类,具体代理哪个,又是从容器中取的。这就厉害了,一方面减少了spring容器的冗余功能,另一方面实现了自己特殊的需求,再着体现了作者的实力。

  1. 一般就不要自己处理引用了,就用spring的,除非比较简单的。如果是引用多个实现,可以@Autowired Map,用的时候可以按名字取真正的实现类。我的项目中启动类就是引用了不同excel的解析类,根据特征名字选择调用。
  2. 引用可以是单向的,也可以是双向的。用@Autowired不用管了,自己写一般就是构造函数或者Init()方法中引入被调用对象,双向就是之后再把自己(this)传给调用对象的设置方法。
  3. 引用还可以是间接的,间接时就要有人穿针引线,通常就是listenner对象。双向引用就是方便反馈结果,用间接则增加了灵活性,反馈可以给自己,也可以给别人,也可以广播,更灵活。至于反馈给谁,调用方产生监听器时会指明的。比如领导调用员工,双向引用(留下手机号)就可以直接反馈结果给领导,也可以间接方式,把秘书手机给员工,并明确秘书收到结果反馈自己和另一领导。
  4. 我的工具中,【解析类】处理时,使用了easyexcel包。先产生一个配置【数据收集类】的【监听器】,把一个【监听器】给easyexcel,监听到数据时,会通知【数据收集类】存起来。
  5. 这里说的关联关系,都是spring默认的singleton类。至于【监听器】,虽然可能也只是一个,但一般不当成@Component,可以说是它的地位太低吧。而是用的时候new一个,复杂的用工厂临时造一个对象。【监听器】通知谁,不是自己@Autowired的,而是由安排它的类指派的。
  6. 还有一种业务关联关系,类之间本身没有任何联系,而是由业务配置联系起来的,这个最典型的就是工作流了,特别是条件节点,根据特征数据不同,调用不同的处理类。还有就是责任链模式,一个个独立的处理节点类,是由另外的数组或者链表前面关联起来的,最常见的是filterChain。特别注意的是filterChain不是单例,每一个处理建一个新的filterChain,因为内部要记录当前起到哪一步了,而链条上的节点filter是单例。这个生活中的例子就是大家参加体检,每人一个体检单chain,而各个科室就是节点,每个人的体检单上打过勾的都可能不同。更复杂的后面的节点,由前面的节点根据情况产生。
  7. 责任链的例子非常多,除了servelet的filter外,druid中的监控功能也是,所以相关的sql类都被代理,代理蹭就插入了一个短chain,其中一个节点功能就是输出监控信息。springaop中也在Invocation中建了一个链,因为一个方法上可以有多个advise,要组织成链条。我以前也用过,一个请求要经过多次处理,如果中间因故断了,还可以再次请求,找到后续处理的索引,继续后续的处理。
  8. 有了spring,类的引用太容易了,但要避免网状引用。而应该用树状引用。最典型的就是rocketmq中,非常多的类都是通过核心协调的controller类调用的,这个不是web中的controller。部门间调用都通过领导。

4、类(包)关系的变更

类的关系建好了,也不是一成不变,用户需求会变,功能要增加,低耦合的目的就是应对这种变化,如果事先考虑这种变化,可以有一定的机制,把变成留给用户,并规范用户的代码。如果没有,变化也可以限制在一定的变动量下。而用户插入的功能可能多个,所以经常发现用责任链模式串起来。

  1. filter机制就是插在http的请求中的过程,可有可无,用户自己来实现具体的filter。但是这限制在特定协议中的灵活,所以spring有自己的interceptor机制。我参与的项目中,平台框架自己弄了一个hander机制。
  2. 如果没有考虑到这个变化,spring提供了aop机制。
  3. 如果原有的关系中插一层关系,而且没有spring,就要用代理的思路,比如druid把所有的数据库操作都包装了一下。中间插入自己的日志处理chain节点,想增加也是很方便的。
  4. 比如我现在做的多个excel解析,可以进一步搞的很复杂,前面可以增加多种协议处理远程excel,也可以解析其它格式文件,解析好的数据可以放内存,也可以放数据文件暂存,输出片断可以是txt,也可以是其它类型。想要日志的话,可以找个地方插入责任链。想切换easyexcel到poi,也可以分别包装一下,上层调用包装接口,具体实现可以在配置中写,也可以从参数中拿。如果要增加加解密操作,可以增加处理层,也可以包装已有的加密方式。如果要增加数据查询,可以在暂存层上増加查询功能,而查询可以是http的请求方式,也可以弄成部分支持sql格式的查询。总之,高聚合的东西不太变,低耦合的设计就是考虑变化的。
  5. 设想,如果没有很好的抽象思维,合理科学的分层 ,改动量与改动难度就越来越大。当然大多数工作都是简单的,专注于业务的,所以乱一点问题不大。这也就是可能做过很多业务,但真正的技术水平还不高的原因。

5、类的自我管理

大型项目,越看越感觉象生活中的场景,比如象构建一个医院,服务一个个病人。过程中有保安进门,有挂号产生一次服务链,每个科进行本科室的处理,人多了排队,再多限流。当然也有行政部门与科内部进行自我的管理,比如事先准备好设备,内部排班,整理统计数据等。发明面向对象编程的人太伟大了,贴合生活,易于理解并设计非常复杂的项目。

  1. spring中产生一个新的对象,有init(),有distroy()进行统一处理,可以做准备动作,也可以优雅结束,说明对象有完整的生命周期。前面提到的业务对象,一般是用完自己销毁了,不考虑这个。
  2. 一般的自我管理是通过内部的管理线程进行的,比如线程池内处理线程的增加与减少。连接池内连接数的增加与减少。微服务中可用服务的从zookeeper上更新等等。远程连接的重连机制,心跳机制。
  3. 这个小工具没有这方面的管理需求,只是init()时,把配置文件中的数据,设置给static变量。因为之前很多地方按static中取的,不想改。

6、全面使用jdk8新特征

8已经老了,但很多项目更老,而且老程序员习惯不容易变。我却喜欢尽可能用点新的,了解新特征产生的原因,才能更好的用好。

- 多线程控制

多个地方用了CompletableFuture,使用这个和lambda表达式,由于可以把方法作为参数进行传递。编程思路要有一定的变化。这就是把大的处理过程,拆分成多个子过程,过程之间有串、并等组合关系,最后可以异步获取最终结果。下面这个是主线程等待多个子线程完成。

     List<CompletableFuture<Void>> outputCfList = new ArrayList<CompletableFuture<Void>>();txtFileMap.forEach((k,v)->{outputCfLst.add(CompletableFuture.runAsync(()->v.createTxt(filePath)));//甜甜的语法糖});CompletableFuture.allOf(outputCfList.toArray(new CompletableFuture[txtFileMap.size()])).join();
     new Thread(() -> {String[] fileList = argsFile.list((dir, name) -> name.toUpperCase().endsWith("XLSX"));//甜甜的语法糖LOGGER.info("the directory's total xlsx files number is [{}]", fileList.length);String rawExcelFile = "";for (String fileName : fileList) {。。。。。。。。。。。}if (StringUtils.isNotEmpty(rawExcelFile)) {LOGGER.info("find raw excel file and begin generate new excel file...");convertMap.get("rawConverter").read(argsFile.getPath() + File.separator + rawExcelFile);}dealRawExcelLock.countDown();}).start();// 主线程等待前一步生成exceldealRawExcelLock.await();

上面这段是用传统的CountDownLatch,因为额外加了一个前置处理过程。

- stream

这是一个把配置的string转成map的语句,也是因为习惯使用springboot配置的方法,后来这个没用了,改成配置类了。不过这样的语句写的很简洁吧,也许有人认为不好理解,其实用几次,你就会喜欢上。

     Map zone2provinceMap = Arrays.stream(zone2province.split(";")).filter(kv -> kv.contains("=")).map(kv -> kv.split("=")).collect(Collectors.toMap(x -> x[0], x -> x[1]));

其它简单的lambda表达式,optional就不提了,简单,好用,甜,上面代码里都有。

- 额外提一下spring的yml配置文件

以前一直用properties文件,因为这个工具有很多字典项,以前用DB存的多,现在用配置文件转map存了,而且有中文。所以开始用yml配置文件了。省的properties的中文还是unicode编码,不方便。

写的过程中,进一步体验并规范了项目的层次感。所有的业务字典map,全都在bizmap下。用一个统一的配置类解析持有,类很简单,增加一个map也很简单,如下,其它类用的地方就@Autowried一下。

@Configuration
@Data
@ConfigurationProperties(prefix="bizmap")
public class ConfigBizMap {public Map<String,String> zone2province;public Map<String,String> ...;public Map<String,String> ...;public Map<String,String> ...;public Map<String,String> ...;public Map<String,String> ...;
}

7、总结

最近看到银行的大型分布式项目中的很多基础平台代码,总的来说,都逃不出上面总结的内容。当看过复杂的,著名的工程代码,掌握了上面的规律,再看这些都相对简单多了。这样可以很快掌握作者的思路,方便用好现有框架,也方便查问题,方便做功能完善。目前问题是大项目功能内容太多了,广度问题,比较花时间。

如何设计一个结构合理的java项目相关推荐

  1. 有始有终,设计一个结构合理的下载模块

    完成开发任务的同时,我们总希望自己能够交付高质量的代码.代码质量的测度有很多方法,可扩展性.可复用性是其中的两项指标.设计模式的理论能够非常有效地指导代码设计,但是光谈这些理论是非常抽象的,本文针对下 ...

  2. 云效(原RDC)如何构建一个基于Maven的Java项目

    最近在将公司的持续集成架构做一个系统的调整,调整过程中受到了RDC团队大量的帮助,所以利用国庆时间写了几篇RDC的分享,希望能让更多的人了解和用好RDC这个产品. 我会把我最近3个月的使用体会分成5个 ...

  3. 现要为某一个销售部门编写一个程序管理约100种商品。要求设计一个结构体类型来描述商品,每种商品包括商品编号(如A001)、商品名称、商品销售量和商品销售额等信息,并编写以下函数···········

    原题:现要为某一个销售部门编写一个程序管理约100种商品.要求设计一个结构体类型来描述商品,每种商品包括商品编号(如A001).商品名称.商品销售量和商品销售额等信息,并编写以下函数: 1.编写一个函 ...

  4. 设计一个排课系统(Java实现)

    题目: 设计一个排课系统 内容: 设计一个排课系统,要求从文本文件导入教室情况(大小,数量).培养方案(课程.学生年级.人数.教师).约束条件(有些约束从培养方案中可知,如一个教师可以教授两门课不可以 ...

  5. 用java设计一个动物声音 模拟器,Java语言 设计一个动物声音“模拟器”(用接口实现)...

    设计一个动物声音"模拟器",希望模拟器可以模拟许多动物的叫声,要求如下. 编写接口Animal Animal接口有两个抽象方法cry()和getAnimalName(),即要求实现 ...

  6. 设计一个长方体类Cuboid(Java)

    设计一个长方体类Cuboid (10 分) 要求:设计一个名为Cuboid的类表示长方体.这个类包括三个名为length.width和height 的double型数据域,它们分别表示长方体的长.宽和 ...

  7. 如何设计一个“成功”的区块链项目?

    前言 区块链的概念已经在国内传播两年多了,越来越多的人开始了解区块链的原理.架构和开发技术.同时,区块链相关的项目也是层出不穷.但是,除了比特币,还没有消费级的产品出现. 现在,区块链领域在快速发展, ...

  8. 设计一个user类(Java)

    class User {private String name;private String password;private static int count=0;//count用于统计个数必须用s ...

  9. java项目构建部署包

    博客分类: JAVA Java 工程在生产环境运行时,一般需要构建成一个jar,同时在运行时需要把依赖的jar添加到classpath中去,如果直接运行添加classpath很不方便,比较方便的是创建 ...

最新文章

  1. Django模拟新浪微博的@功能
  2. python语言下载-python下载_python免费下载[编程工具]-下载之家
  3. ITK:在不复制内存的情况下为每个像素添加常量
  4. 北京大学c语言试题及答案,北大网络教育(计算机专业)C程序设计作业答案
  5. HDU 4946 Area of Mushroom 凸包 第八次多校
  6. php.ini添加的变量读取,PHP5 在扩展里使用 INI 指令(直接添加和配合全局变量两种方式)...
  7. linux c++ 输出到终端,如何将彩色文本输出到Linux终端?
  8. Kubernetes详解(十五)——Pod对象创建过程
  9. java executor 例子_Java中Executor框架的实例
  10. linux常用运行库,软件常用运行库-软件常用运行库scku下载 v3.1.0.0官方版--pc6下载站...
  11. 安泰测试新手教程-泰克数字示波器使用方法
  12. 基于C语言的移位密码和仿射密码
  13. looking for domain authoritative name server and domain name location
  14. 什么是UL2809认证?
  15. 初识edb debugger
  16. java通过poi生成excel并下载出现文件打不开、文件格式和文件扩展名无效问题的分析与解决
  17. 软件开发需要编写的那些文档
  18. 使用ARP欺骗, 截取局域网中任意一台机器的网页请求,破解用户名密码等信息
  19. 【报告】德勤管理咨询2019年境内外TMT标杆企业高管薪酬与激励调研报告
  20. 2113: 小飞的游戏

热门文章

  1. ERP系统常用SQL集锦
  2. 看到这些让员工无处遁形专利,我麻了
  3. 信息奥赛一本通1225:金银岛
  4. 记应聘:华为 可信理论、技术与工程实验室 产品数据工程师
  5. 【免杀前置课——Windows编程】五、窗口控件——什么是控件、Windolws 窗口两大类、Windows标准控件/通用控件、控件响应的接收、创建窗口制作不同控件
  6. FE_CSS 页面布局之定位
  7. 任正非揭秘华为鸿蒙,任正非揭秘!华为鸿蒙系统,比安卓、苹果系统快,为物联网而生...
  8. stitching_detailed.cpp : throw_no_cuda
  9. FreeBSD中编译JDK
  10. vue中created、mounted、activated的区别