Java 后端开发入门:三层架构、Spring Bean 与模板引擎全解析

7 阅读6分钟

Java 后端开发入门:三层架构、Spring Bean 与模板引擎全解析

本文面向 Java 后端初学者,系统梳理四个核心概念:三层架构(Controller / Service / Dao) 的职责划分、Spring Bean 的本质与注入机制、 @Service 与 @Component 的区别,以及 FreeMarker 模板引擎的基本用法。


一、后端三层架构:Controller、Service、Dao

在实际的 Spring Boot 项目中,代码并不是混在一起写的,而是按照职责拆分成三个独立层次。这样做的核心目的是:分工明确、代码易维护、改一处不影响全局。

用餐厅点餐来类比,三层架构的对应关系非常直观:

层次类比核心职责
Controller 层(控制器)前台服务员接收请求,返回结果,不做复杂业务
Service 层(业务逻辑层)后厨厨师系统的大脑,所有业务规则都在这里
Dao 层(数据访问层)仓库管理员只和数据库打交道,纯增删改查

1.1 各层职责详解

Controller 层 是整个请求的入口。它接收前端(网页或 App)发来的 HTTP 请求,调用 Service 层处理业务,再将结果返回给前端。Controller 本身不写任何业务逻辑,只负责"收发快递"。

Service 层 是系统的核心。所有业务规则都在这里实现:下单要扣库存、支付要验余额、权限校验等。它调用 Dao 层读写数据,并把多个 Dao 操作组合成一个完整的业务流程(事务控制也在这一层)。

Dao 层 只做一件事:执行 SQL,操作数据库。它不理解任何业务含义,只负责"存数据、取数据"。

1.2 三层调用关系

调用方向固定,不能乱:

前端请求
    ↓
Controller(接收请求)
    ↓
Service(处理业务)
    ↓
Dao(操作数据库)
    ↓
数据库

返回方向:数据库 → Dao → Service → Controller → 前端

⚠️ 严格规则

  • Controller 只能调用 Service,不能直接调用 Dao
  • Service 只能调用 Dao
  • Dao 不调用任何人,只操作数据库
  • 禁止跨层调用(如 Controller 直接访问数据库)

1.3 代码示例(查询用户)

Controller 层——只负责接收请求和调用 Service:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        // 只调用 Service,不写业务逻辑
        return userService.getUserById(id);
    }
}

Service 层——处理所有业务规则:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public User getUserById(Long id) {
        // 业务校验
        if (id == null || id <= 0) {
            throw new RuntimeException("用户ID无效");
        }
        User user = userDao.selectById(id);
        // 业务处理:隐藏敏感信息
        user.setPassword(null);
        return user;
    }
}

Dao 层——只做数据库查询,零业务逻辑:

@Mapper
public interface UserDao {
    // 只做数据库查询,无任何业务逻辑
    User selectById(Long id);
}

1.4 三层架构的核心好处

  • 解耦:换数据库不用改业务代码,改业务规则不用动接口层
  • 易定位 Bug:业务逻辑出错找 Service,数据库问题找 Dao,定位迅速
  • 可复用:同一个 Service 可被多个 Controller 调用,同一个 Dao 可被多个 Service 使用
  • 好测试:每一层可以单独进行单元测试,互不干扰

二、Spring Bean 与 @Mapper 的本质

2.1 什么是 Spring Bean?

在 Spring 框架中,Bean 就是被 Spring 容器统一管理的对象。只要满足以下任意一种情况,这个对象就是 Spring Bean:

  • 类上标注了 @Component@Service@Controller@Repository
  • 通过 @Bean 方法返回的对象
  • 被 MyBatis 的 @Mapper 标记的接口(最终也会成为 Bean)

2.2 @Mapper 接口是 Bean 吗?

来看一段典型代码:

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(@Param("id") Integer id);
}

答案是:是 Bean,但它是特殊的 Bean。

这里有一个容易混淆的知识点:接口本身不能被实例化,Spring 无法直接管理一个接口。但 @Mapper 告诉 MyBatis:"帮我给这个接口生成一个代理对象,然后交给 Spring 管理。" 整个流程如下:

你写的 UserMapper 接口
        ↓
  @Mapper 触发 MyBatis 扫描
        ↓
  MyBatis 动态生成代理实现类
        ↓
   注册为 Spring Bean ✓

验证它是 Bean 的最直接证据——可以在 Service 中通过 @Autowired 成功注入:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;  // 能注入 → 铁证是 Bean
}

能被 @Autowired 注入 = 一定是 Spring Bean。 这是判断一个对象是否被 Spring 管理的最简单方法。

2.3 普通 Bean 与 Mapper Bean 的对比

对比项普通 Bean(如 @Service)Mapper Bean(@Mapper)
代码形式类(class)接口(interface)
谁来创建实例开发者自己写实现类MyBatis 在运行时动态生成代理类
主要职责业务逻辑处理执行 SQL,操作数据库
归属与管理Spring 直接管理由 MyBatis 生成,交由 Spring 管理

三、@Service 与 @Component 有什么区别?

3.1 功能上几乎等价

Spring 中有四个常见注解,从"能不能注册成 Bean"这件事上,它们完全等价:@Component@Service@Controller@Repository 都会被 Spring 识别为 Bean,都能被 @Autowired 注入。

3.2 源码层面:@Service 就是 @Component

打开 @Service 的源码,你会看到:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component   // 核心:@Service 本质上就是 @Component
public @interface Service { }

@Service 是一个组合注解,内部包含了 @Component。因此,加 @Service 等于加了 @Component,但反过来不成立。

3.3 真正的区别

区别维度@Component@Service
语义通用组件,不指明用途明确表示这是业务逻辑层
可读性看不出是哪一层一看就知道这是 Service 层
框架扩展无额外功能未来可能有专属扩展支持

值得一提的是,@Repository 还拥有一项其他注解没有的特权:数据库异常自动转译,会把底层的数据库异常统一转换为 Spring 的 DataAccessException,方便统一处理。

3.4 实际开发怎么选?

按照标准三层架构规范来用:

  • Controller 层 → @Controller
  • Service 层 → @Service
  • Dao / Mapper 层 → @Repository@Mapper
  • 其他通用工具类 → @Component

建议:能分层就尽量分层,不要把所有类都标注 @Component。清晰的语义能让代码更易读,也方便团队协作。


四、模板引擎与 FreeMarker 入门

4.1 什么是模板引擎?

模板引擎就是用来拼网页的工具。你写好一个 HTML 模板骨架,里面留一些占位变量(如 ${name}),模板引擎负责把后端传来的数据填进去,生成最终完整的 HTML 页面返回给浏览器。

常见的模板引擎有:Thymeleaf(Spring Boot 官方推荐)、FreeMarker、Velocity,以及早期的 JSP。

4.2 前后端两种渲染方式对比

方式代表技术工作原理适用场景
后端渲染(SSR)FreeMarker、Thymeleaf、JSP后端把 HTML 拼好,直接返回完整页面管理后台、内容展示、SEO 敏感页面
前后端分离Vue、React、Angular后端只返回 JSON,前端自己渲染 HTML交互复杂的 SPA 应用、移动端配套接口

4.3 FreeMarker 快速上手

以实现一个积分排行榜页面为例,分三步完成。

第一步:添加 Maven 依赖

pom.xml 中加入 FreeMarker starter,刷新 Maven 后重启项目:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

第二步:创建模板文件(.ftlh)

src/main/resources/templates/ 目录下创建 rank.ftlh

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>排行榜</title>
</head>
<body>
    <h1>积分排行榜</h1>
    <table border="1">
        <tr>
            <th>排名</th>
            <th>用户ID</th>
            <th>用户名</th>
            <th>总分</th>
        </tr>
        <#list rankList as item>
        <tr>
            <td>${item_index + 1}</td>
            <td>${item.user.id}</td>
            <td>${item.user.name}</td>
            <td>${item.score}</td>
        </tr>
        </#list>
    </table>
</body>
</html>

第三步:编写 Controller 传递数据

注意这里使用的是 @Controller(不是 @RestController),返回字符串即模板名称:

@Controller
public class RankController {

    @Autowired
    private RankDao rankDao;

    @GetMapping("/rank")
    public String rank(Model model) {
        List<RankItem> list = rankDao.getRank();
        // 将数据放入模型,FreeMarker 自动取用
        model.addAttribute("rankList", list);
        // 返回模板名 "rank" → 对应 rank.ftlh
        return "rank";
    }
}

访问 http://localhost:8080/rank 即可看到完整的排行榜页面。

4.4 FreeMarker 最常用的三个语法

输出变量:

${user.name}
${score}

循环列表:

<#list list as item>
    ${item.name}
</#list>

获取索引(用于显示排名):

${item_index}       从 0 开始
${item_index + 1}   从 1 开始

总结

知识点核心结论
三层架构Controller 收发请求 → Service 处理业务 → Dao 操作数据库,各司其职,严禁跨层调用
Spring Bean被 Spring 容器管理的对象;@Mapper 接口通过 MyBatis 动态代理成为 Bean,可被 @Autowired 注入
@Service vs @Component功能等价,都能注册 Bean;@Service 语义更清晰,专用于业务逻辑层
FreeMarker加依赖 → 写 .ftlh 模板 → Controller 传数据,三步完成后端页面渲染

三层架构是 Java 后端开发中最经典的代码组织方式。理解它的分层思想,是写出可维护、可扩展的工程级代码的第一步。