Java Web 架构全组件详解

🎯 Java Web 架构全组件详解(终极汇总版)

本文涵盖:Entity、DTO、VO、Controller、Service 的核心定义、职责边界、使用场景、反模式,以及大厂规范。

一、Entity(实体)

📌 定义

数据库表的 Java 映射,与持久层(Repository/DAO)直接交互。

🎯 核心职责

  • 1:1 映射数据库表结构
  • 承载 ORM(JPA/Hibernate/MyBatis)注解
  • 代表业务领域的核心对象

✅ 什么时候用?

  • 必须用:所有与数据库交互的层(Repository、DAO)
  • 必须用:使用 JPA/Hibernate 时,每个表对应一个 Entity
  • 必须用:MyBatis 的结果集映射

❌ 什么时候用不上?

  • 严禁:直接返回给前端
  • 严禁:作为 Controller 的入参
  • 严禁:跨服务传递(除非是 RPC 的共享领域对象)

📦 典型写法

java 复制代码
@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username", unique = true)
    private String username;
    
    @Column(name = "password")
    private String password;  // 加密存储
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @OneToMany(mappedBy = "user")
    private List<Order> orders;  // 对象导航
}

💥 反模式(千万别做)

@Entity public class UserDTO ------ 职责混淆

❌ Controller 直接返回 List<User> ------ 暴露敏感字段

❌ Entity 里写 @NotBlank 校验注解 ------ 校验应在 DTO


二、DTO(数据传输对象)

📌 定义

接口的契约,定义 API 的请求参数和响应数据。

🎯 核心职责

  • 定义前后端/服务间的通信协议
  • 数据校验(请求)
  • 按需裁剪字段(响应)
  • 接口版本管理

✅ 什么时候用?

  • 必须用:所有 Controller 的入参(POST/PUT/GET 参数)
  • 必须用:所有 Controller 的返回体
  • 必须用:微服务间的 RPC 调用
  • 建议用:Service 层与 Controller 层的数据传输

❌ 什么时候用不上?

  • 内部方法调用(private 方法)
  • 同层传输(Service → Service 尽量用 Entity 或 领域对象)
  • 纯查询项目(但依然建议用 DTO 封装查询参数)

📦 典型写法(请求 vs 响应)

请求 DTO

java 复制代码
@Data
public class UserCreateDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20)
    private String username;
    
    @NotBlank
    @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z]).{6,20}$")
    private String password;  // 明文传输
    
    @Email
    private String email;
    
    // 没有 id!没有 createTime!
}

响应 DTO

java 复制代码
@Data
public class UserDetailDTO {
    private Long id;
    private String username;
    private String email;
    private String avatar;        // 完整 URL
    private String createTime;    // "2024-01-01 12:00:00"
    private List<String> roles;   // 聚合数据
    
    // 没有 password!没有 salt!
}

💥 反模式

❌ 一个 DTO 通吃所有接口(UserDTO 被 5 个接口共用)

❌ DTO 里有 password 字段但不小心返回了

❌ DTO 字段名用下划线(user_name)而不是驼峰(userName)

❌ 请求 DTO 用基本类型(int pageNum → 默认 0 不是 1)


三、VO(视图对象)

📌 定义

视图层的模型 ,专门为页面渲染组装的数据结构。

🎯 核心职责

  • 数据格式化(时间、金额、枚举)
  • 多表聚合(页面需要的数据一次性组装好)
  • UI 状态标识(是否选中、是否可编辑)

✅ 什么时候用?

  • 必须用:服务端渲染(Thymeleaf、JSP、Freemarker)
  • 必须用:页面需要的数据和接口 DTO 差异很大
  • 可选用:复杂报表、PDF 导出、Excel 导出
  • 不适用:纯前后端分离项目(前端自己渲染)

❌ 什么时候用不上?

  • 90% 的项目用不上:Vue/React + Spring Boot = 只用 DTO
  • 简单的 CRUD 查询(DTO 直接给前端)

📦 典型写法

java 复制代码
@Data
public class UserProfileVO {
    private String username;
    private String nickname;
    private String avatarUrl;      // 已拼接 CDN 域名
    private String memberLevel;    // "黄金会员"(由积分计算)
    private String registerDate;   // "2024-01-01"
    private String statusDesc;     // "正常"
    private Boolean canEdit;       // 是否有编辑权限(权限计算)
    private Integer orderCount;    // 订单数量(统计)
    
    // 全都是加工后的数据,没有原始类型
}

💥 反模式

❌ 前后端分离项目硬造 VO(多此一举)

❌ VO 和 DTO 字段完全一样(那就别分)

❌ VO 里还有 LocalDateTime(没格式化)


四、Controller(控制器)

📌 定义

HTTP 接口的门面,负责请求接收、参数校验、响应返回。

🎯 核心职责

  • URL 路由映射
  • 请求参数 -> DTO 转换
  • 基础校验(@Valid)
  • 调用 Service 层
  • DTO -> 响应/ VO -> 视图
  • 异常处理

✅ 什么时候用?

  • 必须用:所有对外暴露的 HTTP 接口
  • 必须用:接收前端传参
  • 必须用:返回 JSON/XML/HTML

❌ 什么时候用不上?

  • 内部定时任务(Schedule)
  • 消息消费者(MQ Listener)
  • RPC 服务提供者(用 @Service 暴露)

📦 典型写法

java 复制代码
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // 请求 -> DTO -> Service -> DTO -> 响应
    @PostMapping
    public Result<UserDetailDTO> create(@Valid @RequestBody UserCreateDTO createDTO) {
        UserDetailDTO detail = userService.create(createDTO);
        return Result.success(detail);
    }
    
    // 查询 -> 分页 DTO -> 响应
    @GetMapping
    public Result<PageResult<UserListDTO>> list(@Valid UserQueryDTO queryDTO) {
        PageResult<UserListDTO> page = userService.list(queryDTO);
        return Result.success(page);
    }
    
    // 服务端渲染场景
    @GetMapping("/profile")
    public String profile(Model model) {
        UserVO vo = userService.getProfileForView();
        model.addAttribute("user", vo);
        return "user/profile";  // 返回视图
    }
}

💥 反模式

❌ Controller 里写业务逻辑(应该在 Service)

❌ Controller 直接操作 Entity(Repository 都不经过)

❌ 一个方法几百行(超过 50 行就该拆)

❌ 返回 Entity 给前端


五、Service(服务层)

📌 定义

业务逻辑的核心,处理领域规则、事务、协调。

🎯 核心职责

  • 业务逻辑实现
  • 事务管理(@Transactional)
  • Entity 与 DTO 的转换
  • 多资源协调(DB + Redis + MQ)
  • 权限校验

✅ 什么时候用?

  • 必须用:所有业务逻辑处理
  • 必须用:事务操作
  • 必须用:复杂的数据组装
  • 必须用:调用外部服务/第三方 API

❌ 什么时候用不上?

  • 纯查询且无业务逻辑(但建议保留)
  • 简单的 CRUD(但依然建议 Service 做一层包装)

📦 典型写法

java 复制代码
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private DeptRepository deptRepository;
    
    @Autowired
    private UserConverter converter;  // MapStruct
    
    // 接收 DTO,返回 DTO
    public UserDetailDTO create(UserCreateDTO createDTO) {
        // 1. DTO -> Entity
        User user = converter.toEntity(createDTO);
        
        // 2. 业务逻辑
        user.setPassword(passwordEncoder.encode(createDTO.getPassword()));
        user.setCreateTime(LocalDateTime.now());
        user.setStatus(1);
        
        // 3. 保存
        userRepository.save(user);
        
        // 4. Entity -> DTO 返回
        return converter.toDetailDTO(user);
    }
    
    // 复杂查询:聚合多表
    public UserDetailDTO getDetail(Long id) {
        User user = userRepository.findById(id).orElseThrow();
        Dept dept = deptRepository.findById(user.getDeptId()).orElse(null);
        List<Role> roles = roleRepository.findByUserId(id);
        
        // Entity 组合 -> DTO
        return converter.toDetailDTO(user, dept, roles);
    }
}

💥 反模式

❌ Service 互相调用形成循环依赖

❌ Service 层过厚(1000 行一个类)

❌ Service 返回 Entity 给 Controller

❌ 事务粒度太大(一个方法操作 10 张表)


📊 六、完整数据流转全景图

场景 A:前后端分离(Vue/React + API)

复制代码
【前端】 
   ↓ JSON (请求DTO)
【Controller】参数校验
   ↓ 请求DTO
【Service】业务逻辑、事务
   ↓ Entity ←→ Repository ←→ 【DB】
   ↓ 响应DTO
【Controller】  
   ↓ JSON (响应DTO)
【前端】

典型链路:

复制代码
前端 JSON → Controller(@RequestBody UserCreateDTO) 
         → Service(create) 
         → Repository(save) 
         → DB
         ← Repository(find) 
         ← Service(convert to UserDetailDTO) 
         ← Controller(@ResponseBody) 
         ← 前端

场景 B:服务端渲染(Thymeleaf/JSP)

复制代码
【浏览器】
   ↓ HTTP请求
【Controller】接收参数
   ↓ 请求DTO/Param
【Service】业务逻辑
   ↓ Entity ←→ Repository ←→ 【DB】
   ↓ DTO(原始数据)
【Controller】DTO → VO(组装)
   ↓ Model.addAttribute("user", VO)
【ViewResolver】
   ↓ HTML
【浏览器】

典型链路:

复制代码
浏览器 GET /user/1
→ Controller(@PathVariable) 
→ Service(getDetail) 
→ Repository 
→ DB 
← Service(return UserDTO) 
← Controller(convert to UserVO) 
← Model 
← Thymeleaf 
← HTML

🎯 七、终极总结(面试/复盘用)

组件 一句话定义 数据方向 依赖层级 必备场景
Entity 数据库的影子 双向(Repository) Repository 所有 ORM 项目
DTO 接口的契约 双向(Controller) Controller/Service 所有 API 项目
VO 页面的模型 单向(后端→前端) Controller 服务端渲染
Controller HTTP 门面 接收/响应 Web 层 所有 Web 项目
Service 业务核心 内部流转 核心层 所有业务系统

✅ 什么时候必须用 Entity?

只要用数据库,就必须用 Entity

✅ 什么时候必须用 DTO?

只要有 API,就必须用 DTO

✅ 什么时候必须用 VO?

只要后端渲染 HTML,就必须用 VO

✅ 什么时候必须用 Controller?

只要暴露 HTTP 接口,就必须用 Controller

✅ 什么时候必须用 Service?

只要有业务逻辑,就必须用 Service


❌ 什么时候可以不用?

组件 可以省略的场景 是否推荐省略
VO 前后端分离项目 ✅ 强烈推荐省略
DTO 无 API 的内部模块 ⚠️ 可以,但罕见
Entity 无数据库的项目 ✅ 可以
Service 极简单的 CRUD ❌ 不推荐
Controller 非 Web 项目(Job/MQ) ✅ 可以

📌 八、大厂规范速查表(直接背)

复制代码
✅ 正确架构:
└── controller/
    ├── UserController.java        # 只做参数接收和返回
    └── vo/                       # 仅服务端渲染需要
└── service/
    ├── UserService.java          # 业务逻辑
    └── impl/
└── repository/
    ├── UserRepository.java       # 数据访问
└── entity/
    ├── User.java                # 数据库映射
└── dto/
    ├── request/                 # 请求DTO
    │   ├── UserCreateRequest.java
    │   └── UserQueryRequest.java
    └── response/                # 响应DTO
        ├── UserDetailResponse.java
        └── UserListResponse.java

核心原则:

  1. Entity 永不出口(不返回给前端,不作为接口入参)
  2. DTO 永不存库(不交给 Repository,不写 @Entity)
  3. VO 永不跨层(只在 Controller 组装,Service 不返回 VO)
  4. Controller 零逻辑(只做校验和转换)
  5. Service 无 HTTP(不知道是 API 还是 RPC 还是 Job)
相关推荐
好家伙VCC2 小时前
**标题:发散创新|用Python构建GAN图像生成器:从理论到实战全流程解析**---在深度学习飞速发展的今天,**生成对抗
java·python·深度学习·生成对抗网络
我命由我123452 小时前
Android Studio - 在 Android Studio 中直观查看 Git 代码的更改
android·java·开发语言·git·java-ee·android studio·android jetpack
DevDengChao2 小时前
[Aliyun] [FC] 如何使用 website-fc-serve 插件部署静态网站
前端·后端
前端拿破轮2 小时前
利用Github Page + Hexo 搭建专属的个人网站(一)
前端·人工智能·后端
苏荷水2 小时前
万字总结LeetCode100(持续更新...)
java·算法·leetcode·职场和发展
q1cheng2 小时前
基于Spring Boot + Vue项目online_learn的用户登录认证全流程分析
前端
大时光2 小时前
gsap 配置解读 --2
前端
万岳科技程序员小金2 小时前
AI数字人小程序源码开发全流程实战:前端交互+后端算法部署指南
前端·人工智能·软件开发·ai数字人小程序·ai数字人系统源码·ai数字人软件开发·ai数字人平台搭建