🌈个人首页: 神马都会亿点点的毛毛张
今天毛毛张分享的是SpeingBoot框架学习中的一些基础概念性的东西:MVC结构、三层架构、POJO、Entity、PO、VO、DO、BO、DTO、DAO
1.架构
1.1 基本概述
1.1.1 什么是架构?
- 架构可以分为两种类型:系统架构和应用架构
- 系统架构(通常称为网络架构)主要关注硬件、网络和通信的设计与组织。
- 应用架构(通常指代码架构)则侧重于软件系统内部结构、模块划分、接口设计等方面。
1.1.2 为什么需要架构?
- 在早期,系统较为简单,通常一个应用只部署在一台服务器上,且开发主要集中在基础的CRUD操作,应用结构也相对简单,维护较为容易。然而,随着业务复杂度的增加,功能模块的扩展,系统的耦合度逐渐增高,导致整体复杂度难以管理。
- 为了应对这种复杂性,出现了多种架构设计:
- 网络架构(如分布式架构、微服务架构)旨在降低系统间的耦合度,提升系统的可扩展性和容错性。
- 应用架构(如三层架构、MVC架构)旨在优化代码的组织结构,增强可维护性。在这些架构的基础上,又发展出了如SSM框架、SSH框架等具体技术框架。使用框架的好处在于能够提供清晰的结构,便于管理和维护。
1.2 MVC架构
1.2.1 什么是MVC架构
-
MVC(Model-View-Controller)是模型-视图-控制器的缩写,是一种软件设计模式。它将软件系统划分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。通过这种方式,业务逻辑、数据和界面显示相互分离,使得系统的维护和扩展更加灵活。在这种结构下,业务逻辑集中在模型层,界面和用户交互则由视图层负责,控制器则负责协调模型和视图的交互。
-
视图层(View)
- 视图层负责提供用户交互界面,显示数据并接收用户输入。在传统的应用程序中,视图层会包含与界面显示相关的文件,如 HTML、CSS、JavaScript 等。
- 在前后端分离的项目中,视图层已转化为独立的前端项目,后端不再直接包含视图文件。此时,后端仅负责提供数据和业务逻辑,前端则负责界面展示。
-
模型层(Model)
- 模型层代表存取数据的对象,通常为 Java POJO(Plain Old Java Object,简单Java对象)。模型层不仅仅是数据的承载者,它还可能包含与数据相关的业务逻辑。模型层可以分为两类:
- 数据承载 Bean:例如实体类(如
User
类),专门用于承载数据。 - 业务处理 Bean:如
Service
或Dao
类,专门用于处理用户请求并进行业务逻辑操作。
- 数据承载 Bean:例如实体类(如
- 模型层的结构:
- 实体类包(如
pojo
、entity
、bean
):存放与数据库表对应的实体类以及一些非数据库表相关的 VO(Value Object)对象。 - 数据库访问包(如
dao
、mapper
):封装对数据库表格的 CRUD 操作。 - 服务包(如
service
):包含处理业务逻辑的类。
- 实体类包(如
注: Java Bean 是一种可重用的组件,可以在构建工具中可视化操作
- 模型层代表存取数据的对象,通常为 Java POJO(Plain Old Java Object,简单Java对象)。模型层不仅仅是数据的承载者,它还可能包含与数据相关的业务逻辑。模型层可以分为两类:
-
控制层(Controller)
- 控制层负责接收用户的请求并将其转发给相应的模型进行处理。它还会根据模型的计算结果,将响应数据返回给用户。控制层将视图层和模型层分离,使得系统的各个部分解耦,易于维护和扩展。控制层的职责包括:接收客户端请求、处理请求数据,并调用模型层进行数据处理或计算,最后将处理结果反馈给客户端。
- 控制层的结构:
- 控制器通常包含在一个专门的控制层包中(如
controller
)
- 控制器通常包含在一个专门的控制层包中(如
1.2.2 MVC架构工作流程
流程步骤:
- 用户请求:用户通过 View 页面向服务端提出请求,这个请求可以是表单提交、超链接点击、AJAX请求等。
- 控制器处理请求:服务端的 Controller 接收到请求后,解析请求内容,确定需要处理的 Model。Controller 根据请求的类型和参数,调用相应的模型进行处理。
- 模型处理:Model 根据 Controller 传递的数据执行相应的业务逻辑处理。这可以涉及数据的计算、存储操作、验证等。
- 渲染响应页面:处理完成后,Controller 将 Model 处理结果返回,并选择合适的 View 页面进行渲染。视图会根据传递的数据生成 HTML 内容,并将页面返回给客户端,作为用户的最终响应。
- MVC模式的最大优势在于 将视图和模型分离,这种分离带来了多个显著的好处,尤其是提高了代码的 可重用性。
- 可重用性:多个视图可以共享一个模型。由于 Model 负责处理数据和业务逻辑,而 View 仅负责展示数据,二者的分离使得同一份业务逻辑(模型)可以在多个不同的视图中复用,而不需要重复编写代码。这种设计不仅减少了代码的冗余,也提升了系统的扩展性和维护性。
- 灵活性:视图和模型的分离使得开发者可以独立地修改或替换视图(界面)或模型(业务逻辑),而不会影响到其他层次。这种松耦合的设计大大提升了系统的灵活性和可维护性。
1.3 三层架构
1.3.1 什么是三层架构?
- 三层架构(3-Tier Architecture)是一种将系统划分为三个独立层次的设计模式,以实现“高内聚、低耦合”的目标。这种架构将系统功能模块分为:表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL),每一层独立负责不同的职责,有效降低了系统各个层次之间的耦合度,提升了系统的可维护性和扩展性。
- 表示层(UI)
- 表示层是架构的最上层,直接与用户交互,负责显示界面并接收用户输入。它作为系统与用户之间的桥梁,将用户的请求传递给业务逻辑层,同时将处理结果返回给用户。
- 表示层的主要功能是:提供用户界面、展示数据、接收用户输入,并反馈操作结果。通常,表示层由 Web 应用程序或桌面应用程序构成。
- 业务逻辑层(BLL)
- 业务逻辑层位于架构的核心,负责处理具体的业务规则和逻辑。它充当表示层与数据访问层之间的桥梁,主要负责接收表示层的请求,进行数据处理或计算,并协调调用数据访问层执行必要的数据操作。
- 业务逻辑层的职责包括:处理业务逻辑、验证数据一致性、管理事务、确保业务规则得到正确执行。
- 数据访问层(DAL)
- 数据访问层,也称为持久层,负责与数据存储进行交互(如数据库、文件系统等)。它主要处理数据的增删改查(CRUD)操作,将数据从存储介质中读取并返回给业务逻辑层,或将数据保存回数据库。
- 数据访问层的功能是为业务逻辑层提供必要的数据支持,保证数据的持久性和一致性。
- 层与层之间的关系:
- 表示层依赖于业务逻辑层
- 业务逻辑层依赖于数据访问层
1.3.2 为什么需要三层架构?
-
分层的目的: 三层架构的核心思想是 “高内聚,低耦合”。在设计和开发软件系统时,应当使模块之间的关系更加紧密,同时避免模块之间的过度依赖,以便提升系统的可维护性、可扩展性和可重用性。
- 内聚:指的是一个模块内部各个元素的关联度。高内聚意味着模块内部的功能紧密相关,职责明确。这样的模块容易理解、修改和维护,且出错的可能性较小。
- 耦合:指的是模块之间的关联度。低耦合意味着模块之间的依赖关系尽量减少,避免牵一发而动全身的情况。低耦合的设计能降低系统的复杂性,使得模块可以独立工作,易于扩展和修改。
-
三层架构的出现是为了实现“高内聚,低耦合”:三层架构通过将系统划分为不同的层次,使得每一层关注不同的功能,并减少层与层之间的依赖。具体来说:
- 高内聚:每个模块尽量只负责一个功能。比如,数据访问层(DAL)只负责数据操作,业务逻辑层(BLL)只处理业务逻辑,表示层(UI)只负责用户界面展示。
- 低耦合:不同模块之间的依赖关系尽可能减少,模块间的交互复杂度降低。比如,业务逻辑层和数据访问层通过接口进行交互,而不直接调用具体的实现类。
-
两层架构 vs 三层架构
- 两层架构:所有功能都放在一层中,耦合度较高。如果某一部分发生变化,整个系统都需要重新开发。这样设计不利于扩展和维护,难以适应需求变化。
- 三层架构:通过分层设计,减少了层与层之间的耦合度。每层的职责清晰,发生变化时只需修改相关层,而不会影响到其他层。此设计不仅提高了系统的可维护性和可扩展性,也使得系统更易于适应需求变化。
-
面向接口编程与弱耦合:在三层架构中,采用 面向接口编程,使得不同层次之间通过接口来实现交互。具体来说,各层之间通过接口而非直接依赖具体实现类来进行通信,这减少了层与层之间的耦合度。通过这种方式,实现类是可以替换的,只要接口不变,新的实现类可以轻松替换旧的实现,不影响其他层次。这样就实现了各层之间的解耦,提高了系统的灵活性和可扩展性。
-
接口:提供服务标准,定义了不同层之间交互的规范。
-
实现类:具体实现接口的服务逻辑,提供实际的业务操作。
-
1.3.3 三层架构之间如何联系?
为了将三层有效连接,**实体层(Entity)**起着关键作用,它不是三层架构中的一部分,但它在三层架构中扮演着至关重要的角色。实体层用于在各层之间传递数据,并实现面向对象的封装
- 实体层的作用:
- 实现封装,遵循面向对象思想
- 在三层之间传递数据
- 每一层(UI → BLL → DAL)通过传递实体作为参数来进行数据交换,确保三层之间的连接和数据流动,从而实现功能
- 实体层在三层架构中贯穿各个层次,通过单向数据传递完成层与层之间的协调,如下图
- 实体类是实体层的核心,它代表了数据库表的结构,并用于存储和操作业务数据。实体类通常使用ORM框架(如JPA、Hibernate)来将对象与数据库表中的记录进行映射。实体类通常实现了JavaBean规范,具有无参构造器、私有字段和公共的getter/setter方法。具体的实体传输示意图如下图:
1.4 三层架构与MVC的区别
- MVC架构:主要目的是将应用的视图层和业务逻辑分离,从而实现内聚和低耦合。通过这种分离,开发人员可以更方便地替换视图样式,而不影响核心业务逻辑。例如,用户界面的变化不需要重新修改后台的业务代码。
- 三层架构:是从应用程序整体结构的角度进行分层,通常包括表示层(即UI层)、业务逻辑层和数据访问层。三层架构强调每一层的职责单一,并要求业务逻辑层和数据访问层遵循面向接口编程,实现模块化、可扩展和可维护的设计。
- 总结两者的核心思想:
- 都是为了实现内聚和低耦合。
- MVC重点在于界面与业务逻辑分离。
- 三层架构则从更高层次上实现系统的分层,确保每一层的职责清晰。
- 两者之间的联系和区别可以通过下面两张图结合着理解:
public class UserPOJO {
private Long id;
private String name;
private String email;
public UserPOJO(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// getters and setters
}
- 用途:
POJO
主要用于表示数据结构或数据传输对象,通常不会直接与数据库交互。它可以在业务逻辑中使用,也可以作为 DTO 传递数据。
2.1.2 Entity
- 作用:Entity 是持久化层中的实体对象,通常与 PO 类似。Entity 通常是与数据库表结构对应的对象,用于表示一条记录。
- 内容:Entity 和 PO 类似,都是数据库表中一行数据的映射对象,通常包含与数据库表字段一一对应的属性。这个术语在不同上下文中有时被用来表示 PO,也有时用于表示领域模型中的实体。
- 用法:
- 以
Entity
为结尾(阿里是以 DO 为结尾) - Xxxx 与数据库表名保持一致
- 类中字段要与数据库字段保持一致,不能缺失或者多余
- 类中的每个字段添加注释,并与数据库注释保持一致
- 不允许有组合
- 以
- 示例:
UserEntity
可能是与user
表中的一行记录对应的类。
2.1.3 PO(Persistent Object)
- 作用:PO 是数据库层的对象,用于与数据库的记录进行映射,通常是数据库表中的一行数据。
- 内容:PO 的结构直接对应数据库表的结构,每个 PO 对象通常代表数据库表中的一条记录。PO 类通常没有业务逻辑,主要负责存储数据,包含基本的 getter 和 setter 方法。
- 示例:
-
数据库表
user
存储用户信息,PO 对象User
就代表该表中的一条记录。 -
示例:
public class UserPO { private int id; private String name; private String email; // getters and setters }
-
2.1.4 DAO(Data Access Object)
-
作用:DAO 是数据访问对象,提供与数据库交互的接口,封装了对 PO 对象的增、删、改、查操作。
-
内容:DAO 负责从数据库中获取数据,并将数据封装成 PO 对象。它不包含业务逻辑,而是专注于数据存取和数据库操作。
-
示例:
UserDao
类提供了对User
PO 对象的操作方法,例如获取用户、添加用户等。
public class UserDao { public UserPO getUserById(int id) { // 从数据库查询并返回 UserPO 对象 } public void saveUser(UserPO user) { // 保存 UserPO 到数据库 } }
2.1.5 DO(Domain Object)
- 作用:DO 是领域对象,在领域驱动设计(DDD)中,DO 代表业务逻辑中的核心实体。它通常包含业务逻辑,并与 PO 或 Entity 映射,但其重点在于业务逻辑而非数据库操作。
- 内容:DO 可以包含与 PO 一样的字段,但它比 PO 更加关注业务规则和业务计算。DO 代表了一个业务实体,通常与业务需求密切相关。
- 示例:
- 在电商应用中,
Order
(订单)对象通常是 DO,包含订单的相关信息(如商品、数量、价格等)和业务方法(如计算总金额)。
public class OrderDO { private List<Product> products; private double totalAmount; public double calculateTotal() { // 计算订单总金额 return totalAmount; } }
- 在电商应用中,
2.1.6 BO(Business Object)
- 作用:BO 是业务对象,它通常由多个 PO 对象组合而成,并包含业务逻辑。BO 聚焦于业务操作,除了数据字段外,还包含用于处理业务的计算方法。
- 内容:BO 将多个 PO 或 DO 组合起来,处理复杂的业务逻辑。BO 可能是跨多个 PO 的聚合对象,用于封装某个完整的业务场景,可能包含多个属性和方法。
- 用法:
- 不可以继承自
Entity
BO
对象不得用于controller
层
- 不可以继承自
- 示例:
- 在电商应用中,
ShoppingCart
(购物车)可能是一个 BO,包含多个Product
(商品) PO 和相关的业务方法(如计算总价、应用折扣等)。
public class ShoppingCartBO { private List<ProductPO> products; public double calculateTotalPrice() { // 计算购物车商品的总价 } }
- 在电商应用中,
2.1.7 DTO(Data Transfer Object)
- 概念来源:DTO 起源于 J2EE 的设计模式,最初用于 EJB 的分布式应用。其目的是通过提供粗粒度的数据实体,减少分布式调用的次数,从而提升性能并降低网络负载。
- 定义与功能:DTO(数据传输对象)是一种用于在软件应用系统之间传输数据的设计模式。通常情况下,数据传输对象从数据库中检索数据后,用于在不同系统或模块之间传递。与数据交互对象(Data Interaction Object)或数据访问对象(DAO)不同,DTO 不包含复杂的行为,主要功能是存储和传递数据。简而言之,开发中并不需要将整个
PO
(持久化对象)的所有字段传输到客户端,而是通过重新封装DTO
传递所需的数据。如果这种传输对象用于界面展示,则被称为VO
(View Object)。 - 主要作用:
- 数据传递:DTO 用于不同系统、不同层级(如前后端、服务与服务)之间的数据传输。
- 封装字段:DTO 能根据业务需求封装特定字段,从而简化数据结构、优化传输效率。
- 内容特点:
- DTO 通常包含跨层传输所需的数据字段,可以是完整数据,也可以是根据需求筛选后的部分字段。
- DTO 不包含复杂业务逻辑,仅负责存储和传递数据,确保通信简洁明了。
- 用法:
- 不可继承自
Entity
或者PO
DTO
可以继承、组合其他DTO,VO,BO
等对象DTO
只能用于前端、RPC 的请求参数
- 不可继承自
- 示例:
- 假设需要将用户信息从后端传递到前端,DTO 可能会包含用户的姓名、性别、年龄等信息,可能有多个字段。
public class UserDTO { private String name; private String gender; private int age; // getters and setters }
2.1.8 VO(View Object)
- 作用:VO 是视图对象,用于展示层,通常是展示给用户的数据。VO 主要关注的是数据的展示形式,可能会对 DTO 进行数据格式化或解释。
- 内容:VO 可以从 DTO 或 BO 中派生,通常是展示层需要的简化版本,它可能只包含必要的数据字段,并且进行业务解释和格式化。
- 用法:
- 不可继承自
Entity
或者PO
VO
可以继承、组合其他DTO,VO,BO
等对象VO
只能用于返回前端、rpc 的业务数据封装对象
- 不可继承自
- 示例:
- 假设前端只需要展示用户的性别,且需要将性别展示为“公子”而不是“男”,VO 就是格式化后的数据。
public class UserVO { private String gender; // getters and setters }
2.1.9 req(Request) 和 rsp(Response)
- 作用:
req
和rsp
是与客户端和服务端通信相关的对象,通常用于 API 调用。req
是请求对象,rsp
是响应对象。 - req(Request):请求对象,包含客户端发起的请求数据,如查询条件、请求参数等。它通常是 HTTP 请求中的数据体或 API 请求的参数。
- rsp(Response):响应对象,包含服务器对请求的响应数据,如操作结果、状态码、错误信息等。它通常是 HTTP 响应中的数据体或 API 响应的内容。
- 示例:
-
req:API 请求体包含一个查询参数。
{ "userId": 123, "query": "product" }
-
rsp:服务器返回的数据和状态。
{ "status": "success", "data": [{"productId": 1, "name": "Product A"}] }
-
2.2 区别与联系
- 展示与传输:
- VO 和 DTO 都是用于传输数据的对象,但 VO 更侧重于展示层,DTO 更侧重于不同层或系统之间的数据传输。DTO 可以包含更多的字段,而 VO 主要关注展示所需的信息。简单来说,我们不需要把整个
PO
对象的全部字段传输到客户端,而是可以用DTO
重新封装,传递到客户端。此时,如果这个对象用来对应界面的展现,就叫VO
。
- VO 和 DTO 都是用于传输数据的对象,但 VO 更侧重于展示层,DTO 更侧重于不同层或系统之间的数据传输。DTO 可以包含更多的字段,而 VO 主要关注展示所需的信息。简单来说,我们不需要把整个
- DTO与DO:
- 在设计层面上,展示层传递给服务层的 DTO 和服务层返回给展示层的 DTO 在概念上是不同的,但在实际实现中,通常会设计一个通用的 DTO。这样可以避免为每种场景定义多个类似的对象(例如多个
UserInfo
),从而简化代码。对于服务层接收的数据,展示层不应设置的属性(例如订单总价,应由单价、数量和折扣计算得出)会被服务层忽略;而服务层返回数据时,也会过滤掉不应暴露给展示层的敏感信息(例如用户密码)。 - 至于 DO(领域对象),在服务层中直接返回给展示层并不合理,原因如下:
- 多对多关系:一个 DTO 可能对应多个 DO,或反之,甚至存在复杂的多对多映射关系。直接返回 DO 会导致设计复杂化。
- 数据安全性:DO 包含某些敏感数据,这些数据不应被展示层知晓。
- 业务方法暴露问题:DO 通常包含业务方法,直接暴露给展示层可能导致展示层绕过服务层调用 DO 的方法,破坏业务逻辑的封装。此外,这种设计对基于 AOP 的访问控制机制尤其不友好,还可能引发事务管理问题
- 延迟加载问题:某些 ORM 框架(如 Hibernate)使用延迟加载技术,而展示层通常不在事务范围内。如果展示层在事务关闭后尝试访问未加载的关联对象,会导致运行时异常(如 Hibernate 的
LazyInitializationException
)。 - 跨层依赖耦合:从设计角度来看,展示层应依赖服务层,服务层再依赖领域层。如果直接将 DO 暴露给展示层,就会导致展示层依赖领域层,增加不必要的耦合。
- 对于 DTO,还需要注意其设计原则:DTO 应该是“扁平化”的。比如,当
User
关联了多个实体(如Address
、Account
和Region
),不应直接返回这些关联对象的完整结构。如果getUser()
方法需要返回用户的基本信息以及AccountId
、AccountName
、RegionId
和RegionName
,则应将这些字段直接定义在 DTO(如UserInfo
)中,将复杂的对象树“压扁”为简单的二维结构。这种设计减少了数据传输量,特别是在分布式系统中,可以有效避免因传输、序列化和反序列化开销过大而导致的性能问题。
- 在设计层面上,展示层传递给服务层的 DTO 和服务层返回给展示层的 DTO 在概念上是不同的,但在实际实现中,通常会设计一个通用的 DTO。这样可以避免为每种场景定义多个类似的对象(例如多个
- DO与PO:在大多数情况下,DO(Data Object)和 PO(Persistent Object)是一一对应的,但在不同应用场景下,它们有显著的区别:
- DO 在某些场景下不需要持久化:在某些情况下,DO 不需要持久化到数据库,因此不会有对应的 PO 对象。DO 仅用于业务层数据的处理,不涉及持久化操作。
- PO 在某些场景下没有对应的 DO:同样的道理,在一些场景中,PO 可能没有直接对应的 DO。PO 主要用于数据库持久化操作,因此其设计和使用场景不同于 DO。
- 一个 PO 可能对应多个 DO,反之亦然:为了满足某些持久化策略或性能优化需求,可能会出现一个 PO 对应多个 DO 的情况,或者一个 DO 被多个 PO 共享,尤其是在复杂的业务场景下。
- PO 中某些属性对 DO 没有意义:PO 中有些属性可能是为了实现某些持久化策略或优化(如乐观锁机制)而设计的,这些属性对于 DO 没有任何业务意义。例如,PO 中可能会有一个
version
属性用于乐观锁,而这个属性对 DO 来说并不重要,因此不应包含在 DO 中。反之,DO 中也可能包含一些不需要持久化的属性,这些属性对 PO 无意义。
- DTO与BO:
- DTO(Data Transfer Object)用于在不同系统或层之间传递数据,通常只包含数据本身,不包含业务逻辑。它的作用是简化数据传输,避免传递过多无关信息。
- BO(Business Object)则是业务逻辑的载体,主要用于内部业务层,包含与业务相关的数据和行为。
- 与 DTO 不同,BO 关注的是如何实现和处理具体的业务规则。通过将外部服务返回的 DTO 转换为 BO,可以隔离外部变动对内部系统的影响,同时按需处理数据,使业务逻辑更清晰和稳定。
- 业务与持久化:
- DO 和 BO 都是业务层的对象,但 DO 更侧重于领域模型(DDD),而 BO 更侧重于具体的业务处理。PO 主要是持久化对象,直接与数据库表结构对应。
- 持久化与数据访问:
- PO 与 Entity 其实是很相似的,主要都用于持久化层表示数据库记录。而 DAO 是与持久化交互的对象,负责从数据库中读取、写入数据。
- 请求与响应:
- req 和 rsp 分别表示客户端请求和服务器响应,通常与 API 交互或服务调用相关。
2.3 阿里巴巴技术规范
- 阿里巴巴 Java 开发手册中的分层领域模型规约:
- DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象
- DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象
- BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象
- Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类 来传输
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象
- 领域模型命名规约:
- 数据对象:xxxDO,xxx 即为数据表名
- 数据传输对象:xxxDTO,xxx 为业务领域相关的名称
- 展示对象:xxxVO,xxx 一般为网页名称
- POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO
参考文献
- https://xie.infoq.cn/article/bf881a59e1c4693bdc93d378c
- https://lux-sun.blog.csdn.net/article/details/113695259
- https://blog.csdn.net/baichoufei90/article/details/129424234
- 三层架构介绍-CSDN博客
- MVC架构与三层架构的关系 - 知乎 (zhihu.com)
🌈欢迎和毛毛张一起探讨和交流!
联系方式点击下方个人名片