什么是 Spring MVC 三层架构?
面试考察点
- 架构设计思维:面试官不仅仅想知道你知不知道三层架构的名字,更想知道你理解不理解 "为什么要分层",以及分层带来的好处(解耦、复用、可测试)。
- 实际编码规范:考察你在项目中是否真的严格遵循了分层规范,还是嘴上说分层,代码里 Controller 直接调 Mapper。
- 与 MVC 模式的区分:看你是否能把 "三层架构" 和 "MVC 模式" 这两个容易混淆的概念区分开。
核心答案
三层架构是一种软件架构设计模式,将整个应用从上到下分为 三个层次:
| 层次 | 名称 | 核心职责 | 常用组件 |
|---|---|---|---|
| 表现层(Web 层) | Controller | 接收请求、参数校验、调用业务层、返回响应 | @RestController、@Controller |
| 业务逻辑层(Service 层) | Service | 处理核心业务逻辑、事务管理、组合多个 DAO 操作 | @Service |
| 数据访问层(DAO 层) | Mapper / DAO | 与数据库交互,执行 CRUD | @Mapper、@Repository、MyBatis |
三个层之间单向依赖:Controller → Service → DAO,上层调用下层,下层不感知上层。
深度解析
一、整体架构图
上图展示了 Spring 三层架构的完整调用链路,整体分为三个阶段:
- 表现层(Controller):是整个应用的 "门面"。它不处理任何业务逻辑,只负责接收前端请求、做基本的参数校验、调用 Service 层拿到结果、再组装成统一的响应格式返回给前端。相当于餐厅里的 "服务员",只负责点单和上菜,不负责做菜。
- 业务逻辑层(Service):是整个应用的 "大脑"。所有核心业务规则都在这里实现,比如 "下单要检查库存、扣减余额、生成订单号" 这种跨表的操作,必须在 Service 里编排。事务管理也放在这一层。相当于餐厅里的 "厨师",真正做菜的人。
- 数据访问层(DAO / Mapper):是整个应用的 "仓库管理员"。只负责和数据库打交道,执行纯粹的 CRUD,不关心业务语义。一个
UserMapper只管用户的增删改查,不会去管 "注册要不要发短信" 这种业务逻辑。
二、各层代码示例
用一个 "用户注册" 的场景,看看三个层各写什么:
Controller 层——只做参数接收和响应封装
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public Result register(@RequestBody @Valid RegisterRequest request) {
// Controller 只做:接收参数、调用 Service、返回结果
// 绝不在这里写业务逻辑!
userService.register(request);
return Result.success("注册成功");
}
}
Service 层——核心业务逻辑和事务管理
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private EmailService emailService;
@Override
@Transactional(rollbackFor = Exception.class)
public void register(RegisterRequest request) {
// 1. 检查用户名是否已存在
User existUser = userMapper.selectByUsername(request.getUsername());
if (existUser != null) {
throw new BizException("用户名已存在");
}
// 2. 密码加密
String encodedPwd = PasswordUtil.encode(request.getPassword());
// 3. 保存用户
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(encodedPwd);
userMapper.insert(user);
// 4. 发送注册邮件(组合多个操作)
emailService.sendWelcomeEmail(user.getUsername());
}
}
DAO 层——纯粹的数据库操作
@Mapper
public interface UserMapper {
@Select("SELECT * FROM t_user WHERE username = #{username}")
User selectByUsername(@Param("username") String username);
@Insert("INSERT INTO t_user(username, password) VALUES(#{username}, #{password})")
void insert(User user);
}
三、三层架构 vs MVC 模式
这两个概念经常被搞混,面试的时候一定要能区分开:
| 对比维度 | 三层架构 | MVC 模式 |
|---|---|---|
| 关注点 | 整个应用的纵向分层 | 主要是表现层内部的组织方式 |
| 划分 | Controller / Service / DAO | Model / View / Controller |
| 范围 | 覆盖后端全部 | 只在 Web 层生效 |
说白了,MVC 是三层架构中表现层内部的进一步细分。三层架构的 "表现层" 用了 MVC 的思想,把 Controller 和 View 分开。而 Service 和 DAO 是三层架构独有的,MVC 模式压根不涉及这两层。
四、为什么要分层?不分不行吗?
不分当然能跑,但项目一大就难受了。分层带来的核心好处:
- 解耦:Service 层不关心数据是来自 MySQL 还是 Redis,Controller 层不关心业务逻辑怎么实现。换数据库只需要改 DAO 层,不影响 Service 和 Controller。
- 复用:同一个 Service 方法可以被多个 Controller 调用(Web 接口、定时任务、RPC 调用都能复用)。
- 可测试:Service 层可以脱离 Web 容器独立做单元测试,Mock 掉 DAO 就行,不用启动整个应用。
- 分工协作:前后端分离后,前端同学看 Controller 接口文档就行,后端同学各层各管各的,互不干扰。
五、常见违规写法
说几个我在 Code Review 里经常看到的 "反模式":
// ❌ 反模式 1:Controller 里写业务逻辑
@PostMapping("/order")
public Result createOrder(@RequestBody OrderRequest req) {
// 这些逻辑应该在 Service 层!
Order order = new Order();
order.setUserId(req.getUserId());
BigDecimal total = BigDecimal.ZERO;
for (Item item : req.getItems()) {
total = total.add(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
order.setTotal(total);
orderMapper.insert(order); // Controller 直接调 DAO,越过了 Service 层
return Result.success(order);
}
// ❌ 反模式 2:Service 层直接返回 Map / JSONObject
// Service 应该返回业务对象(BO / VO),不应该返回跟 HTTP 协议耦合的东西
public Map<String, Object> getUserInfo(Long userId) {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("data", userMapper.selectById(userId));
return result;
}
// ✅ 正确做法:Service 返回业务对象,Controller 负责包装响应
public UserVO getUserInfo(Long userId) {
User user = userMapper.selectById(userId);
return UserConverter.toVO(user);
}
面试高频追问
-
追问一:Controller 可以直接调用 DAO 吗?
- 技术上可以,但绝对不应该。跳过 Service 层意味着没有事务管理,业务逻辑散落在 Controller 里,没法复用也没法单独测试。简单查询勉强能忍,但属于严重的架构违规。
-
追问二:Service 层的接口和实现类一定要分离吗?
- 规范上建议
UserService(接口)+UserServiceImpl(实现类)分离。好处是多实现时可以灵活切换(比如UserLocalServiceImpl和UserRemoteServiceImpl),也方便 AOP 代理。但如果项目规模小,只有一个实现,用@Service直接标在类上也不算错,不必教条。
- 规范上建议
-
追问三:三层架构和微服务架构冲突吗?
- 不冲突。微服务架构中的每个服务内部,依然是三层架构。三层架构是单体内部的分层方式,微服务是服务间的拆分方式,两个维度的东西。
常见面试变体
- "说一说你对 MVC 模式的理解?"
- "为什么要分层?分层的好处是什么?"
- "Controller、Service、DAO 三层各自负责什么?"
- "三层架构和 MVC 有什么区别?"
总结
三层架构的核心思想就是 各司其职、单向依赖:Controller 管接请求,Service 管业务逻辑,DAO 管数据库操作。面试的时候把 "为什么分层"(解耦、复用、可测试)和 "各层的边界"(Controller 不写业务,Service 不管 HTTP)讲清楚就够了。
