【阿里巴巴Java编程规范学习 六】Java工程结构规约

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【阿里巴巴Java编程规范学习 六】Java工程结构规约

Java工程结构也是比较重要的部分,由于最近在使用刚初始化的SpringBoot项目,所以本篇Blog重点讨论应用分层划分以及依赖处理服务器部署参数相关,对项目全局有个整体认知,结合《阿里巴巴代码规范》的分层和自己的项目进行一个依照模块的分层划分。红色加粗字体为自己可能会犯的错误以及不规范的地方,蓝色结论部分为几条规则的归纳或一条规则的阐述。

应用分层

依据阿里巴巴给出的分层方案,再结合实际自己确定一个分层分模块的方案

1 分层概览

根据业务架构实践,结合业界分层规范与流行技术框架分析,推荐分层结构如图所示,默认上层依赖于下层,箭头关系表示可直接依赖,如:开放 API 层可以依赖于 Web 层(Controller 层),也可以直接依赖于 Service 层,依此类推:

各分层解释如下:

  1. 开放 API 层:可直接封装 Service 接口暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控制层等。
  2. 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。
  3. Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
  4. Service 层:相对具体的业务逻辑服务层。
  5. Manager 层:通用业务处理层,它有如下特征:
  1. 对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。
  2. 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
  3. 与 DAO 层交互,对多个 DAO 的组合复用。
  1. DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase、OB 等进行数据交互。
  2. 第三方服务:包括其它部门 RPC 服务接口,基础平台,其它公司的 HTTP 接口,如淘宝开放平台、支付宝付款服务、高德地图服务等。
  3. 外部数据接口:外部(应用)数据存储服务提供的接口,多见于数据迁移场景中

如果按照模块划分,可以划分如下几个Module:

  • Web为一个Module(Web)
  • 如果有更加复杂的多模块融合业务处理,还可以加一个BIZ的Moudle向Web输出服务
  • 单一模块垂直为一个Module(API+Service+DAO),例如订单模块,公司模块
  • Manager为一个或多个Module,例如Search服务的Module,缓存服务的Module,Kafka服务的Module
  • 第三方服务为一个Module(RPC调用)
  • 数据库结构库表等为一个Module(db)

综合了Module分模块之后的分层调用关系我认为应该是这样的:

2 分层异常处理规约

可以按照以下几条原则去处理分层的异常,

  • 在DAO层,产生的异常类型有很多,无法用细粒度的异常进行catch,可以使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为异常在 Manager/Service 层一定需要捕获并打印到日志文件中去,同台服务器再打日志浪费性能和存储。DAO层的异常抛出并且不打印日志
  • 在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息, 相当于保护案发现场Service层的异常继续抛出但需要打印日志(尽可能带上参数信息),保留案发现场
  • Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。
  • Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面, 尽量加上友好的错误提示信息。Web层的异常需要捕获并且把错误信息返回给前端,让前端跳转到合适的错误页面或者显示错误信息。不能再往外层抛了,因为抛无可抛
  • 开放接口层(API)要将异常处理成错误码和错误信息方式返回。

综合而言就是DAO粒度太细就不记录了,Service层因为带有业务含义需要记录日志来排查问题,Web层则应该对错误给出自己的应对机制了。

3 分层领域模型规约

各个层需要有自己的数据对象,每一层向下一层传递参数的时候下一层都需要转换为本层使用的对象

  • DTO(Data Transfer Object): 数据传输对象,Web、Service 或 Manager 向下传输的入参对象,也是Service或Manager的返回对象,但是通常分两个文件夹,例如RequestDTO,ResponseDTO.
  • DO(Data Object): 此对象与数据库表结构一一对应, DAO 层向下传输数据源对象,也是DAO层的返回对象,但是通常分两个文件夹,例如RequestDO,ResponseDO.
  • BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
  • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类 来传输。这里个人容易不符合规范
  • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象,Web层的返回对象

以上提到的这些对象都属于POJO对象(Plain Ordinary Java Object):,在本规约中,POJO 专指只有 setter/getter/toString 的对象。如果加上数据模型的概念,那么分层模型图如下:

二方库依赖

什么是二方库?几方库的定义概念如下:

  • 一方库:本工程内部子项目模块依赖的库(jar 包)。
  • 二方库:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)。
  • 三方库:公司之外的开源库(jar 包)

所谓二方库依赖就是规范我们如何定义GAV(GroupId、ArtifactId、Version)(也就是Maven坐标,是用来唯一标识jar包)依赖的使用规范

1 二方库定义规范

1 【强制】定义GAV遵从以下规则:

  1. GroupID 格式:com.{公司/BU }.业务线 [.子业务线],最多 4 级。{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一级,子业务线可选。例如:com.taobao.jstormcom.alibaba.dubbo.register
  2. ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下,例如:dubbo-client / fastjson-api / jstorm-tool
  3. Verson格式:主版本号.次版本号.修订号,注意起始版本号必须为:1.0.0
  • 主版本号: 产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。
  • 次版本号: 保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。
  • 修订号: 保持完全兼容性,修复 BUG、新增次要功能特性等。

2 ,【强制】线上应用不要依赖SNAPSHOT版本(安全包除外),正式发布的类库必须先去中央仓 库进行查证,使 RELEASE 版本号有延续性,且版本号不允许覆盖升级。

  • 说明:不依赖 SNAPSHOT 版本是保证应用发布的幂等性。另外,也可以加快编译时的打包构建。

3,【强制】二方库的新增或升级,保持除功能点之外的其它 jar 包仲裁结果不变。如果有改变,必须明确评估和验证。

  • 说明:在升级时,进行 dependency:resolve 前后信息比对,如果仲裁结果完全不一致,那么通过 dependency:tree 命令,找出差异点,进行<exclude>排除 jar 包。

4, 【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象

5 ,【推荐】二方库不要有配置项,最低限度不要再增加配置项。

6,【参考】为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则**:

  • 精简可控原则。移除一切不必要的 API 和依赖,只包含 Service API、必要的领域模型对象、Utils 类、 常量、枚举等。如果依赖其它二方库,尽量是 provided 引入,让二方库使用者去依赖具体版本号,无 log 具体实现,只依赖日志框架。
  • 稳定可追溯原则。每个版本的变化应该被记录,二方库由谁维护,源码在哪里,都需要能方便查到。除 非用户主动升级版本,否则公共二方库的行为不应该发生变化。

2 二方库使用规范

1 【强制】依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致

  • 说明:例如依赖 springframework-core,-context,-beans,它们都是同一个版本,可以定义一个变量来保存版本:${spring.version},定义依赖的时候,引用该版本。

2, 【强制】禁止在子项目的pom依赖中出现相同的GroupId,相同的ArtifactId,但是不同的 Version

  • 说明:在本地调试时会使用各子项目指定的版本号,但是合并成一个 war,只能有一个版本号出现在最后的 lib 目录中。曾经出现过线下调试是正确的,发布到线上却出故障的先例。

3 ,【推荐】底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现。

4 ,【推荐】所有pom文件中的依赖声明放在<dependencies>语句块中,所有版本仲裁放在 <dependencyManagement>语句块中。

  • 说明:<dependencyManagement>里只是声明版本,并不实现引入,因此子项目需要显式的声明依赖, version 和 scope 都读取自父 pom。而所有声明在主 pom 的里的依赖都会自动引入,并默认被所有的子项目继承。

5 【推荐】不要使用不稳定的工具包或者 Utils 类

  • 说明:不稳定指的是提供方无法做到向下兼容,在编译阶段正常,但在运行时产生异常,因此,尽量使用 业界稳定的二方工具包。

服务器

1 【强制】高并发服务器建议调小TCP协议的time_wait超时时间这里个人容易不符合规范

  • 说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务器端会因为 处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。例如:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout = 30

2 【强制】 调大服务器所支持的最大文件句柄数(FileDescriptor,简写为fd)

  • 说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应于一个 fd。 主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。建议将 linux 服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)

3 【强制】给 JVM 环境参数设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM场景时输出 dump 信息这里个人容易不符合规范

  • 说明:OOM 的发生是有概率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题非常有帮助

4 【强制】在线上生产环境,JVM的Xms和Xmx设置一样大小的内存容量,避免在GC 后调整 堆大小带来的压力

  • -Xmx用来设置应用程序,也就是Java程序(不是JVM)能够使用的最大内存数,如果程序要花很大内存的话,那就需要修改缺省的设置,比如配置tomcat的时候,如果流量啊程序啊都很大的话就需要加大这个值了,BUT不要大得超过你的机器的内存。
  • -Xms用来设置程序初始化的时候内存栈的大小,增加这个值的话程序的启动性能会得到提高。不过同样有前面的限制,以及受到-Xmx的限制

5 【强制】服务器内部重定向必须使用forward; 外部重定向地址必须使用URLBroker生成,否则因线上采用 HTTPS 协议而导致浏览器提示不安全。此外,还会带来 URL 维护不一致的问题。

总结一下

服务分层给我的启发还是蛮大的,因为项目中很容易会有分层不清晰的场景出现,依据规范里的分层结合项目实际画的这个图明确了项目中的各层的作用和概念,例如提供数据服务的和业务服务的代码,以及对外提供的API,远程调用RPC模块,复杂业务的BIZ层,总体来说还是软件设计的基本原则:高内聚,低耦合。模块的职责单一化,内部实现对外部透明。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
23天前
|
Java 数据库 数据安全/隐私保护
银行流水生成器在线制作,银行转账p图在线生成,java实现最牛的生成器【仅供学习用途】
本资料探讨银行系统核心技术,涵盖交易记录生成、电子回单加密验真及基于Java的财务管理系统开发。主要内容包括:交易记录实体类设计(不可变性与数字签名)
|
24天前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
20天前
|
Oracle Java 关系型数据库
java 入门学习视频_2025 最新 java 入门零基础学习视频教程
《Java 21 入门实操指南(2025年版)》提供了Java最新特性的开发指导。首先介绍了JDK 21和IntelliJ IDEA 2025.1的环境配置,包括环境变量设置和预览功能启用。重点讲解了Java 21三大核心特性:虚拟线程简化高并发编程,Record模式优化数据解构,字符串模板提升字符串拼接可读性。最后通过图书管理系统案例,展示如何运用Record定义实体类、使用Stream API进行数据操作,以及结合字符串模板实现控制台交互。该指南完整呈现了从环境搭建到实际项目开发的Java 21全流程实
47 1
|
24天前
|
算法 Java 测试技术
Java 从入门到实战完整学习路径与项目实战指南
本文详细介绍了“Java从入门到实战”的学习路径与应用实例,涵盖基础、进阶、框架工具及项目实战四个阶段。内容包括环境搭建、语法基础、面向对象编程,数据结构与算法、多线程并发、JVM原理,以及Spring框架等核心技术。通过学生管理系统、文件下载器和博客系统等实例,帮助读者将理论应用于实践。最后,提供全链路电商系统的开发方案,涉及前后端技术栈与分布式架构。附代码资源链接,助力成为合格的Java开发者。
50 4
|
23天前
|
Java
银行转账p图软件,对公转账截图生成器,java版开发银行模拟器【仅供学习参考】
这是一套简单的银行账户管理系统代码,包含`BankAccount`和`BankSystem`两个核心类。`BankAccount`负责单个账户的管理
|
23天前
|
存储 Java 数据库
银行流水生成器在线制作,银行转账p图在线生成,java实现最牛的生成器【仅供学习用途】
本示例展示了一个基于Java的银行交易记录管理系统基础架构,涵盖交易记录生成、数字签名加密及账本存储功能。核心内容包括:1) TransactionRecord类
|
1月前
|
存储 安全 Java
Java 基础知识超详细整理总结及学习要点解析
本文全面总结了Java基础知识,涵盖语言特性、语法基础、面向对象编程、集合框架、异常处理等核心内容。文章详细解析了Java的面向对象特性(如类与对象、构造方法、方法重载)、集合框架(如ArrayList、HashMap)、异常分类及处理,并深入探讨JVM内存模型、字符串比较、BigDecimal使用等重要知识点。此外,还提供了实际应用示例,帮助开发者更好地理解和掌握Java编程。代码资源可从文末链接获取。
289 4
|
2月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
|
5月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
300 60
【Java并发】【线程池】带你从0-1入门线程池
|
3月前
|
Java 中间件 调度
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
145 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递