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 后端开发中最经典的代码组织方式。理解它的分层思想,是写出可维护、可扩展的工程级代码的第一步。