什么是 Spring MVC 三层架构?


面试考察点

  1. 架构设计思维:面试官不仅仅想知道你知不知道三层架构的名字,更想知道你理解不理解 "为什么要分层",以及分层带来的好处(解耦、复用、可测试)。
  2. 实际编码规范:考察你在项目中是否真的严格遵循了分层规范,还是嘴上说分层,代码里 Controller 直接调 Mapper。
  3. 与 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 / DAOModel / 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);
}

面试高频追问

  1. 追问一:Controller 可以直接调用 DAO 吗?

    • 技术上可以,但绝对不应该。跳过 Service 层意味着没有事务管理,业务逻辑散落在 Controller 里,没法复用也没法单独测试。简单查询勉强能忍,但属于严重的架构违规。
  2. 追问二:Service 层的接口和实现类一定要分离吗?

    • 规范上建议 UserService(接口)+ UserServiceImpl(实现类)分离。好处是多实现时可以灵活切换(比如 UserLocalServiceImplUserRemoteServiceImpl),也方便 AOP 代理。但如果项目规模小,只有一个实现,用 @Service 直接标在类上也不算错,不必教条。
  3. 追问三:三层架构和微服务架构冲突吗?

    • 不冲突。微服务架构中的每个服务内部,依然是三层架构。三层架构是单体内部的分层方式,微服务是服务间的拆分方式,两个维度的东西。

常见面试变体

  • "说一说你对 MVC 模式的理解?"
  • "为什么要分层?分层的好处是什么?"
  • "Controller、Service、DAO 三层各自负责什么?"
  • "三层架构和 MVC 有什么区别?"

总结

三层架构的核心思想就是 各司其职、单向依赖:Controller 管接请求,Service 管业务逻辑,DAO 管数据库操作。面试的时候把 "为什么分层"(解耦、复用、可测试)和 "各层的边界"(Controller 不写业务,Service 不管 HTTP)讲清楚就够了。