1、这阶段该如何学习(学习规划篇)
1.1 学习核心逻辑
SpringBoot 学习需遵循 **「基础铺垫→快速上手→核心原理→实战进阶」** 的四阶段逻辑,切忌跳过基础直接啃源码。
1.2 分阶段学习路径
| 阶段 | 核心目标 | 必学内容 | 耗时参考 |
|---|---|---|---|
| 基础铺垫期 | 扫清前置障碍 | Java8 + 基础、Maven 依赖管理、Spring 核心(IOC/AOP) | 1-2 周 |
| 快速上手期 | 跑通项目 + 基础开发 | 环境搭建、项目创建、Web 接口开发、配置文件使用 | 1-2 周 |
| 核心原理期 | 理解底层机制 | 自动装配原理、主启动类运行机制、Starter 自定义 | 2-3 周 |
| 实战进阶期 | 企业级开发能力 | 整合 MyBatis/Redis、异常处理、接口文档、微服务整合 | 3-4 周 |
1.3 避坑指南
- 版本匹配:SpringBoot3.x 必须搭配 JDK17+,SpringBoot2.x 搭配 JDK8+,否则会出现依赖冲突。
- 拒绝死磕源码:初期聚焦「怎么用」,理解核心流程后再深挖源码。
- 工具优先:使用 IDEA 开发,利用其 Spring Initializr 快速生成项目,减少环境配置成本。
2、什么是 SpringBoot(核心认知篇)
2.1 定义与定位
SpringBoot 是基于 Spring 框架的快速开发脚手架,核心目标是简化 Spring 应用的初始搭建与开发过程,实现「零 XML 配置、一键启动、内置服务器」。
2.2 核心优势(对比传统 Spring)
- 约定大于配置:默认配置覆盖 80% 常用场景,无需手动配置 XML。
- 内嵌服务器:内置 Tomcat/Jetty/Undertow,无需额外部署服务器。
- 依赖自动管理:通过 Starter 启动器统一管理依赖版本,解决版本冲突问题。
- 生产级特性:内置监控、指标、健康检查等功能,适配生产环境部署。
2.3 应用场景
适用于中小型 Web 项目、微服务架构基础层、快速原型开发,是目前 Java 后端开发的主流技术栈。
3、什么是微服务架构(架构认知篇)
3.1 核心定义
微服务是一种分布式架构风格,将单体应用按业务领域拆分为多个独立、自治的小型服务,每个服务独立部署、独立运维,通过 HTTP/RPC 等协议通信协作。
3.2 单体架构 vs 微服务架构(核心差异)
| 维度 | 单体架构 | 微服务架构 |
|---|---|---|
| 代码结构 | 所有模块打包在一个工程,单进程运行 | 按业务拆分为独立工程,多进程运行 |
| 数据存储 | 共享单一数据库 | 每个服务独立数据库,数据自治 |
| 部署迭代 | 一处修改全量部署,风险高 | 单个服务独立迭代,部署灵活 |
| 故障影响 | 单点故障导致整个系统崩溃 | 故障隔离,仅影响单个服务 |
3.3 核心特征与适用场景
- 核心特征:单一职责、服务自治、轻量级通信、去中心化治理、故障隔离。
- 适用场景 :中大型团队、业务复杂度高、需多团队并行开发;不适用:小型项目、初创团队(硬上会增加运维成本)。
3.4 理论基础
- CAP 定理:分布式系统中,一致性(C)、可用性(A)、分区容错性(P)三者不可兼得,微服务中优先保证 CP/AP。
- BASE 理论:CAP 的落地实践,核心是基本可用、软状态、最终一致性。
4、第一个 SpringBoot 程序(实战入门篇)
4.1 环境准备
-
必备工具:JDK1.8+(推荐 1.8)、Maven3.6+、IDEA(社区版 / 旗舰版)。
-
环境验证 :
# 验证JDK版本 java -version # 验证Maven版本 mvn -v
4.2 项目创建(Spring Initializr)
- 打开 IDEA → 新建项目 → 选择「Spring Initializr」。
- 基础配置:
- Group:com.example(自定义,反向域名格式)
- Artifact:springboot-demo
- Type:Maven
- Java Version:8
- 依赖选择(初学者必选):
- Web → Spring Web(开发 Web 接口)
- Developer Tools → Spring Boot DevTools(热部署)
- Core → Lombok(简化实体类代码)
- 完成创建,等待 Maven 下载依赖。
4.3 项目结构解析
springboot-demo/
├── src/
│ ├── main/
│ │ ├── java/com/example/springbootdemo/
│ │ │ ├── SpringbootDemoApplication.java # 启动类
│ │ │ ├── controller/ # 接口层
│ │ │ ├── service/ # 业务层
│ │ │ └── mapper/ # 数据访问层
│ │ └── resources/
│ │ ├── application.properties # 配置文件
│ │ ├── static/ # 静态资源
│ │ └── templates/ # 模板文件
│ └── test/ # 测试类
└── pom.xml # 依赖配置文件
4.4 编写第一个接口
-
在 controller 包下创建 HelloController:
package com.example.springbootdemo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @RestController = @Controller + @ResponseBody,返回JSON数据 */ @RestController public class HelloController { /** * @GetMapping:处理GET请求,指定访问路径 */ @GetMapping("/hello") public String helloSpringBoot() { return "Hello SpringBoot!这是我的第一个接口~"; } }
4.5 运行与测试
- 运行启动类 SpringbootDemoApplication(main 方法)。
- 浏览器访问:http://localhost:8080/hello,返回结果即表示成功。
5、IDEA 快速创建及彩蛋(工具优化篇)
5.1 快速创建进阶技巧
- 自定义初始依赖:创建项目时可直接勾选所需依赖(如 MyBatis、MySQL),无需后续手动添加。
- 离线模式创建:网络不佳时,使用 IDEA 离线模式创建项目,优先加载本地缓存依赖。
- 多模块项目创建:通过「New → Module」快速添加子模块,适配多模块架构开发。
5.2 IDEA 核心彩蛋(效率提升技巧)
- 代码生成:右键 → Generate → 快速生成 get/set、构造器、toString 等方法(配合 Lombok 可省略此步骤)。
- 热部署配置 :
- 开启 DevTools 后,修改代码无需重启项目,自动生效。
- IDEA 设置:File → Settings → Build, Execution, Deployment → Compiler → 勾选「Build project automatically」。
- 快捷键优化 :
- Ctrl+Alt+O:优化导入包(去除无用导入)。
- Ctrl+Shift+Alt+N:快速查找类 / 方法。
- Alt+Insert:快速生成代码模板。
- 运行配置:启动类配置中开启「Allow parallel run」,支持多项目并行启动。
6、SpringBoot 自动装配原理(核心原理解析篇)
6.1 核心本质
自动装配是 SpringBoot 实现「零配置」的核心,基于Spring 条件注解 + SPI 机制,根据项目依赖自动配置组件,无需手动编写 XML 配置。
6.2 核心入口:@SpringBootApplication
该注解是组合注解,包含三个核心子注解:
@SpringBootConfiguration // 标识配置类,等价于@Configuration
@ComponentScan // 扫描当前包及子包的Bean
@EnableAutoConfiguration // 开启自动装配,核心注解
public @interface SpringBootApplication {}
6.3 自动装配执行流程
- 加载候选配置:通过 SpringFactoriesLoader 加载 META-INF/spring.factories(2.x)/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(3.x)中的自动配置类。
- 条件过滤:通过 @Conditional 系列注解(@ConditionalOnClass、@ConditionalOnMissingBean 等)筛选符合当前环境的配置类。
- 注册 Bean:过滤后的配置类被解析为 Spring 配置类,通过 @Bean 注解将组件注册到 IOC 容器。
6.4 核心规则
- 用户配置优先:@ConditionalOnMissingBean 保证用户自定义组件覆盖自动配置组件。
- Starter 依赖封装:Starter 启动器封装了依赖版本,引入 starter 即可自动管理依赖。
7、了解下主启动类怎么运行(启动流程篇)
7.1 主启动类核心代码
package com.example.springbootdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主启动类:SpringBoot应用的入口
*/
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
// 核心方法:启动SpringBoot应用
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
7.2 运行流程拆解
- 初始化 SpringApplication:加载应用类型、初始化初始化器、监听器等核心组件。
- 执行 run 方法 :
- 加载配置环境(Environment),获取配置信息。
- 创建 IOC 容器(ApplicationContext)。
- 执行自动装配,加载 Bean 定义。
- 刷新容器(refresh ()),完成 Bean 的创建、初始化。
- 启动内嵌服务器(Tomcat),监听端口。
7.3 关键节点
-
包扫描规则 :默认扫描主启动类所在包及子包,若 Controller/Service 不在该包下,需手动指定扫描路径:
@SpringBootApplication(scanBasePackages = "com.xxx") -
异常启动排查 :
- 端口占用:通过 server.port=8081 修改端口。
- 依赖冲突:使用 mvn dependency:tree 排查依赖冲突,排除多余依赖。
8、yaml 语法讲解(配置文件篇)
8.1 定义与优势
YAML(YAML Ain't Markup Language)是人性化的序列化语言,相比 properties 文件,支持层级结构、注释、数据类型,更适合复杂配置。
8.2 核心语法规则
- 缩进规范:使用空格缩进(禁止 Tab),同一层级缩进一致(推荐 2/4 个空格)。
- 键值对格式:key: value(冒号后必须加空格)。
- 数据类型 :
- 字符串:无需引号,含特殊字符时用单 / 双引号。
- 布尔值:true/false。
- 数字:整数 / 浮点数。
- 空值:用~表示。
8.3 常用数据结构
-
对象 / 嵌套配置 :
yaml
# 基础对象 user: name: 张三 age: 20 gender: 男 # 嵌套对象 server: port: 8080 servlet: context-path: /demo -
数组 / 列表 :
yaml
# 列表 hobbies: - 编程 - 阅读 - 运动 # 行内列表 hobbies: [编程, 阅读, 运动] -
多行字符串 :
yaml
# 保留换行 desc: | SpringBoot是快速开发脚手架 支持自动装配、内嵌服务器 # 折叠换行 desc: > SpringBoot是快速开发脚手架 支持自动装配、内嵌服务器
8.4 SpringBoot 中 YAML 使用
- 文件命名:application.yml(核心配置文件)。
- 配置读取 :
-
使用 @Value 注解读取单个配置:
@RestController public class ConfigController { @Value("${user.name}") private String userName; @GetMapping("/name") public String getUserName() { return userName; } } -
使用 @ConfigurationProperties 读取批量配置:
@Component @ConfigurationProperties(prefix = "user") @Data public class UserConfig { private String name; private int age; }
-
9、给属性赋值的几种方式
9.1 核心场景
SpringBoot 中给 Bean 属性赋值,是配置与业务解耦的核心手段,常见 5 种主流方式,覆盖不同使用场景。
9.2 方式 1:@Value 注解(简单场景)
-
适用场景:单个 / 少量配置项注入,简单类型(字符串、数字、布尔)
-
代码示例
@Component
public class UserConfig {
// 直接注入配置文件值
@Value("${user.name}")
private String userName;@Value("${user.age:18}") // 冒号后为默认值,配置不存在时生效 private Integer userAge; // 支持SpEL表达式 @Value("#{10 * 2}") private Integer score;}
-
优缺点:简单易用,不适合批量配置;仅支持基础类型,不支持复杂对象。
9.3 方式 2:@ConfigurationProperties(批量场景)
-
适用场景:批量配置注入,复杂对象 / 嵌套配置,推荐企业级开发使用
-
代码示例:
@Component
@ConfigurationProperties(prefix = "user") // 绑定配置前缀
@Data // Lombok简化get/set
public class UserProperties {
private String name;
private Integer age;
private List<String> hobbies; // 支持集合
private Map<String, String> info; // 支持Map
} -
配置文件(application.yml):
yaml
user:
name: 张三
age: 20
hobbies:
- 编程
- 阅读
info:
phone: 138xxxx1234
address: 北京
- 优缺点 :支持批量注入、复杂类型、自动校验;需配合
@EnableConfigurationProperties或@Component生效。
9.4 方式 3:@PropertySource(自定义配置文件)
-
适用场景 :加载自定义
xxx.properties/xxx.yml文件,拆分配置 -
代码示例:
@Component
@PropertySource("classpath:custom.properties") // 指定自定义配置文件
@ConfigurationProperties(prefix = "custom")
@Data
public class CustomProperties {
private String title;
private String desc;
} -
注意 :SpringBoot 2.4+ 对
@PropertySource加载 yml 支持有限,优先用 properties 文件。
9.5 方式 4:@Environment(动态读取)
-
适用场景:运行时动态获取配置,无需提前绑定 Bean
-
代码示例:
@RestController
public class EnvController {
@Autowired
private Environment environment;@GetMapping("/env") public String getEnv() { // 动态读取配置 String port = environment.getProperty("server.port"); String name = environment.getProperty("user.name", "默认名"); return "端口:" + port + ",用户名:" + name; }}
9.6 方式 5:命令行 / 系统环境变量(启动时赋值)
-
适用场景:启动时动态传参,无需修改配置文件
-
示例
命令行传参(优先级最高)
java -jar demo.jar --server.port=8081 --user.name=李四
系统环境变量(Linux/Mac)
export USER_NAME=王五
java -jar demo.jar -
优先级规则:命令行参数 > 系统环境变量 > 配置文件(application.yml)
10、JSR303 校验
10.1 核心定义
JSR303 是 Java 后端参数校验的标准规范,SpringBoot 通过spring-boot-starter-validation starter 快速集成,实现接口入参的自动化校验,避免手动校验代码冗余。
10.2 环境准备
- pom.xml 依赖:
xml
<!-- SpringBoot 2.3+ 需手动引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
10.3 常用校验注解
| 注解 | 作用 | 适用类型 |
|---|---|---|
@NotNull |
不能为 null | 任意类型 |
@NotBlank |
不能为 null 且长度 > 0(去除空格) | 字符串 |
@NotEmpty |
不能为 null 且长度 > 0 | 字符串、集合、数组 |
@Min/@Max |
数值最小 / 最大值 | 数字 |
@Email |
邮箱格式校验 | 字符串 |
@Pattern |
自定义正则校验 | 字符串 |
@Length |
字符串长度范围 | 字符串 |
10.4 代码实战
-
实体类加校验注解:
@Data
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Length(min = 3, max = 10, message = "用户名长度需在3-10之间")
private String username;@NotBlank(message = "密码不能为空") @Length(min = 6, max = 20, message = "密码长度需在6-20之间") private String password; @Email(message = "邮箱格式不正确") private String email; @Min(value = 18, message = "年龄不能小于18岁") private Integer age;}
-
Controller 开启校验:
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/add")
public String addUser(@Valid @RequestBody UserDTO userDTO, BindingResult result) {
// 校验不通过,返回错误信息
if (result.hasErrors()) {
return result.getFieldError().getDefaultMessage();
}
return "用户添加成功";
}
} -
全局异常处理(优化返回):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidException(MethodArgumentNotValidException e) {
Map<String, String> errorMap = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(error -> {
errorMap.put(error.getField(), error.getDefaultMessage());
});
return errorMap;
}
}
10.5 进阶:分组校验
-
适用场景:同一实体类不同接口(新增 / 编辑)使用不同校验规则
-
代码示例:
// 定义分组接口
public interface AddGroup {}
public interface UpdateGroup {}// 实体类加分组
@Data
public class UserDTO {
@NotNull(message = "用户ID不能为空", groups = UpdateGroup.class)
private Long id;@NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class}) private String username;}
// Controller指定分组
@PostMapping("/add")
public String addUser(@Validated(AddGroup.class) @RequestBody UserDTO userDTO) {
return "新增成功";
}@PutMapping("/update")
public String updateUser(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
return "更新成功";
}
11、多环境配置及配置文件位置
11.1 多环境配置核心需求
开发、测试、生产环境的配置(数据库、端口、日志)不同,需通过多环境配置实现一键切换,避免手动修改配置文件。
11.2 多环境配置实现
- 文件命名规则 :
application-{环境名}.yml,如:application-dev.yml(开发环境)application-test.yml(测试环境)application-prod.yml(生产环境)
- 主配置文件(application.yml)指定激活环境:
yaml
# 主配置文件
spring:
profiles:
active: dev # 激活开发环境
- 各环境配置示例:
yaml
# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
username: root
password: 123456
# application-prod.yml
server:
port: 80
spring:
datasource:
url: jdbc:mysql://prod-mysql:3306/prod_db
username: prod_user
password: xxxxxx
11.3 配置文件加载位置与优先级
SpringBoot 启动时会按优先级从高到低 加载以下位置的application.yml/application.properties,高优先级配置覆盖低优先级:
- 项目根目录下的
/config文件夹 - 项目根目录
classpath:/config/(resources 下的 config 文件夹)classpath:/(resources 根目录)
11.4 环境切换的其他方式
-
命令行启动指定环境:
java -jar demo.jar --spring.profiles.active=prod
-
IDEA 启动指定环境 :
- 编辑启动配置 → VM options 添加:
-Dspring.profiles.active=test
- 编辑启动配置 → VM options 添加:
-
系统环境变量指定:
export SPRING_PROFILES_ACTIVE=prod
java -jar demo.jar
12、自动配置原理再理解
12.1 核心回顾
自动配置是 SpringBoot 的灵魂,核心是 **「条件化加载 + SPI 机制」**,根据项目依赖自动注入 Bean,无需手动配置。
12.2 核心入口:@EnableAutoConfiguration
该注解是自动配置的开关,包含两个核心子注解:
@AutoConfigurationPackage // 自动扫描主启动类所在包的组件
@Import(AutoConfigurationImportSelector.class) // 核心:加载自动配置类
public @interface EnableAutoConfiguration {}
12.3 自动配置完整流程
- 加载候选配置类 :
- SpringBoot 2.x:通过
SpringFactoriesLoader加载META-INF/spring.factories中的所有自动配置类 - SpringBoot 3.x:通过
AutoConfigurationImportSelector加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中的配置类
- SpringBoot 2.x:通过
- 条件过滤(核心) :
- 通过
@Conditional系列注解过滤不符合当前环境的配置类,常见注解:@ConditionalOnClass:类路径下存在指定类才生效@ConditionalOnMissingBean:容器中不存在指定 Bean 才生效(用户自定义 Bean 优先)@ConditionalOnProperty:配置文件中存在指定属性才生效
- 通过
- 注册 Bean 到 IOC 容器 :
- 过滤后的配置类被解析,通过
@Bean注解将组件注册到 Spring 容器
- 过滤后的配置类被解析,通过
- 配置生效 :
- 自动配置的 Bean 可直接在项目中使用,如
DataSource、RestTemplate等
- 自动配置的 Bean 可直接在项目中使用,如
12.4 自定义 Starter(自动配置实战)
-
创建自动配置类:
@Configuration
@ConditionalOnClass(MyService.class) // 类路径存在MyService才生效
@EnableConfigurationProperties(MyProperties.class) // 绑定配置
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户未自定义时才创建
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
} -
注册自动配置类 :
- 在
resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中添加:
plaintext
com.example.config.MyAutoConfiguration - 在
-
引入 Starter 即可自动生效,无需手动配置。
12.5 调试技巧
-
启动时添加
--debug参数,查看自动配置报告:java -jar demo.jar --debug
-
控制台会输出
Positive matches(生效的自动配置)和Negative matches(未生效的自动配置),方便排查问题。
13、web 开发探究
13.1 SpringBoot Web 开发核心特性
SpringBoot 通过spring-boot-starter-web starter,自动集成了 SpringMVC、Tomcat、JSON 处理等 Web 开发核心组件,实现零配置开箱即用。
13.2 自动配置的核心组件
| 组件 | 作用 |
|---|---|
DispatcherServlet |
SpringMVC 核心分发器,自动注册 |
| 内嵌 Tomcat | 默认端口 8080,无需额外部署 |
Jackson |
JSON 序列化 / 反序列化,自动处理接口返回 |
| 静态资源映射 | 自动映射/static、/public等目录 |
| 视图解析器 | 自动配置 Thymeleaf 等模板引擎 |
| 全局异常处理 | 支持@RestControllerAdvice全局捕获 |
13.3 Web 开发核心流程
- 请求入口 :客户端请求进入 Tomcat,由
DispatcherServlet拦截 - 路由匹配 :通过
@RequestMapping/@GetMapping等注解匹配 Controller - 参数解析:自动解析请求参数、JSON、表单数据
- 业务处理:Controller 调用 Service 完成业务逻辑
- 响应处理:自动将返回值序列化为 JSON,返回给客户端
13.4 自定义 Web 配置(不破坏自动配置)
-
方式 1:
WebMvcConfigurer(推荐)@Configuration
public class WebConfig implements WebMvcConfigurer {
// 自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/")
.excludePathPatterns("/login", "/static/");
}// 自定义跨域配置 @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE"); }}
-
方式 2:
@Bean自定义组件@Configuration
public class WebConfig {
// 自定义消息转换器,处理日期格式
@Bean
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converter.setObjectMapper(mapper);
return converter;
}
}
13.5 注意事项
- 禁止使用
@EnableWebMvc:该注解会完全禁用 SpringBoot 的 Web 自动配置,仅在完全自定义 SpringMVC 时使用。 - 拦截器优先级 :通过
order()方法指定拦截器执行顺序,数值越小优先级越高。
14、静态资源导入探究
14.1 静态资源默认映射规则
SpringBoot 自动配置了静态资源映射,无需手动配置,默认映射以下目录(classpath即 resources 目录):
/static/public/resources/META-INF/resources
14.2 访问规则
- 静态资源放在以上目录中,可直接通过
/资源名访问,无需前缀 - 示例:
resources/static/css/style.css→ 访问路径:http://localhost:8080/css/style.css
14.3 自定义静态资源映射
- 方式 1:配置文件修改
yaml
spring:
web:
resources:
static-locations: classpath:/my-static/, classpath:/custom/ # 自定义静态资源目录
-
方式 2:
WebMvcConfigurer配置@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**") // 访问路径
.addResourceLocations("classpath:/my-static/") // 实际目录
.setCachePeriod(3600); // 缓存时间(秒)
}
}
14.4 静态资源缓存配置
yaml
spring:
web:
resources:
cache:
period: 3600 # 静态资源缓存时间(秒),生产环境建议开启
cache-control:
max-age: 3600
no-cache: false
14.5 常见问题排查
- 静态资源 404 :
- 检查资源是否放在默认目录,或自定义映射是否正确
- 检查是否被拦截器拦截,添加排除路径
- 静态资源不更新 :
- 关闭缓存(开发环境):
spring.web.resources.cache.period=0 - 清除浏览器缓存,或强制刷新(Ctrl+F5)
- 关闭缓存(开发环境):
15、首页和图标定制
15.1 首页定制
SpringBoot 默认会自动识别静态资源目录下的index.html作为首页,无需额外配置:
- 放置位置:
resources/static/index.html - 访问路径:
http://localhost:8080/直接访问首页
自定义首页(非 index.html)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 将根路径映射到自定义首页
registry.addViewController("/").setViewName("forward:/home.html");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
15.2 图标(favicon.ico)定制
1. 传统方式(SpringBoot 2.x)
- 放置位置:
resources/static/favicon.ico - 自动生效,浏览器标签页显示自定义图标
2. SpringBoot 3.x 配置(推荐)
SpringBoot 3.x 移除了默认 favicon 自动配置,需手动配置:
yaml
spring:
web:
resources:
static-locations: classpath:/static/
- 放置
favicon.ico到resources/static/目录 - 或通过 HTML 手动引入:
html
<link rel="icon" type="image/x-icon" href="/favicon.ico">
15.3 注意事项
- 图标格式必须为
ico,推荐尺寸 16x16、32x32 - 清除浏览器缓存后才能看到新图标
- 生产环境建议使用 CDN 加速静态资源,提升加载速度
16、thymeleaf 模板引擎
16.1 核心定义
Thymeleaf 是 SpringBoot 推荐的服务端模板引擎,用于渲染动态 HTML 页面,替代传统 JSP,支持自然模板(直接打开 HTML 可查看静态效果),完美适配 SpringMVC。
16.2 环境准备
- pom.xml 依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 默认配置(无需修改):
yaml
spring:
thymeleaf:
prefix: classpath:/templates/ # 模板文件目录
suffix: .html # 模板文件后缀
mode: HTML # 模板模式
cache: true # 生产环境开启缓存,开发环境关闭
16.3 核心语法
1. 变量表达式 ${}
- 用于获取后台传递的变量,渲染到页面
html
<p th:text="${user.name}">用户名</p>
<p th:text="${user.age}">年龄</p>
2. 选择表达式 *{}
- 配合
th:object使用,简化变量获取
html
<div th:object="${user}">
<p th:text="*{name}">用户名</p>
<p th:text="*{age}">年龄</p>
</div>
3. 链接表达式 @{}
- 用于处理 URL,自动拼接项目上下文路径
html
<a th:href="@{/user/list}">用户列表</a>
<img th:src="@{/static/img/logo.png}" alt="logo">
4. 条件判断
html
<!-- if判断 -->
<p th:if="${user.age >= 18}">成年</p>
<p th:unless="${user.age >= 18}">未成年</p>
<!-- 三目运算 -->
<p th:text="${user.age >= 18} ? '成年' : '未成年'"></p>
5. 循环遍历
html
<table>
<tr th:each="user : ${userList}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
</tr>
</table>
16.4 代码实战
-
Controller 传递数据
@Controller
@RequestMapping("/page")
public class PageController {
@GetMapping("/index")
public String index(Model model) {
// 传递单个对象
User user = new User("张三", 20);
model.addAttribute("user", user);// 传递集合 List<User> userList = Arrays.asList( new User("张三", 20), new User("李四", 22) ); model.addAttribute("userList", userList); // 返回模板名称(对应templates/index.html) return "index"; }}
-
模板文件(templates/index.html):
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1 th:text="'欢迎你,' + ${user.name}"></h1>
<h3>用户列表</h3>
<table border="1">
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
</tr>
<tr th:each="user : ${userList}">
<td th:text="${userStat.count}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
</tr>
</table>
</body>
</html>
16.5 开发环境配置(关闭缓存)
yaml
spring:
thymeleaf:
cache: false # 开发环境关闭缓存,修改页面实时生效
prefix: classpath:/templates/
16.6 常见问题
- 模板 404 :检查模板是否放在
templates目录,文件名与 Controller 返回值一致 - 页面不更新:关闭 Thymeleaf 缓存,清除浏览器缓存
- 静态资源无法加载 :使用
@{}表达式处理 URL,确保静态资源放在static目录
17、Thymeleaf 语法(进阶篇)
17.1 核心语法回顾与补充
Thymeleaf 是 SpringBoot 推荐的服务端模板引擎,核心是通过th:前缀的自定义属性实现动态渲染,以下是高频进阶语法:
1. 常用表达式(核心)
| 表达式类型 | 语法 | 作用 |
|---|---|---|
| 变量表达式 | ${} |
获取后台 Model 传递的变量,支持对象、集合、方法调用 |
| 选择表达式 | *{} |
配合th:object使用,简化对象属性获取 |
| 链接表达式 | @{} |
处理 URL,自动拼接项目上下文路径 |
| 消息表达式 | #{} |
读取国际化资源文件,实现多语言 |
| 片段表达式 | ~{} |
引用模板片段,实现页面复用 |
2. 常用th:属性(高频)
html
<!-- 文本渲染 -->
<p th:text="${user.name}">默认文本</p>
<p th:utext="${user.htmlContent}">渲染HTML内容(不转义)</p>
<!-- 条件判断 -->
<p th:if="${user.age >= 18}">成年</p>
<p th:unless="${user.age < 18}">未成年</p>
<div th:switch="${user.role}">
<p th:case="'admin'">管理员</p>
<p th:case="'user'">普通用户</p>
<p th:case="*">其他角色</p>
</div>
<!-- 循环遍历 -->
<tr th:each="emp : ${empList}" th:class="${empStat.odd}? 'odd'">
<td th:text="${empStat.count}">序号</td>
<td th:text="${emp.id}">ID</td>
<td th:text="${emp.name}">姓名</td>
</tr>
<!-- empStat为循环状态对象,包含count、index、even/odd等属性 -->
<!-- URL绑定 -->
<a th:href="@{/emp/list}">员工列表</a>
<a th:href="@{/emp/detail/{id}(id=${emp.id})}">详情</a>
<img th:src="@{/static/img/logo.png}" alt="logo">
<!-- 表单绑定 -->
<input type="text" th:field="*{user.name}" />
<input type="checkbox" th:field="*{user.hobbies}" th:value="'编程'" />
<!-- 片段引用 -->
<div th:replace="~{common/head :: head}"></div>
3. 内置工具对象(常用)
html
<!-- 日期格式化 -->
<p th:text="${#temporals.format(user.createTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
<!-- 字符串处理 -->
<p th:text="${#strings.toUpperCase(user.name)}"></p>
<!-- 集合处理 -->
<p th:text="${#lists.size(empList)}"></p>
<!-- 数字格式化 -->
<p th:text="${#numbers.formatDecimal(user.salary, 0, 2)}"></p>
4. 模板片段复用(页面模块化)
- 定义片段(
resources/templates/common/head.html):
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head(title)">
<meta charset="UTF-8">
<title th:text="${title}">默认标题</title>
<link rel="stylesheet" th:href="@{/static/css/bootstrap.min.css}">
</head>
</html>
- 引用片段:
html
<head th:replace="~{common/head :: head('员工管理系统')}"></head>
18、MVC 配置原理
18.1 SpringBoot MVC 自动配置核心
SpringBoot 通过spring-boot-starter-web自动配置 SpringMVC,核心入口是WebMvcAutoConfiguration自动配置类,遵循 **「约定大于配置」** 原则,默认配置覆盖 80% 常用场景。
18.2 自动配置的核心组件
| 组件 | 作用 |
|---|---|
DispatcherServlet |
SpringMVC 核心分发器,自动注册,拦截所有请求 |
HandlerMapping/HandlerAdapter |
处理请求映射与适配器,支持 RESTful 接口 |
ViewResolver |
视图解析器,自动配置 Thymeleaf 等模板引擎 |
MessageConverter |
消息转换器,默认集成 Jackson 处理 JSON |
StaticResourceHandler |
静态资源映射,自动映射/static等目录 |
LocaleResolver |
国际化解析器,支持多语言切换 |
HandlerExceptionResolver |
全局异常处理器,支持@RestControllerAdvice |
18.3 自动配置生效条件
- 类路径下存在
spring-webmvc依赖 - 用户未自定义
@EnableWebMvc注解(该注解会完全禁用自动配置) - 遵循
@ConditionalOnMissingBean规则:用户自定义 Bean 优先于自动配置
18.4 配置优先级
用户自定义配置(WebMvcConfigurer) > SpringBoot 自动配置 > SpringMVC 默认配置
19、扩展 SpringMVC
19.1 扩展原则
SpringBoot 提供WebMvcConfigurer接口,用于扩展 SpringMVC 功能,不破坏自动配置 ,禁止使用@EnableWebMvc(会完全覆盖自动配置)。
19.2 常用扩展场景与代码实现
1. 拦截器配置(登录校验、权限控制)
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册登录拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns(
"/login", "/logout", // 登录/登出
"/static/**", "/error" // 静态资源、错误页
);
}
}
2. 跨域配置(CORS)
java
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 允许所有来源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600); // 预检请求有效期
}
3. 视图控制器(无业务逻辑的页面跳转)
java
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 根路径跳转到首页
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
4. 静态资源映射(自定义目录)
java
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600);
}
5. 消息转换器(自定义日期格式、全局序列化)
java
@Bean
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
// 自定义日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 处理null值
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
converter.setObjectMapper(mapper);
return converter;
}
20、员工管理系统:准备工作
20.1 项目需求
实现一个基础员工管理系统,包含:首页展示、员工 CRUD、登录认证、权限拦截、国际化等功能,基于 SpringBoot+Thymeleaf+Bootstrap 实现。
20.2 环境准备
- 依赖引入(pom.xml):
xml
<dependencies>
<!-- Web核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 项目结构:
plaintext
src/main/java/com/example/emp/
├── controller/ # 控制器
├── entity/ # 实体类
├── interceptor/ # 拦截器
├── service/ # 业务层
└── EmpApplication.java # 启动类
src/main/resources/
├── static/ # 静态资源(css、js、img)
├── templates/ # 模板页面
│ ├── common/ # 公共片段
│ ├── emp/ # 员工相关页面
│ └── index.html # 首页
├── application.yml # 主配置文件
└── i18n/ # 国际化资源文件
- 核心配置(application.yml):
yaml
server:
port: 8080
spring:
# 国际化配置
messages:
basename: i18n/login
# Thymeleaf配置
thymeleaf:
cache: false # 开发环境关闭缓存
prefix: classpath:/templates/
suffix: .html
- 实体类定义:
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; // 1男 0女
private String department;
private Date birth;
}
21、员工管理系统:首页实现
21.1 首页需求
- 展示系统导航栏、侧边栏
- 欢迎语展示
- 跳转员工列表页面
21.2 页面实现(templates/index.html)
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('员工管理系统')}"></head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="#">员工管理系统</a>
<div class="pull-right">
<a class="btn btn-outline-light" th:href="@{/logout}">退出登录</a>
</div>
</nav>
<!-- 主体内容 -->
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<div class="col-md-2">
<div class="list-group">
<a href="#" class="list-group-item active">首页</a>
<a th:href="@{/emp/list}" class="list-group-item">员工管理</a>
<a href="#" class="list-group-item">部门管理</a>
</div>
</div>
<!-- 主内容区 -->
<div class="col-md-10">
<div class="jumbotron">
<h1 class="display-4">欢迎使用员工管理系统!</h1>
<p class="lead">本系统基于SpringBoot+Thymeleaf开发,支持员工CRUD、登录认证、国际化等功能。</p>
<hr class="my-4">
<a class="btn btn-primary btn-lg" th:href="@{/emp/list}" role="button">查看员工列表</a>
</div>
</div>
</div>
</div>
</body>
</html>
21.3 控制器实现
java
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
22、员工管理系统:国际化
22.1 国际化核心原理
SpringBoot 通过MessageSourceAutoConfiguration自动配置国际化,基于ResourceBundleMessageSource加载资源文件,通过LocaleResolver解析用户语言环境,实现多语言切换。
22.2 实现步骤
-
创建国际化资源文件 (
resources/i18n/目录):login.properties(默认中文)login_en_US.properties(英文)login_zh_CN.properties(中文)
-
资源文件内容:
properties
# login.properties(默认中文)
login.username=用户名
login.password=密码
login.btn=登录
login.tip=请登录
# login_en_US.properties(英文)
login.username=Username
login.password=Password
login.btn=Sign in
login.tip=Please sign in
- 配置文件指定资源路径:
yaml
spring:
messages:
basename: i18n/login # 资源文件前缀
encoding: UTF-8
- 页面国际化(
templates/login.html):
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head(#{login.tip})}"></head>
<body>
<div class="container">
<form th:action="@{/login}" method="post" class="form-signin">
<h2 class="form-signin-heading" th:text="#{login.tip}"></h2>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}"></button>
</form>
<!-- 语言切换 -->
<div class="text-center mt-3">
<a th:href="@{/login(lang='zh_CN')}">中文</a> |
<a th:href="@{/login(lang='en_US')}">English</a>
</div>
</div>
</body>
</html>
-
自定义 LocaleResolver(切换语言):
@Component
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String lang = request.getParameter("lang");
Locale locale = Locale.getDefault();
if (StringUtils.hasText(lang)) {
String[] split = lang.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}@Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {}}
23、员工管理系统:登录功能实现
23.1 登录需求
- 用户名 / 密码校验
- 登录成功跳转首页
- 登录失败返回错误提示
- 记住登录状态(Session 存储)
23.2 登录页面(templates/login.html)
见 22.2 节国际化页面实现。
23.3 控制器实现
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage() {
return "login";
}
@PostMapping("/login")
public String login(String username, String password, HttpSession session, Model model) {
// 模拟校验(实际项目对接数据库)
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功,存储用户信息
session.setAttribute("loginUser", username);
return "redirect:/";
} else {
// 登录失败,返回错误提示
model.addAttribute("msg", "用户名或密码错误");
return "login";
}
}
@GetMapping("/logout")
public String logout(HttpSession session) {
// 清除Session
session.invalidate();
return "redirect:/login";
}
}
23.4 登录页面错误提示优化
html
<div class="text-center text-danger" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></div>
24、员工管理系统:登录拦截器
24.1 拦截器需求
- 未登录用户禁止访问系统页面,强制跳转登录页
- 放行登录、静态资源等请求
24.2 拦截器实现
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取登录用户
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
// 未登录,跳转登录页
request.setAttribute("msg", "请先登录");
request.getRequestDispatcher("/login").forward(request, response);
return false;
}
// 已登录,放行
return true;
}
}
24.3 注册拦截器(WebConfig)
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/login", "/logout",
"/static/**", "/error",
"/favicon.ico"
);
}
}
24.4 注意事项
- 必须放行静态资源,否则页面样式、JS 无法加载
- 登录成功后使用
redirect跳转,避免表单重复提交 - 生产环境需配合 Session 超时、CSRF 防护等安全机制
25、员工管理系统:展示员工列表
25.1 需求分析
- 分页 / 列表展示所有员工信息
- 支持跳转到新增、修改、删除操作
- 页面样式优化(Bootstrap 表格)
25.2 业务层实现(模拟数据)
java
@Service
public class EmployeeService {
// 模拟数据库数据
private static final List<Employee> empList = new ArrayList<>();
static {
empList.add(new Employee(1, "张三", "zhangsan@example.com", 1, "技术部", new Date()));
empList.add(new Employee(2, "李四", "lisi@example.com", 1, "人事部", new Date()));
empList.add(new Employee(3, "王五", "wangwu@example.com", 0, "财务部", new Date()));
}
// 查询所有员工
public List<Employee> getAllEmployees() {
return empList;
}
}
25.3 控制器实现
java
@Controller
@RequestMapping("/emp")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/list")
public String list(Model model) {
List<Employee> employees = employeeService.getAllEmployees();
model.addAttribute("emps", employees);
return "emp/list";
}
}
25.4 页面实现(templates/emp/list.html)
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('员工列表')}"></head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" th:href="@{/}">员工管理系统</a>
<a class="btn btn-outline-light" th:href="@{/logout}">退出登录</a>
</nav>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<div class="col-md-2">
<div class="list-group">
<a th:href="@{/}" class="list-group-item">首页</a>
<a th:href="@{/emp/list}" class="list-group-item active">员工管理</a>
</div>
</div>
<!-- 主内容区 -->
<div class="col-md-10">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5>员工列表</h5>
<a th:href="@{/emp/toAdd}" class="btn btn-primary">添加员工</a>
</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>性别</th>
<th>部门</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp : ${emps}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender == 1 ? '男' : '女'}"></td>
<td th:text="${emp.department}"></td>
<td>
<a th:href="@{/emp/toUpdate(id=${emp.id})}" class="btn btn-sm btn-warning">修改</a>
<a th:href="@{/emp/delete(id=${emp.id})}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
26、员工管理系统:增加员工实现
26.1 需求分析
- 新增员工表单页面
- 表单数据校验
- 新增成功后跳转列表页
26.2 控制器实现
java
// 跳转到新增页面
@GetMapping("/toAdd")
public String toAdd(Model model) {
// 传递部门列表用于下拉框
List<String> depts = Arrays.asList("技术部", "人事部", "财务部");
model.addAttribute("depts", depts);
return "emp/add";
}
// 处理新增请求
@PostMapping("/add")
public String add(Employee employee) {
// 模拟新增(实际项目对接数据库)
employee.setId(empList.size() + 1);
empList.add(employee);
return "redirect:/emp/list";
}
26.3 新增页面(templates/emp/add.html)
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('添加员工')}"></head>
<body>
<div class="container">
<h3>添加员工</h3>
<form th:action="@{/emp/add}" method="post">
<div class="form-group">
<label>姓名</label>
<input type="text" class="form-control" name="lastName" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" class="form-control" name="email" required>
</div>
<div class="form-group">
<label>性别</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="gender" value="1" checked>
<label class="form-check-label">男</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>部门</label>
<select class="form-control" name="department">
<option th:each="dept : ${depts}" th:text="${dept}" th:value="${dept}"></option>
</select>
</div>
<div class="form-group">
<label>生日</label>
<input type="date" class="form-control" name="birth" required>
</div>
<button type="submit" class="btn btn-primary">提交</button>
<a th:href="@{/emp/list}" class="btn btn-secondary">取消</a>
</form>
</div>
</body>
</html>
27、员工管理系统:修改员工信息
27.1 需求分析
- 回显员工信息到表单
- 提交修改后更新数据
- 跳转回列表页
27.2 控制器实现
java
// 跳转到修改页面,回显数据
@GetMapping("/toUpdate")
public String toUpdate(Integer id, Model model) {
// 根据ID查询员工
Employee employee = empList.stream()
.filter(e -> e.getId().equals(id))
.findFirst()
.orElse(null);
model.addAttribute("emp", employee);
model.addAttribute("depts", Arrays.asList("技术部", "人事部", "财务部"));
return "emp/update";
}
// 处理修改请求
@PostMapping("/update")
public String update(Employee employee) {
// 模拟更新(实际项目对接数据库)
for (int i = 0; i < empList.size(); i++) {
if (empList.get(i).getId().equals(employee.getId())) {
empList.set(i, employee);
break;
}
}
return "redirect:/emp/list";
}
27.3 修改页面(templates/emp/update.html)
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('修改员工')}"></head>
<body>
<div class="container">
<h3>修改员工</h3>
<form th:action="@{/emp/update}" method="post">
<!-- 隐藏ID用于更新 -->
<input type="hidden" name="id" th:value="${emp.id}">
<div class="form-group">
<label>姓名</label>
<input type="text" class="form-control" name="lastName" th:value="${emp.lastName}" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" class="form-control" name="email" th:value="${emp.email}" required>
</div>
<div class="form-group">
<label>性别</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender == 1}">
<label class="form-check-label">男</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender == 0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>部门</label>
<select class="form-control" name="department">
<option th:each="dept : ${depts}" th:text="${dept}" th:value="${dept}" th:selected="${dept == emp.department}"></option>
</select>
</div>
<div class="form-group">
<label>生日</label>
<input type="date" class="form-control" name="birth" th:value="${#temporals.format(emp.birth, 'yyyy-MM-dd')}" required>
</div>
<button type="submit" class="btn btn-primary">提交修改</button>
<a th:href="@{/emp/list}" class="btn btn-secondary">取消</a>
</form>
</div>
</body>
</html>
28、员工管理系统:删除及 404 处理
28.1 删除功能实现
控制器
java
@GetMapping("/delete")
public String delete(Integer id) {
// 模拟删除(实际项目对接数据库)
empList.removeIf(e -> e.getId().equals(id));
return "redirect:/emp/list";
}
28.2 404 页面定制
SpringBoot 默认会返回白标错误页,可自定义 404 页面提升用户体验:
- 创建 404 页面 :
resources/templates/error/404.html
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('页面不存在')}"></head>
<body>
<div class="container text-center mt-5">
<h1 class="display-1">404</h1>
<h2>页面不存在</h2>
<p>您访问的页面不存在或已被删除</p>
<a th:href="@{/}" class="btn btn-primary">返回首页</a>
</div>
</body>
</html>
- 配置生效 :SpringBoot 会自动识别
error/目录下的状态码页面,无需额外配置。
28.3 全局异常处理(兜底)
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理404异常
@ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle404(NoHandlerFoundException e) {
return new ModelAndView("error/404");
}
// 处理500异常
@ExceptionHandler(Exception.class)
public ModelAndView handle500(Exception e) {
ModelAndView mv = new ModelAndView("error/500");
mv.addObject("msg", e.getMessage());
return mv;
}
}
- 开启 404 异常捕获(application.yml)
yaml
spring:
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false
29、聊聊该如何写一个网站
29.1 网站开发完整流程
| 阶段 | 核心任务 | 关键产出 |
|---|---|---|
| 需求分析 | 明确功能、用户群体、业务流程 | 需求文档、原型图 |
| 技术选型 | 确定前后端技术栈、数据库、中间件 | 技术方案文档 |
| 架构设计 | 分层架构(Controller/Service/Mapper)、数据库设计 | ER 图、接口文档 |
| 环境搭建 | 项目初始化、依赖引入、基础配置 | 可运行的项目骨架 |
| 功能开发 | 按模块开发(登录、列表、CRUD 等) | 可测试的功能模块 |
| 测试优化 | 功能测试、性能优化、安全加固 | 测试报告、优化方案 |
| 部署上线 | 服务器配置、项目打包、运维监控 | 上线文档、监控方案 |
29.2 核心设计原则
- 分层架构:遵循 MVC 分层,解耦业务与视图,便于维护
- 约定大于配置:遵循 SpringBoot 默认规范,减少冗余配置
- 前后端分离 / 服务端渲染:小型项目用 Thymeleaf 服务端渲染,大型项目用前后端分离(Vue/React)
- 安全优先:登录拦截、权限控制、SQL 注入防护、XSS 防护
- 可扩展性:预留扩展接口,便于后续功能迭代
29.3 避坑指南
- 禁止在 Controller 中写业务逻辑,业务逻辑统一放在 Service 层
- 避免硬编码,配置统一放在配置文件中
- 统一异常处理,避免暴露敏感错误信息
- 静态资源统一管理,使用 CDN 加速
30、回顾及这周安排
30.1 核心知识点回顾
- Thymeleaf:模板引擎核心语法、页面复用、国际化
- SpringMVC:自动配置原理、扩展(拦截器、跨域、视图控制器)
- 员工管理系统:登录、拦截器、CRUD、异常处理全流程
- 网站开发方法论:完整开发流程、设计原则
30.2 周学习安排建议
| 时间 | 学习任务 | 目标 |
|---|---|---|
| 第 1-2 天 | 回顾 SpringBoot 基础、Thymeleaf 语法 | 熟练掌握模板渲染、页面开发 |
| 第 3-4 天 | 完成员工管理系统全功能(CRUD、登录、拦截器) | 跑通完整项目,理解业务流程 |
| 第 5 天 | 学习数据层集成(JDBC、Druid) | 掌握 SpringBoot 数据库操作 |
| 第 6-7 天 | 项目优化、部署上线、拓展学习 | 优化项目,学习 MyBatis 等进阶技术 |
31、整合 JDBC 使用
31.1 核心原理
SpringBoot 通过spring-boot-starter-jdbc自动配置JdbcTemplate,简化 JDBC 操作,无需手动创建连接、处理资源释放。
31.2 环境准备
- 依赖引入(pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- 数据库配置(application.yml)
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/emp_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
31.3 JdbcTemplate 核心用法
java
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
@Autowired
private JdbcTemplate jdbcTemplate;
// 查询所有员工
@GetMapping("/list")
public List<Map<String, Object>> list() {
String sql = "select * from employee";
return jdbcTemplate.queryForList(sql);
}
// 新增员工
@PostMapping("/add")
public String add(Employee employee) {
String sql = "insert into employee(last_name, email, gender, department, birth) values(?,?,?,?,?)";
int rows = jdbcTemplate.update(sql,
employee.getLastName(),
employee.getEmail(),
employee.getGender(),
employee.getDepartment(),
employee.getBirth());
return "新增成功,影响行数:" + rows;
}
// 修改员工
@PutMapping("/update")
public String update(Employee employee) {
String sql = "update employee set last_name=?, email=?, gender=?, department=?, birth=? where id=?";
int rows = jdbcTemplate.update(sql,
employee.getLastName(),
employee.getEmail(),
employee.getGender(),
employee.getDepartment(),
employee.getBirth(),
employee.getId());
return "修改成功,影响行数:" + rows;
}
// 删除员工
@DeleteMapping("/delete")
public String delete(Integer id) {
String sql = "delete from employee where id=?";
int rows = jdbcTemplate.update(sql, id);
return "删除成功,影响行数:" + rows;
}
}
31.4 核心优势
- 自动管理数据库连接,无需手动关闭
- 简化 SQL 执行,支持参数绑定
- 内置事务管理,可通过
@Transactional开启事务
32、整合 Druid 数据源
32.1 Druid 核心优势
Druid 是阿里开源的高性能数据库连接池,相比默认 HikariCP,提供监控、防 SQL 注入、连接池管理等企业级特性,适合生产环境使用。
32.2 环境准备
- 依赖引入(pom.xml)
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
- Druid 配置(application.yml)
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/emp_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Druid连接池配置
druid:
# 初始化大小、最小、最大
initial-size: 5
min-idle: 5
max-active: 20
# 连接超时时间
max-wait: 60000
# 间隔多久进行一次检测,检测需要关闭的空闲连接
time-between-eviction-runs-millis: 60000
# 一个连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 监控配置
stat-view-servlet:
enabled: true
login-username: admin
login-password: 123456
allow: 127.0.0.1
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 1000
wall:
enabled: true # 防SQL注入
32.3 Druid 监控面板
- 访问地址:
http://localhost:8080/druid - 输入配置的用户名 / 密码,即可查看:
- 数据源连接池状态
- SQL 执行监控(慢 SQL、执行次数)
- URI 监控、Spring 监控
- SQL 防火墙拦截记录
32.4 核心特性
- 高性能连接池:比 HikariCP 更适合高并发场景
- SQL 监控:实时监控 SQL 执行性能,定位慢 SQL
- SQL 防火墙:防 SQL 注入、防暴力破解
- 日志记录:完整记录 SQL 执行日志,便于排查问题
33、整合 MyBatis 框架
33.1 核心定位
MyBatis 是一款优秀的持久层框架 ,SpringBoot 通过mybatis-spring-boot-starter实现零配置快速整合,替代原生 JDBC,简化数据库操作,支持自定义 SQL、存储过程和高级映射。
33.2 环境准备
- 依赖引入(pom.xml)
xml
<!-- MyBatis SpringBoot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Druid数据源(可选,生产环境推荐) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
- 核心配置(application.yml)
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/emp_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Druid连接池配置(可选)
druid:
initial-size: 5
min-idle: 5
max-active: 20
# MyBatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml # Mapper XML文件路径
type-aliases-package: com.example.emp.entity # 实体类包别名
configuration:
map-underscore-to-camel-case: true # 开启驼峰命名自动映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(开发环境)
33.3 项目结构与代码实现
src/main/java/com/example/emp/
├── entity/ # 实体类
├── mapper/ # Mapper接口
└── service/ # 业务层
src/main/resources/
├── mapper/ # Mapper XML文件
└── application.yml # 配置文件
- 实体类(Employee.java)
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private String department;
private Date birth;
}
- Mapper 接口(EmployeeMapper.java)
java
@Mapper // 标识MyBatis Mapper接口,Spring会自动扫描并生成代理对象
public interface EmployeeMapper {
// 查询所有员工
List<Employee> selectAll();
// 根据ID查询员工
Employee selectById(Integer id);
// 新增员工
int insert(Employee employee);
// 更新员工
int update(Employee employee);
// 删除员工
int deleteById(Integer id);
}
- Mapper XML(resources/mapper/EmployeeMapper.xml)
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.emp.mapper.EmployeeMapper">
<!-- 查询所有 -->
<select id="selectAll" resultType="Employee">
select * from employee
</select>
<!-- 根据ID查询 -->
<select id="selectById" parameterType="int" resultType="Employee">
select * from employee where id = #{id}
</select>
<!-- 新增 -->
<insert id="insert" parameterType="Employee">
insert into employee(last_name, email, gender, department, birth)
values(#{lastName}, #{email}, #{gender}, #{department}, #{birth})
</insert>
<!-- 更新 -->
<update id="update" parameterType="Employee">
update employee set last_name=#{lastName}, email=#{email}, gender=#{gender},
department=#{department}, birth=#{birth} where id=#{id}
</update>
<!-- 删除 -->
<delete id="deleteById" parameterType="int">
delete from employee where id=#{id}
</delete>
</mapper>
- 业务层(EmployeeService.java)
java
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
public List<Employee> getAllEmployees() {
return employeeMapper.selectAll();
}
public Employee getEmployeeById(Integer id) {
return employeeMapper.selectById(id);
}
public int addEmployee(Employee employee) {
return employeeMapper.insert(employee);
}
public int updateEmployee(Employee employee) {
return employeeMapper.update(employee);
}
public int deleteEmployee(Integer id) {
return employeeMapper.deleteById(id);
}
}
33.4 核心注解说明
| 注解 | 作用 |
|---|---|
@Mapper |
标识 Mapper 接口,Spring 自动扫描生成代理 Bean |
@MapperScan |
批量扫描 Mapper 接口,替代每个接口加@Mapper(启动类添加) |
@Param |
多参数查询时,给参数命名,对应 XML 中的#{} |
@Select/@Insert/@Update/@Delete |
注解方式写 SQL,替代 XML 文件 |
34、SpringSecurity 环境搭建
34.1 核心定位
SpringSecurity 是 Spring 官方提供的安全框架,用于实现认证(登录)、授权(权限控制)、防攻击(CSRF、XSS)等安全功能,是 Spring 生态的标准安全解决方案。
34.2 环境准备
- 依赖引入(pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 默认效果引入依赖后,SpringSecurity 会自动拦截所有请求,跳转到默认登录页:
- 默认用户名:
user - 默认密码:控制台随机生成(如
Using generated security password: xxx)
34.3 基础配置类
java
@Configuration
@EnableWebSecurity // 开启SpringSecurity安全配置
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 关闭CSRF(开发环境,生产环境建议开启)
.csrf(csrf -> csrf.disable())
// 配置请求授权规则
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated() // 所有请求需要认证
)
// 配置登录表单
.formLogin(form -> form
.loginPage("/login") // 自定义登录页
.defaultSuccessUrl("/") // 登录成功跳转首页
.permitAll() // 登录页所有人可访问
)
// 配置登出
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
);
return http.build();
}
// 配置用户信息(内存方式,测试用)
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("admin")
.password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
35、用户认证和授权
35.1 核心概念
- 认证(Authentication):验证用户身份(登录),确认「你是谁」
- 授权(Authorization):验证用户权限,确认「你能做什么」
35.2 数据库用户认证(实战)
- 用户实体类(User.java)
java
@Data
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private String role; // 权限角色
// 实现UserDetails接口方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
}
@Override
public String getPassword() { return password; }
@Override
public String getUsername() { return username; }
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
- 自定义 UserDetailsService
java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
return user;
}
}
- 授权规则配置
java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// 权限控制:不同角色访问不同资源
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/static/**").permitAll() // 静态资源放行
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
)
.logout(logout -> logout.permitAll());
return http.build();
}
35.3 常用权限注解
java
// 方法级权限控制
@RestController
@RequestMapping("/admin")
public class AdminController {
// 只有ADMIN角色可访问
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/list")
public String adminList() {
return "管理员列表";
}
}
需在配置类添加
@EnableMethodSecurity开启方法级权限控制
36、注销及权限控制
36.1 注销功能完善
java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.logoutUrl("/logout") // 注销请求路径
.logoutSuccessUrl("/login?logout") // 注销成功跳转页
.invalidateHttpSession(true) // 清除Session
.clearAuthentication(true) // 清除认证信息
.addLogoutHandler(new HeaderLogoutHandler()) // 自定义注销处理器
.permitAll()
);
return http.build();
}
36.2 权限控制进阶
1. 权限不足处理(403 页面定制)
java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling(ex -> ex
.accessDeniedPage("/403") // 权限不足跳转403页
);
}
}
2. 动态权限控制(基于数据库权限表)
- 设计权限表:
sys_permission(权限 ID、权限名称、请求路径、角色) - 自定义权限拦截器,从数据库加载权限规则,动态校验用户权限
36.3 权限控制最佳实践
- 遵循最小权限原则:给用户分配完成工作所需的最小权限
- 分层控制:请求级(SecurityFilterChain)+ 方法级(
@PreAuthorize)+ 数据级(行级权限) - 敏感操作二次验证:如删除、修改操作需二次密码验证
37、记住我及首页定制
37.1 记住我(Remember-Me)功能
实现用户登录后,关闭浏览器再次访问无需重新登录,基于持久化 Token 实现。
- 配置类开启记住我
java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, DataSource dataSource) throws Exception {
http
.rememberMe(remember -> remember
.key("uniqueAndSecretKey") // 加密密钥(自定义)
.tokenRepository(new JdbcTokenRepositoryImpl(dataSource)) // 持久化到数据库
.tokenValiditySeconds(60 * 60 * 24 * 7) // 有效期7天
.userDetailsService(userDetailsService)
);
return http.build();
}
- 登录页添加记住我复选框
html
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember-me" id="remember">
<label class="form-check-label" for="remember">记住我</label>
</div>
37.2 首页定制
根据用户角色动态展示首页内容,实现不同角色看到不同页面:
java
@Controller
public class IndexController {
@GetMapping("/")
public String index(Model model, Authentication authentication) {
// 获取当前登录用户
User user = (User) authentication.getPrincipal();
model.addAttribute("user", user);
// 根据角色跳转不同首页
if (user.getRole().contains("ADMIN")) {
return "admin/index";
} else {
return "user/index";
}
}
}
38、Shiro 快速开始
38.1 核心定位
Apache Shiro 是一款轻量级安全框架,功能与 SpringSecurity 类似,核心优势是简单易用、不依赖 Spring 容器,可独立使用,适合中小型项目。
38.2 核心组件
| 组件 | 作用 |
|---|---|
Subject |
当前用户,所有操作的入口 |
SecurityManager |
Shiro 核心,管理所有 Subject |
Authenticator |
认证器,处理登录 |
Authorizer |
授权器,处理权限 |
Realm |
自定义数据源,连接数据库 |
38.3 快速入门(独立使用)
java
public class ShiroQuickStart {
public static void main(String[] args) {
// 1. 创建SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2. 创建Realm(内存用户)
IniRealm realm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(realm);
// 3. 绑定SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 4. 获取Subject(当前用户)
Subject subject = SecurityUtils.getSubject();
// 5. 登录认证
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
try {
subject.login(token);
System.out.println("登录成功");
// 6. 权限校验
if (subject.hasRole("admin")) {
System.out.println("拥有admin角色");
}
if (subject.isPermitted("user:add")) {
System.out.println("拥有添加用户权限");
}
} catch (AuthenticationException e) {
System.out.println("登录失败:" + e.getMessage());
}
// 7. 注销
subject.logout();
}
}
- shiro.ini 配置文件
ini
[users]
admin = 123456, admin
[roles]
admin = user:add, user:delete
39、Shiro 的 Subject 分析
39.1 Subject 核心本质
Subject 是 Shiro 的核心入口,代表当前用户,封装了用户的认证、授权、会话等操作,所有安全操作都通过 Subject 完成。
39.2 Subject 核心方法
| 方法 | 作用 |
|---|---|
login(Token) |
登录认证 |
logout() |
注销 |
isAuthenticated() |
判断是否已认证 |
hasRole(String role) |
判断是否拥有角色 |
isPermitted(String permission) |
判断是否拥有权限 |
getSession() |
获取用户会话 |
getPrincipal() |
获取用户身份信息 |
39.3 Subject 执行流程
- 用户发起请求,Subject 调用
login()方法 - Subject 将请求委托给 SecurityManager
- SecurityManager 调用 Authenticator 进行认证
- Authenticator 通过 Realm 从数据库获取用户信息,校验密码
- 认证成功后,Subject 保存用户认证状态,后续权限校验直接从 Subject 获取
39.4 Subject 与线程绑定
Shiro 通过ThreadContext将 Subject 绑定到当前线程,可在任意位置通过SecurityUtils.getSubject()获取当前用户,无需传递参数。
40、SpringBoot 整合 Shiro 环境搭建
40.1 环境准备
- 依赖引入(pom.xml)
xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.13.0</version>
</dependency>
40.2 核心配置类
java
@Configuration
public class ShiroConfig {
// 1. 创建Realm(自定义数据源)
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
// 2. 创建SecurityManager
@Bean
public SecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
// 开启RememberMe
securityManager.setRememberMeManager(new CookieRememberMeManager());
return securityManager;
}
// 3. 创建ShiroFilterFactoryBean(拦截请求)
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 配置登录页
shiroFilter.setLoginUrl("/login");
// 登录成功跳转页
shiroFilter.setSuccessUrl("/");
// 权限不足跳转页
shiroFilter.setUnauthorizedUrl("/403");
// 配置拦截规则
Map<String, String> filterMap = new LinkedHashMap<>();
// 静态资源放行
filterMap.put("/static/**", "anon");
// 登录页放行
filterMap.put("/login", "anon");
// 注册页放行
filterMap.put("/register", "anon");
// 管理员路径需要admin角色
filterMap.put("/admin/**", "roles[admin]");
// 所有请求需要认证
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
// 4. 开启Shiro注解(@RequiresRoles、@RequiresPermissions)
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
40.3 自定义 Realm
java
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取当前用户
User user = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 添加角色
info.addRole(user.getRole());
// 添加权限(从数据库查询)
info.addStringPermission("user:add");
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户名
String username = token.getPrincipal().toString();
// 从数据库查询用户
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new UnknownAccountException("用户名不存在");
}
// 校验密码(Shiro自动加密比对)
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
}
40.4 Shiro 常用过滤器
| 过滤器 | 作用 |
|---|---|
anon |
匿名访问,无需认证 |
authc |
需要认证才能访问 |
user |
记住我或认证通过可访问 |
roles[admin] |
需要 admin 角色 |
perms[user:add] |
需要 user:add 权限 |
logout |
注销过滤器 |
41、Shiro 实现登录拦截
41.1 核心原理
Shiro 通过过滤器链(FilterChain) 实现请求拦截,基于配置的拦截规则,对未登录用户强制跳转登录页,实现登录拦截功能。
41.2 核心配置(ShiroConfig.java)
java
@Configuration
public class ShiroConfig {
/**
* Shiro过滤器工厂:核心拦截入口
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 1. 配置登录页:未登录用户自动跳转到此路径
shiroFilter.setLoginUrl("/login");
// 2. 登录成功默认跳转页
shiroFilter.setSuccessUrl("/");
// 3. 权限不足跳转页
shiroFilter.setUnauthorizedUrl("/403");
// 4. 配置拦截规则(LinkedHashMap保证顺序,从上到下匹配)
Map<String, String> filterMap = new LinkedHashMap<>();
// 放行静态资源(anon=匿名访问,无需登录)
filterMap.put("/static/**", "anon");
// 放行登录、注册接口
filterMap.put("/login", "anon");
filterMap.put("/register", "anon");
// 放行Swagger接口文档(开发环境)
filterMap.put("/swagger-ui/**", "anon");
filterMap.put("/v3/api-docs/**", "anon");
// 放行错误页
filterMap.put("/error", "anon");
filterMap.put("/favicon.ico", "anon");
// 拦截所有其他请求(authc=需要登录认证)
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
// 其他Bean(SecurityManager、Realm等,见第40章)
}
41.3 常用过滤器说明
| 过滤器 | 作用 |
|---|---|
anon |
匿名访问,无需登录 |
authc |
需要登录认证才能访问 |
user |
记住我或登录认证均可访问 |
logout |
注销过滤器,访问后自动登出 |
roles[admin] |
需要拥有 admin 角色 |
perms[user:add] |
需要拥有 user:add 权限 |
41.4 注意事项
- 拦截规则严格按顺序匹配,放行规则必须放在拦截规则之前
- 静态资源必须放行,否则页面样式、JS 无法加载
- 生产环境需关闭 Swagger 等开发接口的放行
42、Shiro 实现用户认证
42.1 核心原理
用户认证是验证用户身份的过程,Shiro 通过Realm连接数据库,校验用户名 / 密码,认证成功后将用户信息存入 Subject,后续请求可直接获取用户状态。
42.2 自定义 Realm(核心)
java
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
/**
* 认证方法:处理登录请求
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1. 从token中获取用户名(前端传入)
String username = (String) token.getPrincipal();
// 2. 从数据库查询用户信息
User user = userMapper.selectByUsername(username);
if (user == null) {
// 用户名不存在,抛出异常
throw new UnknownAccountException("用户名不存在");
}
// 3. 封装认证信息(Shiro自动比对密码,无需手动校验)
// 参数1:用户信息(存入Subject),参数2:数据库密码,参数3:Realm名称
return new SimpleAuthenticationInfo(
user,
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), // 加盐(密码加密用)
getName()
);
}
/**
* 授权方法:处理权限校验(见第44章)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 配置密码加密器(BCrypt加密)
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("SHA-256"); // 加密算法
matcher.setHashIterations(1024); // 加密次数
matcher.setStoredCredentialsHexEncoded(true);
super.setCredentialsMatcher(matcher);
}
}
42.3 登录 Controller 实现
java
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage() {
return "login";
}
@PostMapping("/login")
@ResponseBody
public String login(String username, String password) {
// 1. 获取Subject(当前用户)
Subject subject = SecurityUtils.getSubject();
// 2. 封装登录令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 3. 执行登录(Shiro自动调用Realm的认证方法)
try {
subject.login(token);
return "登录成功";
} catch (UnknownAccountException e) {
return "用户名不存在";
} catch (IncorrectCredentialsException e) {
return "密码错误";
} catch (AuthenticationException e) {
return "登录失败:" + e.getMessage();
}
}
@GetMapping("/logout")
public String logout() {
SecurityUtils.getSubject().logout();
return "redirect:/login";
}
}
42.4 认证流程总结
- 前端提交用户名 / 密码 → Controller 封装
UsernamePasswordToken - 调用
subject.login(token)→ Shiro 委托给 SecurityManager - SecurityManager 调用 Realm 的
doGetAuthenticationInfo方法 - Realm 从数据库查询用户,校验密码 → 认证成功,用户信息存入 Subject
- 后续请求通过
SecurityUtils.getSubject()获取用户状态
43、Shiro 整合 MyBatis
43.1 核心目标
将 Shiro 的用户认证、授权与 MyBatis 结合,从数据库动态加载用户、角色、权限数据,替代硬编码的内存用户,实现企业级动态权限控制。
43.2 数据库表设计
sql
-- 用户表
CREATE TABLE `sys_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码(加密后)',
`salt` varchar(50) NOT NULL COMMENT '盐值',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0禁用 1启用',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色表
CREATE TABLE `sys_role` (
`id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_desc` varchar(100) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 权限表
CREATE TABLE `sys_permission` (
`id` int NOT NULL AUTO_INCREMENT,
`perm_name` varchar(50) NOT NULL COMMENT '权限名称',
`perm_code` varchar(100) NOT NULL COMMENT '权限标识(如user:add)',
`url` varchar(200) DEFAULT NULL COMMENT '请求路径',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户角色关联表
CREATE TABLE `sys_user_role` (
`user_id` int NOT NULL,
`role_id` int NOT NULL,
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色权限关联表
CREATE TABLE `sys_role_perm` (
`role_id` int NOT NULL,
`perm_id` int NOT NULL,
PRIMARY KEY (`role_id`,`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
43.3 MyBatis Mapper 实现
- UserMapper.java
java
@Mapper
public interface UserMapper {
// 根据用户名查询用户
User selectByUsername(String username);
// 根据用户ID查询角色列表
List<String> selectRolesByUserId(Integer userId);
// 根据用户ID查询权限列表
List<String> selectPermissionsByUserId(Integer userId);
}
- UserMapper.xml
xml
<mapper namespace="com.example.emp.mapper.UserMapper">
<!-- 根据用户名查询用户 -->
<select id="selectByUsername" parameterType="string" resultType="User">
select * from sys_user where username = #{username}
</select>
<!-- 根据用户ID查询角色 -->
<select id="selectRolesByUserId" parameterType="int" resultType="string">
select r.role_name from sys_role r
join sys_user_role ur on r.id = ur.role_id
where ur.user_id = #{userId}
</select>
<!-- 根据用户ID查询权限 -->
<select id="selectPermissionsByUserId" parameterType="int" resultType="string">
select p.perm_code from sys_permission p
join sys_role_perm rp on p.id = rp.perm_id
join sys_user_role ur on rp.role_id = ur.role_id
where ur.user_id = #{userId}
</select>
</mapper>
43.4 改造 Realm(动态加载权限)
java
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = userMapper.selectByUsername(username);
if (user == null) throw new UnknownAccountException("用户名不存在");
if (user.getStatus() == 0) throw new LockedAccountException("账号已被禁用");
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取当前登录用户
User user = (User) principals.getPrimaryPrincipal();
// 从数据库查询用户角色和权限
List<String> roles = userMapper.selectRolesByUserId(user.getId());
List<String> perms = userMapper.selectPermissionsByUserId(user.getId());
// 封装授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(new HashSet<>(roles));
info.setStringPermissions(new HashSet<>(perms));
return info;
}
44、Shiro 请求授权实现
44.1 核心原理
授权是验证用户是否拥有访问资源权限的过程,Shiro 支持URL 拦截授权 和方法注解授权两种方式,实现细粒度的权限控制。
44.2 方式 1:URL 拦截授权(ShiroConfig)
java
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, String> filterMap = new LinkedHashMap<>();
// 放行静态资源、登录页
filterMap.put("/static/**", "anon");
filterMap.put("/login", "anon");
// 角色授权:/admin/** 必须拥有admin角色
filterMap.put("/admin/**", "roles[admin]");
// 权限授权:/emp/add 必须拥有user:add权限
filterMap.put("/emp/add", "perms[user:add]");
filterMap.put("/emp/delete", "perms[user:delete]");
// 所有请求需要登录
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
44.3 方式 2:方法注解授权
- 开启 Shiro 注解(ShiroConfig)
java
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
- Controller 方法加注解
java
@RestController
@RequestMapping("/emp")
public class EmployeeController {
// 必须拥有user:add权限才能访问
@RequiresPermissions("user:add")
@PostMapping("/add")
public String addEmployee(@RequestBody Employee employee) {
return "添加成功";
}
// 必须拥有admin角色才能访问
@RequiresRoles("admin")
@DeleteMapping("/delete/{id}")
public String deleteEmployee(@PathVariable Integer id) {
return "删除成功";
}
// 登录即可访问
@RequiresUser
@GetMapping("/list")
public List<Employee> list() {
return employeeService.getAllEmployees();
}
}
44.4 方式 3:代码中动态校验
java
@GetMapping("/detail/{id}")
public String detail(@PathVariable Integer id) {
Subject subject = SecurityUtils.getSubject();
// 动态校验权限
if (!subject.isPermitted("user:detail")) {
return "无权限访问";
}
return employeeService.getById(id);
}
44.5 权限不足处理
java
@ControllerAdvice
public class ShiroExceptionHandler {
// 处理权限不足异常
@ExceptionHandler(UnauthorizedException.class)
public ModelAndView handleUnauthorized(UnauthorizedException e) {
ModelAndView mv = new ModelAndView("error/403");
mv.addObject("msg", "您无权限访问此资源");
return mv;
}
// 处理角色不足异常
@ExceptionHandler(AuthorizationException.class)
public ModelAndView handleAuthorization(AuthorizationException e) {
ModelAndView mv = new ModelAndView("error/403");
mv.addObject("msg", "您的角色无权限访问此资源");
return mv;
}
}
45、Shiro 整合 Thymeleaf
45.1 核心目标
在 Thymeleaf 页面中,通过 Shiro 标签实现权限控制的页面元素渲染,如根据用户角色显示 / 隐藏按钮、菜单,实现前端权限控制。
45.2 环境准备
- 依赖引入(pom.xml)
xml
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
- 配置方言(Thymeleaf 配置)
java
@Configuration
public class ThymeleafConfig {
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
45.3 页面标签使用
- 页面引入命名空间
html
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
- 常用标签示例
html
<!-- 已登录用户显示 -->
<div shiro:authenticated>
<p>欢迎您,<span shiro:principal></span></p>
<a th:href="@{/logout}">退出登录</a>
</div>
<!-- 未登录用户显示 -->
<div shiro:notAuthenticated>
<a th:href="@{/login}">请登录</a>
</div>
<!-- 拥有admin角色显示 -->
<a shiro:hasRole="admin" th:href="@{/admin/list}">管理员后台</a>
<!-- 拥有user:add权限显示 -->
<button shiro:hasPermission="user:add" class="btn btn-primary">添加员工</button>
<!-- 没有权限时显示 -->
<button shiro:lacksPermission="user:add" class="btn btn-secondary" disabled>添加员工</button>
<!-- 任意角色满足显示 -->
<div shiro:hasAnyRoles="admin,manager">
<p>您是管理员/经理</p>
</div>
<!-- 获取当前用户名 -->
<p>当前用户:<span shiro:principal></span></p>
45.4 注意事项
- 标签仅控制页面元素的显示 / 隐藏,后端必须同时做权限校验(防止绕过前端直接访问接口)
- 生产环境需确保 Shiro 过滤器和注解双重校验,保障安全
46、鸡汤分析开源项目
46.1 核心目标
通过分析开源项目,学习优秀项目的架构设计、代码规范、技术选型,提升自身开发能力,快速落地企业级项目。
46.2 开源项目分析维度
| 维度 | 分析重点 | 学习价值 |
|---|---|---|
| 项目架构 | 分层结构(Controller/Service/Mapper)、模块划分、技术栈选型 | 学习企业级项目标准架构 |
| 代码规范 | 命名规范、注释规范、异常处理、日志规范 | 提升代码可读性、可维护性 |
| 安全设计 | 认证授权、防攻击(CSRF/XSS)、数据加密 | 学习企业级安全最佳实践 |
| 性能优化 | 数据库优化、缓存设计、异步处理 | 提升项目性能、并发能力 |
| 文档与测试 | 接口文档、单元测试、部署文档 | 学习项目工程化管理 |
46.3 推荐学习的开源项目
- SpringBoot-Learning:SpringBoot 入门实战项目,覆盖基础功能
- RuoYi-Vue:基于 SpringBoot+Vue 的后台管理系统,集成权限管理、代码生成
- MyBatis-Plus:MyBatis 增强工具,学习持久层优化
- Spring-Cloud-Alibaba:微服务开源项目,学习微服务架构
46.4 学习方法
- 跑通项目:本地部署运行,熟悉项目功能
- 阅读源码:从入口类(启动类)开始,梳理核心流程
- 模仿开发:基于开源项目改造,实现自己的业务功能
- 总结沉淀:整理项目架构、技术栈、最佳实践,形成自己的知识体系
47、Swagger 介绍及集成
47.1 核心定义
Swagger 是一款RESTful 接口文档工具,通过注解自动生成接口文档,支持在线调试,解决前后端接口文档不一致、维护成本高的问题,是 Java 后端开发的标准工具。
47.2 环境准备(SpringDoc/Swagger3,SpringBoot3.x 推荐)
- 依赖引入(pom.xml)
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
若为 SpringBoot2.x,使用
springdoc-openapi-ui1.6.x 版本
- 基础配置(application.yml)
yaml
springdoc:
api-docs:
enabled: true # 开启接口文档
path: /v3/api-docs # 接口JSON路径
swagger-ui:
enabled: true # 开启SwaggerUI页面
path: /swagger-ui.html # UI访问路径
47.3 访问地址
- SwaggerUI 页面:
http://localhost:8080/swagger-ui.html - 接口 JSON:
http://localhost:8080/v3/api-docs
47.4 核心注解说明
| 注解 | 作用 |
|---|---|
@OpenAPIDefinition |
全局文档配置(标题、版本、描述) |
@Operation |
接口方法描述 |
@Parameter |
接口参数描述 |
@ApiResponse |
接口响应描述 |
@Schema |
实体类 / 属性描述 |
@Tag |
接口分组标签 |
48、配置 Swagger 信息
48.1 全局配置类
java
@Configuration
@OpenAPIDefinition(
info = @Info(
title = "员工管理系统API文档",
version = "1.0.0",
description = "基于SpringBoot+Shiro+MyBatis的员工管理系统接口文档",
contact = @Contact(
name = "开发者",
email = "developer@example.com",
url = "https://www.example.com"
),
license = @License(
name = "Apache 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0"
)
),
servers = @Server(url = "http://localhost:8080", description = "本地开发环境")
)
public class SwaggerConfig {
/**
* 配置接口扫描规则(仅扫描指定包下的接口)
*/
@Bean
public GroupedOpenApi employeeApi() {
return GroupedOpenApi.builder()
.group("员工管理模块")
.pathsToMatch("/emp/**")
.packagesToScan("com.example.emp.controller")
.build();
}
/**
* 配置用户模块接口
*/
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("用户管理模块")
.pathsToMatch("/user/**", "/admin/**")
.build();
}
/**
* 生产环境关闭Swagger(通过@Profile指定环境)
*/
@Bean
@Profile("!prod")
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("全部接口")
.pathsToMatch("/**")
.build();
}
}
48.2 接口注解使用
java
@RestController
@RequestMapping("/emp")
@Tag(name = "员工管理接口", description = "员工CRUD相关接口")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@Operation(summary = "查询所有员工", description = "获取员工列表,支持分页")
@ApiResponse(responseCode = "200", description = "查询成功")
@GetMapping("/list")
public List<Employee> list() {
return employeeService.getAllEmployees();
}
@Operation(summary = "根据ID查询员工", description = "通过员工ID查询详细信息")
@ApiResponse(responseCode = "200", description = "查询成功")
@ApiResponse(responseCode = "404", description = "员工不存在")
@GetMapping("/{id}")
public Employee getById(
@Parameter(description = "员工ID", required = true) @PathVariable Integer id) {
return employeeService.getById(id);
}
@Operation(summary = "添加员工", description = "新增员工信息")
@ApiResponse(responseCode = "200", description = "添加成功")
@PostMapping("/add")
public String add(
@Parameter(description = "员工信息", required = true) @RequestBody Employee employee) {
employeeService.add(employee);
return "添加成功";
}
}
48.3 实体类注解使用
java
@Data
@Schema(description = "员工实体类")
public class Employee {
@Schema(description = "员工ID", example = "1")
private Integer id;
@Schema(description = "员工姓名", example = "张三")
private String lastName;
@Schema(description = "员工邮箱", example = "zhangsan@example.com")
private String email;
@Schema(description = "性别:1男 0女", example = "1")
private Integer gender;
@Schema(description = "部门", example = "技术部")
private String department;
@Schema(description = "生日", example = "2000-01-01")
private Date birth;
}
48.4 生产环境注意事项
- 生产环境必须关闭 Swagger,避免接口泄露:通过
@Profile("!prod)或配置文件springdoc.api-docs.enabled=false关闭 - 敏感接口(如登录、支付)需隐藏,避免泄露
- 可配置认证,仅授权用户可访问 Swagger 文档
49、配置扫描接口及开关
49.1 核心目标
通过配置灵活控制 Swagger 接口扫描范围、文档开关,实现开发环境开启、生产环境关闭,避免接口泄露,同时精准过滤敏感接口。
49.2 接口扫描配置
方式 1:按包扫描(推荐)
java
@Configuration
public class SwaggerConfig {
/**
* 仅扫描指定包下的接口,过滤无关接口
*/
@Bean
public GroupedOpenApi api() {
return GroupedOpenApi.builder()
.group("员工管理系统API")
// 扫描指定包
.packagesToScan("com.example.emp.controller")
// 排除指定包(如测试接口)
.packagesToExclude("com.example.emp.test")
.build();
}
}
方式 2:按路径匹配
java
@Bean
public GroupedOpenApi api() {
return GroupedOpenApi.builder()
.group("员工管理系统API")
// 匹配指定路径
.pathsToMatch("/emp/**", "/user/**")
// 排除敏感路径(如登录、支付)
.pathsToExclude("/login", "/pay/**")
.build();
}
49.3 文档开关配置(环境隔离)
方式 1:通过 @Profile 注解(推荐)
java
@Configuration
@EnableOpenApi // 仅在指定环境开启Swagger
@Profile({"dev", "test"}) // 仅开发、测试环境开启,生产环境自动关闭
public class SwaggerConfig {
// 配置类内容
}
方式 2:配置文件动态控制
yaml
# application-dev.yml(开发环境)
springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: true
# application-prod.yml(生产环境)
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
方式 3:代码动态判断
java
@Bean
public GroupedOpenApi api(@Value("${springdoc.api-docs.enabled:true}") boolean enabled) {
return GroupedOpenApi.builder()
.group("API文档")
.displayOperationId(enabled)
.build();
}
49.4 敏感接口过滤
java
@Bean
public GroupedOpenApi api() {
return GroupedOpenApi.builder()
.group("API文档")
// 排除登录、注册、支付等敏感接口
.pathsToExclude(
"/login", "/register", "/pay/**",
"/admin/**" // 管理员接口
)
.build();
}
50、分组和接口注释及小结
50.1 接口分组配置
按业务模块对接口分组,提升文档可读性,方便前后端协作:
java
@Configuration
public class SwaggerConfig {
/**
* 员工管理模块分组
*/
@Bean
public GroupedOpenApi empApi() {
return GroupedOpenApi.builder()
.group("员工管理模块")
.pathsToMatch("/emp/**")
.packagesToScan("com.example.emp.controller.emp")
.build();
}
/**
* 用户管理模块分组
*/
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("用户管理模块")
.pathsToMatch("/user/**", "/admin/**")
.packagesToScan("com.example.emp.controller.user")
.build();
}
/**
* 系统模块分组
*/
@Bean
public GroupedOpenApi systemApi() {
return GroupedOpenApi.builder()
.group("系统管理模块")
.pathsToMatch("/system/**", "/log/**")
.build();
}
}
50.2 接口注释规范
1. 类 / 接口注释
java
@RestController
@RequestMapping("/emp")
@Tag(
name = "员工管理接口",
description = "员工CRUD、列表查询、信息修改等相关接口"
)
public class EmployeeController {
// 接口方法
}
2. 方法 / 参数注释
java
@Operation(
summary = "分页查询员工列表",
description = "根据页码、每页条数分页查询员工,支持按姓名模糊搜索"
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "401", description = "未登录"),
@ApiResponse(responseCode = "403", description = "无权限")
})
@GetMapping("/list")
public R<PageResult<Employee>> list(
@Parameter(description = "页码", example = "1", required = true)
@RequestParam(defaultValue = "1") Integer pageNum,
@Parameter(description = "每页条数", example = "10", required = true)
@RequestParam(defaultValue = "10") Integer pageSize,
@Parameter(description = "搜索关键词", example = "张三")
@RequestParam(required = false) String keyword
) {
// 业务逻辑
}
3. 实体类 / 属性注释
java
@Data
@Schema(description = "员工实体类")
public class Employee {
@Schema(description = "员工ID", example = "1", required = true)
private Integer id;
@Schema(description = "员工姓名", example = "张三", minLength = 2, maxLength = 10)
private String lastName;
@Schema(description = "员工邮箱", example = "zhangsan@example.com", format = "email")
private String email;
}
50.3 Swagger 核心小结
| 核心特性 | 作用 |
|---|---|
| 自动生成文档 | 基于注解自动生成接口文档,无需手动维护 |
| 在线调试 | 直接在文档页发送请求,测试接口 |
| 分组管理 | 按业务模块分组,提升可读性 |
| 环境隔离 | 开发 / 测试环境开启,生产环境关闭,保障安全 |
| 多格式导出 | 支持导出 JSON/HTML 格式文档,方便协作 |
51、异步任务
51.1 核心原理
SpringBoot 通过@Async注解实现异步任务,将耗时操作(如文件上传、短信发送)放入子线程执行,主线程快速返回,提升接口响应速度。
51.2 快速入门
1. 开启异步支持(启动类添加)
java
@SpringBootApplication
@EnableAsync // 开启异步任务支持
public class EmpApplication {
public static void main(String[] args) {
SpringApplication.run(EmpApplication.class, args);
}
}
2. 异步方法实现
java
@Service
public class AsyncService {
/**
* 异步方法:耗时操作
*/
@Async // 标记为异步任务,Spring自动创建子线程执行
public void asyncMethod() throws InterruptedException {
System.out.println("异步任务开始,线程名:" + Thread.currentThread().getName());
// 模拟耗时操作(如文件处理、第三方接口调用)
Thread.sleep(5000);
System.out.println("异步任务结束,线程名:" + Thread.currentThread().getName());
}
}
3. Controller 调用
java
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/async")
public String asyncTest() throws InterruptedException {
System.out.println("主线程开始,线程名:" + Thread.currentThread().getName());
// 调用异步方法,主线程不阻塞
asyncService.asyncMethod();
System.out.println("主线程结束,线程名:" + Thread.currentThread().getName());
return "异步任务已提交";
}
}
51.3 自定义线程池(推荐)
默认线程池为SimpleAsyncTaskExecutor,无限制创建线程,生产环境需自定义线程池:
java
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(20);
// 队列容量
executor.setQueueCapacity(100);
// 线程名前缀
executor.setThreadNamePrefix("async-emp-");
// 线程存活时间
executor.setKeepAliveSeconds(60);
// 等待任务完成再关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
/**
* 异步任务异常处理
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.out.println("异步任务异常:" + ex.getMessage());
// 记录日志、告警
};
}
}
51.4 异步任务带返回值
java
@Async
public Future<String> asyncTaskWithResult() throws InterruptedException {
Thread.sleep(3000);
return new AsyncResult<>("异步任务执行完成");
}
// 调用
Future<String> future = asyncService.asyncTaskWithResult();
String result = future.get(); // 阻塞获取结果
52、邮件任务
52.1 核心目标
SpringBoot 通过spring-boot-starter-mail快速集成邮件功能,支持普通邮件、HTML 邮件、带附件邮件、模板邮件,适用于注册验证码、通知、告警等场景。
52.2 环境准备
1. 依赖引入(pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2. 配置文件(application.yml)
yaml
spring:
mail:
# 邮件服务器SMTP地址(QQ邮箱为smtp.qq.com,163为smtp.163.com)
host: smtp.qq.com
# 端口(QQ邮箱465/587,163为25/465)
port: 465
# 发件人邮箱
username: xxx@qq.com
# 授权码(非邮箱密码,需在邮箱设置中开启SMTP获取)
password: xxxxxxxxxxxxxxxx
# 编码
default-encoding: UTF-8
# 开启SSL
properties:
mail:
smtp:
ssl:
enable: true
auth: true
52.3 邮件发送实现
1. 普通文本邮件
java
@Service
public class MailService {
@Autowired
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String from;
/**
* 发送普通文本邮件
*/
public void sendSimpleMail(String to, String subject, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from); // 发件人
message.setTo(to); // 收件人
message.setSubject(subject); // 主题
message.setText(content); // 内容
javaMailSender.send(message);
}
}
2. HTML 邮件(带样式)
java
public void sendHtmlMail(String to, String subject, String htmlContent) throws MessagingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
// 第二个参数true表示启用HTML
helper.setText(htmlContent, true);
javaMailSender.send(message);
}
3. 带附件邮件
java
public void sendAttachmentMail(String to, String subject, String content, String filePath) throws MessagingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
// 添加附件
File file = new File(filePath);
helper.addAttachment(file.getName(), file);
javaMailSender.send(message);
}
4. 模板邮件(Thymeleaf)
java
@Autowired
private TemplateEngine templateEngine;
public void sendTemplateMail(String to, String subject, Map<String, Object> params) throws MessagingException {
// 加载Thymeleaf模板
Context context = new Context();
context.setVariables(params);
String htmlContent = templateEngine.process("email/register", context);
// 发送HTML邮件
sendHtmlMail(to, subject, htmlContent);
}
53、定时执行任务
53.1 核心原理
SpringBoot 通过@Scheduled注解实现定时任务,支持固定频率、固定延迟、Cron 表达式三种方式,适用于数据统计、日志清理、定时通知等场景。
53.2 快速入门
1. 开启定时任务(启动类添加)
java
@SpringBootApplication
@EnableScheduling // 开启定时任务支持
public class EmpApplication {
public static void main(String[] args) {
SpringApplication.run(EmpApplication.class, args);
}
}
2. 定时任务实现
java
@Service
public class ScheduledService {
/**
* 固定频率执行:每隔5秒执行一次(从上一次开始时间计算)
*/
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
System.out.println("固定频率任务执行,时间:" + new Date());
}
/**
* 固定延迟执行:上一次执行完成后,延迟3秒执行
*/
@Scheduled(fixedDelay = 3000)
public void fixedDelayTask() {
System.out.println("固定延迟任务执行,时间:" + new Date());
}
/**
* Cron表达式:每天凌晨1点执行
*/
@Scheduled(cron = "0 0 1 * * ?")
public void cronTask() {
System.out.println("Cron定时任务执行,时间:" + new Date());
}
}
53.3 Cron 表达式详解
Cron 表达式格式:秒 分 时 日 月 周 年(可选)
| 字段 | 允许值 | 允许特殊字符 |
|---|---|---|
| 秒 | 0-59 | , - * / |
| 分 | 0-59 | , - * / |
| 时 | 0-23 | , - * / |
| 日 | 1-31 | , - * ? / L W |
| 月 | 1-12 或 JAN-DEC | , - * / |
| 周 | 1-7 或 SUN-SAT(1 = 周日) | , - * ? / L # |
常用 Cron 表达式示例
0 0/5 * * * ?:每隔 5 分钟执行0 0 8,12,18 * * ?:每天 8 点、12 点、18 点执行0 0 1 ? * MON-FRI:工作日凌晨 1 点执行0 0 0 1 * ?:每月 1 号凌晨执行
53.4 自定义线程池(多线程定时任务)
默认定时任务单线程执行,任务阻塞会影响其他任务,需自定义线程池:
java
@Configuration
@EnableScheduling
public class ScheduledConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 线程池大小
scheduler.setThreadNamePrefix("scheduled-emp-");
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}
53.5 动态定时任务
基于数据库配置定时任务,实现动态修改执行时间,无需重启项目:
java
@Service
public class DynamicScheduledService implements SchedulingConfigurer {
@Autowired
private TaskMapper taskMapper;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
// 任务逻辑
() -> System.out.println("动态定时任务执行"),
// 动态触发时间
triggerContext -> {
// 从数据库查询Cron表达式
String cron = taskMapper.getCronById(1);
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
);
}
}
54、SpringBoot 集成 Redis
54.1 核心定位
Redis 是高性能内存数据库 ,SpringBoot 通过spring-boot-starter-data-redis快速集成,用于缓存、分布式锁、会话共享、计数器等场景,提升系统性能。
54.2 环境准备
1. 依赖引入(pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池(SpringBoot2.x+默认集成) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2. 配置文件(application.yml)
yaml
spring:
redis:
# Redis地址
host: localhost
# 端口
port: 6379
# 密码(无密码则省略)
password: 123456
# 数据库(0-15,默认0)
database: 0
# 连接超时时间
timeout: 3000ms
# 连接池配置
lettuce:
pool:
# 最大连接数
max-active: 8
# 最大空闲连接
max-idle: 8
# 最小空闲连接
min-idle: 0
# 最大等待时间
max-wait: -1ms
54.3 RedisTemplate 常用操作
SpringBoot 自动配置StringRedisTemplate(操作字符串)和RedisTemplate<Object, Object>(操作对象):
java
@RestController
public class RedisController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/**
* 字符串操作
*/
@GetMapping("/string")
public String stringTest() {
// 写入字符串
stringRedisTemplate.opsForValue().set("username", "admin");
// 读取字符串
String username = stringRedisTemplate.opsForValue().get("username");
// 设置过期时间
stringRedisTemplate.expire("username", 1, TimeUnit.HOURS);
return username;
}
/**
* 对象操作
*/
@GetMapping("/object")
public Employee objectTest() {
Employee emp = new Employee(1, "张三", "zhangsan@example.com", 1, "技术部", new Date());
// 写入对象
redisTemplate.opsForValue().set("emp:1", emp);
// 读取对象
Employee empResult = (Employee) redisTemplate.opsForValue().get("emp:1");
return empResult;
}
/**
* 哈希操作
*/
@GetMapping("/hash")
public Map<Object, Object> hashTest() {
// 写入哈希
stringRedisTemplate.opsForHash().put("user:1", "name", "张三");
stringRedisTemplate.opsForHash().put("user:1", "age", "20");
// 读取哈希
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("user:1");
return userMap;
}
/**
* 列表操作
*/
@GetMapping("/list")
public List<String> listTest() {
// 左推写入列表
stringRedisTemplate.opsForList().leftPush("list:1", "a");
stringRedisTemplate.opsForList().leftPush("list:1", "b");
// 右弹出读取
String value = stringRedisTemplate.opsForList().rightPop("list:1");
// 获取列表全部
List<String> list = stringRedisTemplate.opsForList().range("list:1", 0, -1);
return list;
}
}
55、自定义 RedisTemplate
55.1 核心问题
默认RedisTemplate使用 JDK 序列化,存储对象时会出现乱码,且可读性差,需自定义序列化方式(推荐 JSON 序列化)。
55.2 自定义 RedisTemplate 配置
java
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 配置JSON序列化器(Jackson2JsonRedisSerializer)
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 解决日期序列化问题
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jsonSerializer.setObjectMapper(objectMapper);
// 配置String序列化器(用于key)
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// 设置key和hashKey的序列化方式为String
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// 设置value和hashValue的序列化方式为JSON
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
// 初始化RedisTemplate
template.afterPropertiesSet();
return template;
}
}
55.3 序列化方式对比
| 序列化方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JDK 序列化 | 无需额外配置 | 乱码、可读性差、体积大 | 快速测试 |
| JSON 序列化 | 可读性好、体积小、跨语言 | 需配置 | 生产环境推荐 |
| String 序列化 | 简单、可读性好 | 仅支持字符串 | 字符串类型 key/value |
| Kryo 序列化 | 体积小、速度快 | 需额外依赖、兼容性一般 | 高性能场景 |
55.4 常用工具类封装
java
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 写入缓存
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 写入缓存并设置过期时间
*/
public boolean set(String key, Object value, long time, TimeUnit unit) {
try {
redisTemplate.opsForValue().set(key, value, time, unit);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 读取缓存
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 删除缓存
*/
public boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 分布式锁(加锁)
*/
public boolean tryLock(String key, String value, long expireTime) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS));
}
/**
* 分布式锁(解锁)
*/
public boolean unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
return result != null && result == 1;
}
}
56、分布式系统理论
56.1 核心定义
分布式系统是由多个独立节点组成的系统,节点通过网络通信协作,共同完成一个目标,核心目标是提升系统性能、可用性、可扩展性。
56.2 核心特性
| 特性 | 定义 |
|---|---|
| 可扩展性 | 系统可通过增加节点线性提升性能,支持水平扩展 |
| 高可用性 | 系统部分节点故障时,仍能正常提供服务 |
| 一致性 | 所有节点数据保持一致(CAP 定理核心) |
| 分区容错性 | 网络分区时,系统仍能正常工作 |
| 容错性 | 系统可自动处理节点故障、网络异常 |
56.3 核心理论
1. CAP 定理
分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance) 三者不可兼得,最多同时满足两个:
- CA:放弃分区容错,仅适用于单体系统
- CP:放弃可用性,保证一致性(如 Zookeeper、分布式锁)
- AP:放弃强一致性,保证可用性(如 Redis 集群、最终一致性)
2. BASE 理论
CAP 的落地实践,核心是基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistent),适用于分布式系统:
- 基本可用:系统出现故障时,允许部分功能不可用
- 软状态:系统中间状态不影响最终结果
- 最终一致性:数据最终会达到一致,无需实时一致
3. 分布式锁
解决分布式系统中并发资源竞争问题,核心要求:
- 互斥性:同一时间只有一个节点持有锁
- 安全性:锁只能被持有者释放
- 可用性:锁超时自动释放,避免死锁
- 高性能:加锁 / 解锁操作高效
4. 分布式事务
解决分布式系统中数据一致性问题,常见方案:
- 2PC(两阶段提交):强一致性,性能差,适合低并发场景
- TCC(Try-Confirm-Cancel):柔性事务,需业务改造
- 最终一致性:基于消息队列,保证数据最终一致(如 RocketMQ 事务消息)
56.4 分布式系统核心问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 服务间通信 | RESTful API、RPC(Dubbo、Feign)、消息队列(RabbitMQ、RocketMQ) |
| 服务注册发现 | Nacos、Eureka、Consul |
| 负载均衡 | Nginx、Ribbon、Spring Cloud LoadBalancer |
| 服务熔断降级 | Hystrix、Sentinel |
| 分布式缓存 | Redis 集群、Codis |
| 分布式日志 | ELK、SkyWalking |
| 分布式追踪 | Zipkin、Jaeger |
56.5 SpringBoot 与分布式
SpringBoot 是分布式系统的基础开发框架,配合 Spring Cloud 可快速构建微服务架构,核心组件:
- Spring Cloud Alibaba:Nacos(注册发现 / 配置中心)、Sentinel(熔断降级)、Seata(分布式事务)
- Spring Cloud Netflix:Eureka、Hystrix、Zuul
- Spring Cloud Gateway:API 网关
57、什么是 RPC
57.1 核心定义
RPC(Remote Procedure Call,远程过程调用)是一种分布式系统通信协议,允许程序像调用本地方法一样调用远程服务器的方法,屏蔽网络通信细节,让开发者无需关注底层网络实现,专注于业务逻辑。
57.2 RPC 核心原理
RPC 的核心是 **「透明化远程调用」**,完整流程如下:
- 客户端调用:客户端像调用本地方法一样调用远程服务,传入参数
- 客户端 Stub(存根):将方法名、参数等信息序列化,封装成网络请求报文
- 网络传输:通过 TCP/IP 等协议将请求发送到服务端
- 服务端 Stub(存根):接收请求,反序列化报文,解析出方法名和参数
- 服务端执行:调用本地服务方法,执行业务逻辑,返回结果
- 服务端 Stub 序列化:将返回结果序列化,封装成响应报文
- 网络回传:将响应发送回客户端
- 客户端 Stub 反序列化:解析响应,将结果返回给调用者
57.3 RPC 核心组件
| 组件 | 作用 |
|---|---|
| Stub(存根) | 封装序列化 / 反序列化,屏蔽网络细节,让调用像本地方法 |
| 序列化框架 | 将对象转换为二进制流,常用:Hessian、Protobuf、JSON |
| 网络传输 | 底层通信协议,常用:TCP(长连接)、HTTP/2 |
| 服务注册发现 | 动态管理服务地址,实现服务上下线感知 |
| 负载均衡 | 客户端 / 服务端负载均衡,提升系统可用性 |
| 熔断降级 | 服务故障时自动降级,避免雪崩 |
57.4 RPC vs HTTP
| 对比维度 | RPC | HTTP(RESTful) |
|---|---|---|
| 性能 | 基于 TCP 长连接,序列化高效,性能高 | 基于 HTTP 协议,开销大,性能较低 |
| 易用性 | 调用像本地方法,开发效率高 | 需手动处理请求 / 响应,开发成本高 |
| 通用性 | 跨语言支持弱(部分框架) | 跨语言、跨平台,通用性强 |
| 适用场景 | 微服务内部、高并发、低延迟场景 | 对外 API、前后端分离、跨平台场景 |
57.5 主流 RPC 框架
- Dubbo:阿里开源,Java 生态主流,SpringBoot 生态完美适配
- gRPC:Google 开源,基于 HTTP/2,跨语言支持优秀
- Thrift:Apache 开源,跨语言,适合多语言异构系统
- Feign:SpringCloud 组件,基于 HTTP,封装 RESTful 调用
58、Dubbo 及 Zookeeper 安装
58.1 核心定位
- Dubbo:阿里开源的高性能 Java RPC 框架,是微服务架构的核心通信组件,提供服务注册发现、负载均衡、熔断降级等能力
- Zookeeper :Apache 开源的分布式协调服务,作为 Dubbo 的注册中心,存储服务地址信息,实现服务上下线感知
58.2 Zookeeper 安装(Linux 环境)
-
环境准备:JDK1.8+(Zookeeper 依赖 Java)
-
下载安装 :
bash
# 下载Zookeeper(推荐3.7.x版本) wget https://archive.apache.org/dist/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz # 解压 tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz # 重命名 mv apache-zookeeper-3.7.1-bin zookeeper -
配置文件 :
bash
cd zookeeper/conf cp zoo_sample.cfg zoo.cfg # 编辑配置文件 vim zoo.cfg核心配置:
properties
# 数据存储目录 dataDir=/usr/local/zookeeper/data # 客户端端口 clientPort=2181 # 心跳时间 tickTime=2000 -
启动与验证 :
bash
# 启动Zookeeper bin/zkServer.sh start # 查看状态 bin/zkServer.sh status # 客户端连接 bin/zkCli.sh -server localhost:2181
58.3 Dubbo 环境准备(SpringBoot 集成)
-
依赖引入(pom.xml) :
xml
<!-- Dubbo SpringBoot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!-- Zookeeper客户端 --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.7.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> -
核心配置(application.yml) :
yaml
# 服务提供者配置 dubbo: application: name: dubbo-provider # 应用名 registry: address: zookeeper://localhost:2181 # Zookeeper地址 timeout: 3000 protocol: name: dubbo # 通信协议 port: 20880 # Dubbo端口 scan: base-packages: com.example.dubbo.provider # 扫描服务接口
59、Dubbo-admin 安装测试
59.1 核心定位
Dubbo-admin 是 Dubbo 提供的可视化管理控制台,用于监控服务状态、管理服务上下线、查看调用统计、配置负载均衡等,是 Dubbo 微服务的运维核心工具。
59.2 安装部署(Docker 方式,推荐)
-
拉取镜像 :
bash
docker pull apache/dubbo-admin:latest -
启动容器 :
bash
docker run -d \ -p 38080:38080 \ -e admin.registry.address=zookeeper://zookeeper地址:2181 \ -e admin.config-center.address=zookeeper://zookeeper地址:2181 \ --name dubbo-admin \ apache/dubbo-admin:latest -
访问验证 :
- 访问地址:
http://localhost:38080 - 默认账号 / 密码:
root / root、guest / guest
- 访问地址:
59.3 核心功能
- 服务治理:查看服务列表、服务提供者 / 消费者、服务上下线
- 监控统计:服务调用次数、响应时间、成功率
- 配置管理:动态配置负载均衡、熔断降级、路由规则
- 权限管理:用户、角色、权限管理
59.4 测试流程
- 启动 Zookeeper、Dubbo-admin
- 启动 Dubbo 服务提供者,服务自动注册到 Zookeeper
- 登录 Dubbo-admin,查看服务是否注册成功
- 启动 Dubbo 服务消费者,调用服务,查看调用统计
60、服务注册发现实战
60.1 核心原理
服务注册发现是微服务架构的核心机制:
- 服务注册:服务启动时,将自己的地址、端口、服务名等信息注册到注册中心(Zookeeper)
- 服务发现:服务消费者从注册中心获取服务提供者地址列表
- 服务感知:注册中心实时监控服务状态,服务上下线时自动通知消费者
60.2 实战步骤
1. 定义服务接口(公共模块)
java
public interface EmployeeService {
// 根据ID查询员工
Employee getEmployeeById(Integer id);
// 查询所有员工
List<Employee> listEmployees();
}
2. 服务提供者实现
java
@DubboService // Dubbo服务注解,标记为服务提供者
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Override
public Employee getEmployeeById(Integer id) {
return new Employee(id, "张三", "zhangsan@example.com", 1, "技术部", new Date());
}
@Override
public List<Employee> listEmployees() {
return Arrays.asList(
new Employee(1, "张三", "zhangsan@example.com", 1, "技术部", new Date()),
new Employee(2, "李四", "lisi@example.com", 1, "人事部", new Date())
);
}
}
3. 服务消费者实现
java
@RestController
@RequestMapping("/consumer")
public class EmployeeConsumerController {
@DubboReference // Dubbo引用注解,标记为服务消费者
private EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable Integer id) {
return employeeService.getEmployeeById(id);
}
@GetMapping("/emp/list")
public List<Employee> listEmployees() {
return employeeService.listEmployees();
}
}
4. 消费者配置(application.yml)
yaml
dubbo:
application:
name: dubbo-consumer
registry:
address: zookeeper://localhost:2181
protocol:
name: dubbo
consumer:
timeout: 3000 # 调用超时时间
loadbalance: round-robin # 负载均衡策略(轮询)
5. 启动验证
- 启动 Zookeeper
- 启动服务提供者,服务自动注册到 Zookeeper
- 启动服务消费者,调用接口,验证服务调用成功
- 登录 Dubbo-admin,查看服务提供者 / 消费者状态
60.3 核心特性
- 负载均衡:Dubbo 提供多种负载均衡策略:轮询、随机、加权轮询、一致性哈希
- 熔断降级:服务故障时自动降级,返回默认值,避免系统雪崩
- 服务治理:动态配置服务权重、路由规则,实现灰度发布
- 高可用:注册中心集群、服务多实例部署,避免单点故障
61、聊聊现在和未来
61.1 技术现状总结
本教程完整覆盖了 SpringBoot 从入门到微服务的全栈技术,核心技术栈如下:
| 技术模块 | 核心内容 |
|---|---|
| SpringBoot 基础 | 自动配置、启动原理、配置文件、Web 开发 |
| 数据层集成 | JDBC、Druid、MyBatis、事务管理 |
| 安全框架 | SpringSecurity、Shiro、认证授权、登录拦截 |
| 工具集成 | Swagger 接口文档、异步 / 邮件 / 定时任务 |
| 中间件集成 | Redis 缓存、分布式锁、分布式系统理论 |
| 微服务通信 | RPC、Dubbo、Zookeeper、服务注册发现 |
61.2 技术发展趋势
1. 微服务架构演进
- 云原生:SpringBoot+SpringCloud+K8s 成为微服务标准架构
- 服务网格(Service Mesh):Istio、Linkerd,将服务治理下沉到基础设施
- Serverless:无服务器架构,进一步降低运维成本
2. RPC 框架发展
- Dubbo 3.x:支持 HTTP/3、云原生、多语言,性能大幅提升
- gRPC:云原生时代主流 RPC 框架,跨语言、高性能
- RSocket:响应式 RPC,支持双向通信、流式调用
3. 数据层技术
- MyBatis-Plus:MyBatis 增强工具,简化 CRUD 开发
- SpringData JPA:ORM 框架,适合简单 CRUD 场景
- 分布式数据库:Sharding-JDBC、TiDB,解决分库分表问题
4. 安全与监控
- SpringSecurity 6.x:全新 API,更简洁的安全配置
- SkyWalking:分布式链路追踪、APM 监控,微服务运维核心工具
- Sentinel:流量控制、熔断降级,保障微服务稳定性
61.3 学习路线建议
- 夯实基础:深入理解 SpringBoot 自动配置原理、Spring 核心 IOC/AOP
- 数据层精通:MyBatis、Redis、分布式锁、分布式事务
- 微服务进阶:SpringCloud Alibaba、Dubbo、Nacos、Sentinel
- 云原生学习:Docker、K8s、Jenkins、CI/CD
- 性能优化:JVM 调优、数据库优化、缓存优化、并发编程
61.4 总结
SpringBoot 是 Java 后端开发的基石框架,本教程从入门到实战,完整覆盖了企业级项目开发的核心技术栈。通过本教程的学习,你已经掌握了从单体应用到微服务架构的全流程开发能力,后续可基于此深入学习微服务、云原生等进阶技术,成为 Java 后端全栈工程师。