大家好!SpringBoot 作为 Spring 生态的 "轻量级脚手架",凭借自动配置、起步依赖、内嵌服务器等特性,彻底简化了 Java Web 开发的繁琐配置。很多新手从 SSM 框架转向 SpringBoot 时,既好奇它的 "零配置" 魔力,也容易在 "分层开发" 和 "解耦设计" 上踩坑。
这篇文章会从 SpringBoot Web 的核心原理入手,通过一个完整的用户管理案例实现快速入门,再深入讲解分层解耦的设计思想与实践技巧,帮你夯实 SpringBoot Web 开发的基础。
一、SpringBoot Web 核心认知:为什么它是 Web 开发的首选?
1.1 什么是 SpringBoot Web?
SpringBoot Web 是 SpringBoot 针对 Web 开发提供的一站式解决方案,它整合了Spring MVC、Tomcat(内嵌服务器)、Jackson(JSON 序列化) 等核心组件,通过起步依赖(Starter) 实现一键引入,通过自动配置省去繁琐的 XML 或 Java 配置,让开发者专注于业务逻辑。
1.2 SpringBoot Web 的核心优势
- 零配置启动:无需手动配置 Spring MVC 的 DispatcherServlet、视图解析器等,启动类一键运行;
- 内嵌服务器:内置 Tomcat、Jetty 等服务器,无需手动部署,直接运行 JAR 包即可启动 Web 服务;
- 起步依赖 :
spring-boot-starter-web一个依赖即可引入 Web 开发所需的所有组件,避免依赖版本冲突; - 无缝集成:与 MyBatis、Redis、Spring Data JPA 等框架无缝集成,降低技术栈整合成本。
1.3 核心概念:起步依赖与自动配置
- 起步依赖(Starter) :是 Maven 的依赖集合,如
spring-boot-starter-web包含了 Spring MVC、Tomcat、Jackson 等依赖,只需引入一个即可; - 自动配置(AutoConfiguration) :SpringBoot 通过
@EnableAutoConfiguration注解,根据类路径下的依赖自动配置 Bean(如检测到spring-webmvc则自动配置 DispatcherServlet)。
二、SpringBoot Web 快速入门:搭建第一个 Web 项目
2.1 环境准备
- JDK:8 及以上(推荐 JDK 8/11);
- 构建工具:Maven/Gradle(本文使用 Maven);
- IDE:IntelliJ IDEA 2020+;
- SpringBoot 版本:3.5.0。
2.2 创建 SpringBoot Web 项目(IDEA 版)
方式 1:通过 Spring Initializr 创建
-
打开 IDEA,点击
File > New > Project; -
左侧选择
Spring Initializr,选择 JDK 版本,点击Next; -
填写项目信息:
- Group:
com.xxx(如com.demo); - Artifact:
springboot-web-demo; - Type:
Maven Project; - Java Version:
17;
- Group:
-
选择依赖:在
Web分类下勾选Spring Web,点击Next; -
选择项目存储路径,点击
Create,IDEA 会自动下载依赖并生成项目结构。
方式 2:手动创建 Maven 项目
- 创建普通 Maven 项目,在
pom.xml中添加 SpringBoot 父依赖和 Web 起步依赖:
xml
<!-- SpringBoot父依赖(统一版本管理) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/>
</parent>
<dependencies>
<!-- Web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 打包插件(可执行JAR包) -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.3 核心项目结构
SpringBoot Web 项目遵循标准的 Maven 结构,核心目录如下:
plaintext
springboot-web-demo
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── demo
│ │ │ ├── SpringbootWebDemoApplication.java // 启动类
│ │ │ ├── controller // 控制器层(接收请求)
│ │ │ ├── service // 服务层(业务逻辑)
│ │ │ ├── mapper // 数据访问层(操作数据库)
│ │ │ └── entity // 实体类(封装数据)
│ │ └── resources
│ │ ├── application.properties // 配置文件
│ │ ├── static // 静态资源(CSS/JS/图片)
│ │ └── templates // 模板文件(Thymeleaf等)
│ └── test // 测试目录
└── pom.xml
2.4 编写第一个接口:Hello World
步骤 1:编写启动类
启动类是 SpringBoot 项目的入口,需添加@SpringBootApplication注解:
java
package com.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 包含@Configuration、@EnableAutoConfiguration、@ComponentScan三个注解
@SpringBootApplication
public class SpringbootWebDemoApplication {
public static void main(String[] args) {
// 启动SpringBoot应用
SpringApplication.run(SpringbootWebDemoApplication.class, args);
}
}
步骤 2:编写控制器(Controller)
在controller包下创建HelloController,处理 HTTP 请求:
java
package com.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //标识当前类可以处理请求,并且会创建当前类对象加入spring容器(内存)
public class HelloController {
//处理指定资源路径的请求
@RequestMapping("/hello")
public String hello(String name){//这里的参数name必须要与前端传递的参数名字name一样,就可以接收数据
System.out.println("后端处理/hello请求,并且接收数据:name="+name);
return "hello " + name;
}
}
步骤 3:启动项目并测试
- 右键启动类,选择
Run 'SpringbootWebDemoApplication'; - 控制台出现
Tomcat started on port(s): 8080 (http)表示启动成功; - 打开浏览器,访问
http://localhost:8080/hello/SpringBoot Web,页面显示Hello SpringBoot Web。
三、实战案例:用户管理系统(CRUD)
接下来通过一个基于SpringBoot开发的web程序------用户管理系统,实现用户列表的渲染展示,让你快速掌握 SpringBoot Web 的开发流程。
3.1 创建实体类
在pojo包下创建User类,封装用户数据:
java
package com.tgt.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data//生成get/set/toString/hashCode/equals方法
@NoArgsConstructor//生成无参构造方法
@AllArgsConstructor//生成全参构造方法
public class User {
private Integer id;
private String username;
private String password;
private String name;
private Integer age;
private LocalDateTime updateTime;
}
注意 :需在pom.xml中添加 Lombok 依赖(简化代码):
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3.2 编写前端HTML
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户列表数据</title>
<style>
/*定义css,美化表格*/
table{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
border: 1px solid #ccc;
text-align: center;
font-size: 14px;
}
tr {
height: 40px;
}
th,td{
border: 1px solid #ccc;
}
thead{
background-color: #e8e8e8;
}
h1{
text-align: center;
font-family: 楷体;
}
</style>
</head>
<body>
<div id="app">
<h1>用户列表数据</h1>
<!--定义一个表格,包括6列,分别是: ID, 用户名, 密码, 姓名, 年龄, 更新时间-->
<table>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
<th>姓名</th>
<th>年龄</th>
<th>更新时间</th>
</tr>
</thead>
<tbody>
<tr v-for="user in userList">
<td>{{user.id}}</td>
<td>{{user.username}}</td>
<td>{{user.password}}</td>
<td>{{user.name}}</td>
<td>{{user.age}}</td>
<td>{{user.updateTime}}</td>
</tr>
</tbody>
</table>
</div>
<!--引入axios-->
<script src="js/axios.min.js"></script>
<script type="module">
import { createApp } from './js/vue.esm-browser.js'
createApp({
data() {
return {
userList: []
}
},
methods: {
async search(){
const result = await axios.get('/list');
this.userList = result.data;
}
},
mounted() {
this.search();
}
}).mount('#app')
</script>
</body>
</html>
3.3 编写控制器(Controller)
在controller包下创建UserController,处理查询用户列表请求:
java
@RestController//标识当前类可以处理请求,并且创建当前类对象加入spring容器中
public class UserController {
/**
* 处理查询用户列表请求
* @return List<User>
*
* 方法返回List<User>,spring框架会自动将其转换为json字符串返回给前端
* '[{"id":xx,"username":xx},{"id":xx,"username":xx},...]'
*/
@RequestMapping("/list")
public List<User> list(){
//定义List<User>用于存储用户列表数据
List<User> users = new ArrayList<>();
//1.创建输入流对象操作user.txt文件
try (
// InputStream in = new FileInputStream("day04-02-web-springboot/src/main/resources/user.txt")
//上面这样写在web项目中不可以,因为web项目会部署给tomcat,目录结构就不是这样的结构
// 解决方案:在web项目要先获取类路径target/classes目录下资源文件的固定语法:类加载器对象.getResourceAsStream("资源文件名")
InputStream in = UserController.class.getClassLoader().getResourceAsStream("user.txt")
){
//2.读取文件中的数据,利用hutool工具一次性读取所有行数据封装到List<String>
List<String> rows = IoUtil.readUtf8Lines(in, new ArrayList<>());
//3.将List<String>转换为List<User>
// 遍历List<String>的每个元素
for (String s : rows) {
// 将类似1,daqiao,1234567890,大乔,22,2024-07-15 15:05:45按照","分隔得到数组
String[] split = s.split(",");
// 将分隔出的数据封装到User对象中
User user = new User(
Integer.valueOf(split[0]),
split[1],
split[2],
split[3],
Integer.valueOf(split[4]),
LocalDateTime.parse(split[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
// 将字符串转换为LocalDateTime
// yyyy 格式化为4个数字的年2025, yy两个数字的年25
// MM 格式化为2个数字的月
// dd 格式化为2个数字的日
// HH 格式化为24小时,hh格式为12小时
// mm 格式化分钟
// ss 格式化秒
);
// 将user对象添加List<User>集合中
users.add(user);
}
return users;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
3.4 编写user.txt
txt
1,daqiao,1234567890,大乔,22,2024-07-15 15:05:45
2,xiaoqiao,1234567890,小乔,18,2024-07-15 15:12:09
3,diaochan,1234567890,貂蝉,21,2024-07-15 15:07:16
4,lvbu,1234567890,吕布,28,2024-07-16 10:05:15
5,zhaoyun,1234567890,赵云,27,2024-07-16 11:03:28
6,zhangfei,1234567890,张飞,31,2024-07-16 11:03:28
7,guanyu,1234567890,关羽,34,2024-07-16 12:05:12
8,liubei,1234567890,刘备,37,2024-07-16 15:03:28
3.5 编写启动类
java
package com.tgt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication//标识当前类为启动类
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class,args);//启动程序
}
}
运行main方法,启动服务器端程序、
在浏览器中输入(http://localhost:8080/user.html)
四、分层解耦:理解 SpringBoot 的设计思想
在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。
由此引申出Controller、Service、Mapper 三层架构,这是 SpringBoot Web 开发的标准分层方式,其核心目的是解耦,让代码更易维护、扩展和测试。
4.1 为什么要分层?
在传统的单体代码中,所有逻辑写在一个类中,会导致:
- 代码耦合度高:修改一个功能可能影响其他功能;
- 可读性差:几百行代码混在一起,难以理解;
- 可测试性差:无法单独测试某个功能点。
分层开发通过单一职责原则,将不同功能拆分到不同层级,每个层级只做一件事:
- Controller 层:只负责接收请求、返回响应,不处理业务逻辑;
- Service 层:只负责业务逻辑处理,不关心请求和数据存储;
- Mapper 层:只负责数据访问,不处理业务逻辑。
4.2 分层架构的核心职责
| 层级 | 核心注解 | 核心职责 | 依赖关系 |
|---|---|---|---|
| Controller(控制层) | @RestController、@RequestMapping |
接收 HTTP 请求、参数校验、返回响应 | 依赖 Service 层 |
| Service(服务层) | @Service |
业务逻辑处理、事务管理、数据校验 | 依赖 Mapper 层 |
| Mapper/Dao(数据访问层) | @Repository |
数据库 CRUD 操作、数据映射 | 无(被 Service 层依赖) |
| Entity/Pojo(实体层) | @Data(Lombok) |
封装数据(对应数据库表 / 请求响应体) | 被所有层级使用 |
4.3 实战案例示例:重写上一节案例的Controller
4.3.1 数据访问层:负责数据的访问操作,包含数据的增、删、改、查
在 com.tgt.dao中创建UserDao接口,代码如下:
java
package com.tgt.dao;
import java.util.List;
/**
* @Description 用户数据数据访问接口
* @Author tgt
* @Date 2025-10-21
*/
public interface IUserDao {
/**
* 解析文件返回数据列表
*
* @return
*/
List<String> GetDao();
}
在 com.tgt.dao.impl 中创建UserDaoImpl接口,代码如下:
java
package com.tgt.dao.impl;
import cn.hutool.core.io.IoUtil;
import com.tgt.controller.UserController;
import com.tgt.dao.IUserDao;
import com.tgt.pojo.User;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
//数据访问层DAO:操作数据增删改查
public class UserDaoImpl implements IUserDao {
/**
* 解析文件返回数据列表
*
* @return
*/
@Override
public List<String> GetDao() {
//定义List<User>用于存储用户列表数据
List<User> users = new ArrayList<>();
//1.创建输入流对象操作user.txt文件
try (
InputStream in = UserController.class.getClassLoader().getResourceAsStream("user.txt")
) {
//2.读取文件中的数据,利用hutool工具一次性读取所有行数据封装到List<String>
List<String> rows = IoUtil.readUtf8Lines(in, new ArrayList<>());
return rows;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
4.3.2 业务逻辑层:处理具体的业务逻辑
在 com.tgt.service中创建UserSerivce接口,代码如下:
java
package com.tgt.service;
import com.tgt.pojo.User;
import java.util.List;
public interface IUserService {
/**
* 获取用户列表业务方法
* @return
*/
List<User> GetUserList();
}
在 com.tgt.service.impl 中创建UserSerivceImpl接口,代码如下:
java
package com.tgt.service.Impl;
import com.tgt.dao.IUserDao;
import com.tgt.dao.impl.UserDaoImpl;
import com.tgt.pojo.User;
import com.tgt.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class UserServiceImpl implements IUserService {
//定义数据访问对象
private IUserDao userDao =new IUserService();
/**
* 获取用户列表业务方法
*
* @return
*/
@Override
public List<User> GetUserList() {
List<User> users = new ArrayList<>();
//1.调用数据访问层方法获取磁盘数据List<String>
List<String> rows = userDao.GetDao();
//2.将List<String>转换为List<User>
// 遍历List<String>的每个元素
for (String s : rows) {
// 将类似1,daqiao,1234567890,大乔,22,2024-07-15 15:05:45按照","分隔得到数组
String[] split = s.split(",");
// 将分隔出的数据封装到User对象中
User user = new User(
Integer.valueOf(split[0]),
split[1],
split[2],
split[3],
Integer.valueOf(split[4]),
LocalDateTime.parse(split[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
);
// 将user对象添加List<User>集合中
users.add(user);
}
return users;
}
}
4.3.3 控制层:接收前端发送的请求,对请求进行处理,并响应数据
在 com.itheima.controller 中创建UserController类,代码如下:
java
package com.tgt.controller;
import cn.hutool.core.io.IoUtil;
import com.tgt.pojo.User;
import com.tgt.service.IUserService;
import com.tgt.service.Impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@RestController//标识当前类可以处理请求,并且创建当前类对象加入spring容器中
public class UserController {
//定义业务对象
private IUserService userService = new UserServiceImpl();
/**
* 处理查询用户列表请求
*
* @return List<User>
*/
@RequestMapping("/list")
public List<User> list() {
return userService.GetUserList();
}
}
4.3 解耦的关键:依赖注入与控制反转
之前我们在编写代码时,需要什么对象,就直接new一个就可以了。 这种做法呢,层与层之间代码就耦合了,当service层的实现变了之后, 我们还需要修改controller层的代码。 那应该怎么解耦呢?
SpringBoot 的依赖注入(DI) 和控制反转(IOC) 是实现分层解耦的核心机制:
- 控制反转(IOC) :将对象的创建权交给 Spring 容器,而非手动
new对象; - 依赖注入(DI) :Spring 容器自动将依赖的对象注入到当前类中(如
@Autowired注入 UserService)。
示例:传统方式 vs 依赖注入
传统方式(耦合度高) :
java
// Controller中手动创建Service实例,耦合度高
@RestController
public class UserController {
private UserService userService = new UserServiceImpl();
}
依赖注入方式(解耦) :
java
// 由Spring容器管理对象,通过@Autowired注入,降低耦合
@RestController
public class UserController {
@Autowired
private UserService userService;
}
4.4 进一步解耦:面向接口编程
我们先定义接口再实现类,这是面向接口编程的思想,其优势在于:
- 降低耦合:Controller 层只依赖 Service 接口,不依赖具体实现类;
- 便于扩展:如需修改业务逻辑,只需新增实现类,无需修改 Controller 代码;
- 便于测试:可通过 Mock 对象模拟 Service 接口,实现单元测试。
4.5 分层解耦的最佳实践
- 层级边界清晰:禁止跨层级调用(如 Controller 直接调用 Mapper);
- 面向接口编程:Service 层优先定义接口,实现类负责具体逻辑;
- 依赖注入 :使用
@Autowired或构造器注入,避免手动创建对象; - 单一职责:每个类 / 方法只做一件事,避免 "全能类";
- 异常统一处理:通过全局异常处理器统一处理各层级的异常。
五、常见问题与解决方案
5.1 启动项目时提示 "找不到 Bean"
- 原因 :组件未被 Spring 扫描(如忘记加
@Service/@Repository注解); - 解决 :检查类上的注解是否正确,确保启动类的包路径包含所有组件(
@SpringBootApplication默认扫描当前包及子包)。
5.2 接口返回 404
- 原因 :请求路径错误、Controller 注解错误(如用
@Controller而非@RestController); - 解决 :检查请求路径与
@RequestMapping是否一致,确保返回数据的注解正确。
5.3 请求体传参时提示 "参数绑定失败"
- 原因:请求体格式错误(非 JSON)、实体类字段与请求体字段不匹配;
- 解决 :确保请求体为 JSON 格式,实体类字段名称与请求体一致(或使用
@JsonProperty注解映射)。
六、总结与延伸
6.1 核心总结
- SpringBoot Web 通过起步依赖和自动配置,实现了 Web 开发的 "零配置" 启动;
- 标准的分层架构为Controller(控制层)、Service(服务层)、Mapper(数据访问层) ,各层级职责单一;
- 依赖注入和面向接口编程是实现分层解耦的关键,能大幅提升代码的可维护性;
- 实战案例中的 CRUD 接口是 Web 开发的基础,掌握后可扩展到更复杂的业务场景。
SpringBoot Web 的入门门槛低,但想要写出优雅、高效的代码,需要深入理解分层解耦的设计思想。建议在实际项目中多动手实践,逐步掌握 SpringBoot 的高级特性,最终形成自己的开发体系。