当前位置: 首页 > 科技观察

JavaWeb应用程序的代码分层最佳实践

时间:2023-03-14 15:52:35 科技观察

代码分层对于任何JavaWeb开发都应该不陌生。良好的层级划分不仅可以让代码结构更加清晰,也可以让项目分工更加明确,大大提高可读性,更有利于后期的维护和升级。从另一个角度来看,一个好的代码分层架构应该能够很好地匹配单一职责原则。这样可以减少层与层之间的依赖,最大程度的复用各层的逻辑。本文将介绍JavaWeb项目的代码应该如何分层。三层架构在软件架构设计中,分层结构是最常见也是最重要的结构。微软推荐的层次结构一般分为三层,从下到上依次为:数据访问层、业务逻辑层(或领域层)、表现层。这也是JavaWeb中重要的三层架构中的三个层次。区分层次的目的是“高内聚低耦合”的思想。所谓三层架构就是在客户端和数据库之间增加了一个“中间层”,也叫组件层。这里所说的三层系统并不是指物理上的三层系统。不是简单的放三台机器,这是三层架构,也不是只有B/S应用才是三层架构。三层是指逻辑上的三层,也就是把这三层放在一台机器上。数据访问层主要是对非原始数据(数据库或文本文件等)的操作层,而不是原始数据,也就是说是对数据库的操作,不是数据,具体是业务逻辑层或表示层提供数据服务。业务逻辑层主要是针对具体问题的操作。也可以理解为数据层的操作,数据业务的逻辑处理。如果说数据层是一个积木,那么逻辑层就是这些积木的搭建。界面层主要代表WEB方式。如果逻辑层相当强大和完善,无论表现层如何定义和改变,逻辑层都可以完美地提供服务。三层架构和MVC之间的区别MVC(ModelModel-ViewView-ControllerController)是一种架构模式,可以用来区分域对象和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接口等。了解了事务处理的分层之后,我们再来看看大家在编写JavaWeb代码时比较关心的一个问题,即当涉及到数据库操作时,事务处理应该控制在哪一层?对于这个问题,仁者见仁,智者见智。笔者认为,事务处理应该放在Service层和Manager层。DAO层应该没有事务,应该只是纯CRUD等比较通用的数据访问方式。一个DAO应该只处理与自身相关的操作,不能进行任何组合。组合对上层很重要。Service层和Manager层一般结合了多个DAO的CRUD操作。比如注册用户时,需要INSERTlogs到log表中,然后在Service层构造一个事务,在transaction()中调用Dao层的User.Insert和Log。插入()。异常处理异常处理是Java中一个比较重要的话题。《Effective Java》中有很多关于异常处理的最佳实践,这里不再详细介绍。他们之间的异常如何处理,是自己捕获,还是抛给上层。首先,每一层都有例外。由于每一层的职责不同,处理方式也可能不同。DAO层在DAO层中,可能会出现很多种异常,可能是SQL相关的异常,也可能是数据库连接相关的异常。这一层的处理方式可以更简单一些,直接try-catch(Exception),然后封装成DAOException抛给上层。该层一般不需要打印日志,交给Service或Manager层打印。try{CRUD}catch(Exceptione){thrownewDAOException(e);}Manager/Service首先必须捕获DAO层抛出的异常,现场记录并打印日志。但值得注意的是,如果是需要事务控制的方法,注意捕获异常后抛出新的异常,如TransactionRolledbackException,否则无法回滚事务。这两层的异常可以根据情况决定是继续往上抛还是自己处理。如果是自己可以处理的异常,就会被捕获,记录下来,然后通过ErrorCode等方法返回给上层。如果是自己处理不了或者不知道怎么处理的异常,丢给上层处理就好了。首先,我们可以明确一点:web层不应该抛出异常,因为一旦这一层抛出异常,可能会导致用户跳转到不友好的错误页面,甚至看到错误信息。如果你意识到这个异常会导致页面无法正常渲染,你应该直接跳转到一个友好的错误页面,错误信息易于用户理解。开放接口层和Web层一样,不能抛出异常。一般通过ErrorCode和ErrorMessage反馈给外部调用者。在这一层,你必须自己处理所有的异常,定义ErrorCode,记录日志,以方便日后排查问题。总结本文主要介绍JavaWeb项目中的代码分层方案。分层之后,每一层都可以更加专注和解耦。并简单介绍一下分层后的事务处理和异常处理的逻辑。【本文为专栏作家霍利斯原创文章,微信公众号Hollis(ID:hollishuang)撰文】点此阅读更多本作者好文