代码分层, 对于任何一个 Java web 开发来说应该都不陌生. 一个好的层次划分不仅可以能使代码结构更加清楚, 还可以使项目分工更加明确, 可读性大大提升, 更加有利于后期的维护和升级.
从另外一个角度来看, 好的代码分层架构, 应该是可以很好的匹配上单一职责原则的. 这样就可以降低层与层之间的依赖, 还能最大程度的复用各层的逻辑. 本文就来介绍下 Java Web 项目的代码到底应该如何分层.
三层架构
在软件体系架构设计中, 分层式结构是最常见, 也是最重要的一种结构. 微软推荐的分层式结构一般分为三层, 从下至上分别为: 数据访问层, 业务逻辑层(又或称为领域层), 表示层. 这也是 Java Web 中重要的三层架构中的三个层次. 区分层次的目的即为了 "高内聚低耦合" 的思想.
所谓三层体系结构, 是在客户端与数据库之间加入了一个 "中间层", 也叫组件层. 这里所说的三层体系, 不是指物理上的三层, 不是简单地放置三台机器就是三层体系结构, 也不仅仅有 B/S 应用才是三层体系结构, 三层是指逻辑上的三层, 即把这三个层放置到一台机器上.
数据访问层
主要是对非原始数据 (数据库或者文本文件等存放数据的形式) 的操作层, 而不是指原始数据, 也就是说, 是对数据库的操作, 而不是数据, 具体为业务逻辑层或表示层提供数据服务.
业务逻辑层
主要是针对具体的问题的操作, 也可以理解成对数据层的操作, 对数据业务逻辑处理, 如果说数据层是积木, 那逻辑层就是对这些积木的搭建.
界面层
主要表示 WEB 方式. 如果逻辑层相当强大和完善, 无论表现层如何定义和更改, 逻辑层都能完善地提供服务.
三层架构与 MVC 的区别
MVC(模型 Model - 视图 View - 控制器 Controller)是一种架构模式, 可以用它来创建在域对象和 UI 表示层对象之间的区分.
同样是架构级别的, 相同的地方在于他们都有一个表现层, 但是他们不同的地方在于其他的两个层.
在三层架构中没有定义 Controller 的概念. 这是最不同的地方. 而 MVC 也没有把业务的逻辑访问看成两个层, 这是采用三层架构或 MVC 搭建程序最主要的区别.
更加细致的分层
随着网站的用户量的不断提升, 系统架构也在不断的调整. 有时候, 随着业务越来越复杂, 有时候三层架构好像不够用了. 比如, 我们的应用除了要给用户提供页面访问以外, 还需要提供一些开放接口, 供外部系统调用. 这个接口既不属于界面层, 也不应该属于业务逻辑层, 因为他还可能包含一些和业务逻辑无关的处理, 如权限控制, 流量控制等.
还有, 随着微服务的盛行, 我们应用中可能要依赖很多外部接口或第三方平台. 这部分代码放下业务逻辑层和数据访问层也都不合适.
所以, 渐渐的, 在三层架构的基础上, 系统架构的分层变得更加复杂了. 也正是因为复杂, 就非常考验架构设计能力, 因为层次划分的不好, 很可能会影响后面的开发, 给代码维护带来很大的困难.
下图, 是阿里巴巴 (参考阿里巴巴 Java 开发手册) 提倡的应用分层结构:
开放接口层
可直接封装 Service 方法暴露成 RPC 接口; 通过 Web 封装成 http 接口; 进行网关安全控制, 流量控制等.
终端显示层
各个端的模板渲染并执行显示的层. 当前主要是 velocity 渲染, JS 渲染, JSP 渲染, 移动端展示等.
Web 层
主要是对访问控制进行转发, 各类基本参数校验, 或者不复用的业务简单处理等.
Service 层
相对具体的业务逻辑服务层.
Manager 层
通用业务处理层, 它有如下特征: 1) 对第三方平台封装的层, 预处理返回结果及转化异常信息; 2) 对 Service 层通用能力的下沉, 如缓存方案, 中间件通用处理; 3) 与 DAO 层交互, 对多个 DAO 的组合复用.
DAO 层
数据访问层, 与底层 MySQL,Oracle,Hbase 等进行数据交互.
外部接口或第三方平台
包括其它部门 RPC 开放接口, 基础平台, 其它公司的 HTTP 接口.
事务处理
在了解了分层之后, 我们再来看一下写 Java Web 代码的时候, 大家比较关心的一个问题, 那就是涉及到数据库操作的时候, 事务处理应该在哪一层控制呢?
关于这个问题, 仁者见仁, 智者见智. 作者认为, 事务处理应该放在 Service 层和 Manager 层.
DAO 层不应该有事务, 应该只是很纯的 CRUD 等比较通用的数据访问方法. 一个 DAO 应该只处理和自己相关的操作, 不要有任何组合. 组合的事情交给上层.
Service 层和 Manager 层一般会组合多个 DAO 的 CRUD 操作, 例如: 在注册一个用户的时候需要往日志表里 INSERT 日志, 那么就在 Service 层构造事务, 在该事务中调用 Dao 层的 User.Insert () 与 Log.Insert ().
异常处理
异常处理是 Java 中比较重要的一个话题, 在Effective Java中有很多关于异常处理的最佳实践, 这里不详细介绍了, 本文主要简单说一下在应用代码分层之后, 各个层次之间的异常应该如何处理, 是自己捕获, 还是向上一层抛出.
首先, 每一层都是可能发生异常的. 由于每一层的职责都不通, 处理方式也可能有差别.
DAO 层
在 DAO 层, 产生的异常类型可能有很多, 可能是 SQL 相关的异常, 也可能是数据库连接相关的异常.
这一层的处理方式可以简单一点, 直接 try-catch(Exception), 然后封装成 DAOException 抛给上一层. 这一层一般不需要打印日志, 交给 Service 或者 Manager 层来打印.
- try{
- CRUD
- }catch(Exception e){
- throw new DAOException(e);
- }
- Manager/Service
首先, 对于 DAO 层抛上来的异常一定要捕获的, 并且记录日志打印现场.
但是值得注意的是, 如果是需要事务控制的方法, 要注意捕获到异常之后再向上抛一个新的异常, 如 TransactionRolledbackException, 否则事务无法回滚.
这两层发生的异常可以根据情况决定是继续向上抛还是自己处理掉. 如果是自己可以处理的异常, 就捕获, 打日志, 然后通过 ErrorCode 等方式返回给上一层. 如果是自己无法处理或者不知道该如何处理的异常, 就直接抛给上一层来处理.
Web
首先, 可以明确的一点: Web 层不应该再往外抛异常, 因为这一层一旦抛异常, 就可能会导致用户跳转到不友好的错误页面甚至看到错误信息等.
如果意识到这个异常将导致页面无法正常渲染, 那么就应该直接跳转到友好错误页面, 加上用户容易理解的错误提示信息.
开放接口层
这一层和 Web 层一样, 不可以抛出异常. 一般通过 ErrorCode 和 ErrorMessage 反馈给外部调用方.
这一层, 要自己处理好所有的异常, 定义好 ErrorCode, 并记录好日志, 便于日后排查问题.
总结
本文主要介绍了 Java Web 项目中代码分层的方案, 通过分层之后可以使没一层更加专注, 解除耦合. 并简单介绍了一下分层之后的事务处理和异常处理的逻辑.
(全文完)
来源: http://www.tuicool.com/articles/7Z3Abur