Java Web应用的代码分层最佳实践

代码分层,对于任何一个Java Web开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。

从另外一个角度来看,好的代码分层架构,应该是可以很好的匹配上单一职责原则的。这样就可以降低层与层之间的依赖,还能最大程度的复用各层的逻辑。本文就来介绍下Java Web项目的代码到底应该如何分层。

Java Web应用的代码分层最佳实践

三层架构

在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。这也是Java Web中重要的三层架构中的三个层次。区分层次的目的即为了“高内聚低耦合”的思想。

所谓三层体系结构,是在客户端与数据库之间加入了一个“中间层”,也叫组件层。这里所说的三层体系,不是指物理上的三层,不是简单地放置三台机器就是三层体系结构,也不仅仅有B/S应用才是三层体系结构,三层是指逻辑上的三层,即把这三个层放置到一台机器上。

数据访问层

主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。

业务逻辑层

主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。

界面层

主要表示WEB方式。如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。

三层架构与MVC的区别

MVC(模型Model-视图View-控制器Controller)是一种架构模式,可以用它来创建在域对象和UI表示层对象之间的区分。

Java Web应用的代码分层最佳实践

同样是架构级别的,相同的地方在于他们都有一个表现层,但是他们不同的地方在于其他的两个层。

在三层架构中没有定义Controller的概念。这是最不同的地方。而MVC也没有把业务的逻辑访问看成两个层,这是采用三层架构或MVC搭建程序最主要的区别。

Java Web应用的代码分层最佳实践

分层的最佳实践

随着网站的用户量的不断提升,系统架构也在不断的调整。有时候,随着业务越来越复杂,有时候三层架构好像不够用了。比如,我们的应用除了要给用户提供页面访问以外,还需要提供一些开放接口,供外部系统调用。这个接口既不属于界面层,也不应该属于业务逻辑层,因为他还可能包含一些和业务逻辑无关的处理,如权限控制、流量控制等。

还有,随着微服务的盛行,我们应用中可能要依赖很多外部接口或第三方平台。这部分代码放下业务逻辑层和数据访问层也都不合适。

所以,渐渐的,在三层架构的基础上,系统架构的分层变得更加复杂了。也正是因为复杂,就非常考验架构设计能力,因为层次划分的不好,很可能会影响后面的开发,给代码维护带来很大的困难。

下图,是阿里巴巴(参考《阿里巴巴Java开发手册》)提倡的应用分层结构:

Java Web应用的代码分层最佳实践

开放接口层

可直接封装 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,并记录好日志,便于日后排查问题。

相关推荐