单元1 · Spring Boot 快速入门
Spring Initializr 建项目、pom 依赖、启动类、第一个 REST 接口、热部署与日志
- Spring Initializr:官方脚手架 start.spring.io,勾选依赖后一键生成可运行项目骨架。
- @SpringBootApplication:组合注解,包含 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。
- 内嵌容器:默认内嵌 Tomcat,无需外部部署,java -jar 即可运行。
- starter 机制:spring-boot-starter-* 聚合依赖,引入即获得对应功能自动配置。
- 热部署:spring-boot-devtools 提供开发期自动重启,生产环境自动禁用。
实训1.1 使用 Spring Initializr 创建项目
描述通过 Spring Initializr(start.spring.io)创建 Spring Boot 项目的步骤,并给出核心 pom.xml 依赖与启动类代码。
选择 Java 版本与依赖(spring-boot-starter-web);启动类上加 @SpringBootApplication,通过 main 方法运行 SpringApplication.run 启动内嵌 Tomcat。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.example</groupId>
<artifactId>hello-boot</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
// 启动类
package com.example.helloboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloBootApplication {
public static void main(String[] args) {
SpringApplication.run(HelloBootApplication.class, args);
}
}
实训1.2 第一个 REST 接口 HelloWorld
编写一个 REST 接口,访问 /hello 时返回字符串 Hello, Spring Boot!,并说明内嵌容器默认端口。
用 @RestController 声明控制器,@GetMapping("/hello") 映射 GET 请求;默认端口 8080,可在 application.properties 中用 server.port 修改。
package com.example.helloboot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}
实训1.3 热部署与日志输出
集成 spring-boot-devtools 实现热部署,并在接口中使用 SLF4J 日志输出请求信息。
引入 devtools 依赖后修改代码会自动重启;使用 lombok 的 @Slf4j 或 LoggerFactory 获取 logger,调用 info/debug 方法记录日志。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
package com.example.helloboot.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogController {
private static final Logger log =
LoggerFactory.getLogger(LogController.class);
@GetMapping("/log")
public String logDemo() {
log.info("收到 /log 请求");
log.debug("调试信息:{}", System.currentTimeMillis());
return "ok";
}
}
单元2 · 依赖注入与控制反转
IoC 容器、@Component/@Service、@Autowired、构造器注入、@Qualifier、@Configuration/@Bean
- IoC 容器:Spring 容器统一管理对象生命周期与依赖关系,实现控制反转。
- 组件注册:@Component/@Service/@Repository/@Controller 将类注册为 Bean。
- 依赖注入:@Autowired 支持字段/构造器/Setter 注入,推荐构造器注入。
- @Qualifier:接口多实现时按名称指定注入目标,Bean 默认名为类名首字母小写。
- @Configuration/@Bean:配置类中通过 @Bean 方法注册第三方组件,可指定初始/销毁方法。
实训2.1 @Component 与 @Autowired 注入
定义一个问候服务 GreetService,在控制器中通过 @Autowired 注入并调用。
@Service 将类注册为 Spring Bean;字段注入用 @Autowired;更推荐构造器注入,便于测试与不可变性。
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class GreetService {
public String greet(String name) {
return "你好," + name + "!";
}
}
package com.example.demo.controller;
import com.example.demo.service.GreetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetController {
@Autowired
private GreetService greetService;
@GetMapping("/greet")
public String greet(String name) {
return greetService.greet(name);
}
}
实训2.2 构造器注入与 @Qualifier 多实现选择
定义接口 MessageSender 与两个实现,通过构造器注入并配合 @Qualifier 选择指定实现。
构造器注入推荐在接口有多个实现时结合 @Qualifier 指定 Bean 名称;Bean 名称默认是类名首字母小写。
package com.example.demo.sender;
public interface MessageSender {
String send(String msg);
}
package com.example.demo.sender;
import org.springframework.stereotype.Component;
@Component("smsSender")
public class SmsSender implements MessageSender {
@Override
public String send(String msg) {
return "短信发送:" + msg;
}
}
package com.example.demo.sender;
import org.springframework.stereotype.Component;
@Component("mailSender")
public class MailSender implements MessageSender {
@Override
public String send(String msg) {
return "邮件发送:" + msg;
}
}
package com.example.demo.controller;
import com.example.demo.sender.MessageSender;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SendController {
private final MessageSender sender;
public SendController(@Qualifier("smsSender") MessageSender sender) {
this.sender = sender;
}
@GetMapping("/send")
public String send() {
return sender.send("Hello");
}
}
实训2.3 @Configuration 与 @Bean 注册第三方组件
通过 @Configuration 配置类使用 @Bean 手动注册一个第三方对象(如 java.time.Clock 的固定实例)。
@Configuration 类中的 @Bean 方法返回值会注册为容器 Bean;方法名即 Bean 名称,可设置 initMethod/destroyMethod。
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Clock;
import java.time.ZoneId;
@Configuration
public class AppConfig {
@Bean
public Clock systemClock() {
return Clock.system(ZoneId.of("Asia/Shanghai"));
}
}
单元3 · 自动配置原理
@SpringBootApplication、starter 机制、@ConfigurationProperties、条件装配
- 约定优于配置:按默认约定组织结构与配置,极少配置即可启动完整应用。
- 自动配置:@EnableAutoConfiguration 根据 classpath 依赖与配置属性自动装配。
- @ConfigurationProperties:将配置前缀批量绑定到 JavaBean,配合 IDE 提示使用更佳。
- 条件装配:@ConditionalOnXxx 系列注解根据条件决定是否装配 Bean。
实训3.1 @SpringBootApplication 组合注解分析
解释 @SpringBootApplication 由哪些注解组成,并编写一个自定义组合注解加深理解。
它由 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 组合;@EnableAutoConfiguration 通过 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 加载自动配置。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
/*
* @SpringBootApplication 等价于:
* @SpringBootConfiguration
* @EnableAutoConfiguration
* @ComponentScan(basePackages = "com.example.demo")
*/
实训3.2 @ConfigurationProperties 绑定配置
在 application.yml 中定义自定义配置项,使用 @ConfigurationProperties 绑定到配置类。
配置类加 @ConfigurationProperties(prefix = "app") 与 @Component 注册;YAML 中 app.name、app.version 会自动映射到字段。
app:
name: demo-service
version: 1.0.0
package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
}
实训3.3 @ConditionalOnProperty 条件装配
根据配置项 switch.enabled 是否开启来决定是否注册某个 Bean。
@ConditionalOnProperty(name = "switch.enabled", havingValue = "true") 表示仅当配置为 true 时才装配该 Bean,实现开关式扩展。
switch:
enabled: true
package com.example.demo.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwitchConfig {
@Bean
@ConditionalOnProperty(name = "switch.enabled", havingValue = "true")
public FeatureService featureService() {
return new FeatureService();
}
}
class FeatureService {
public String work() { return "功能已开启"; }
}
单元4 · Web 开发基础
@RestController、@GetMapping/@PostMapping、@PathVariable、@RequestParam、@RequestBody、统一返回
- @RestController:@Controller + @ResponseBody,方法返回值直接写回 HTTP 响应体。
- 请求映射:@GetMapping/@PostMapping/@PutMapping/@DeleteMapping 对应 HTTP 方法。
- 参数绑定:@PathVariable 路径参数、@RequestParam 查询参数、@RequestBody JSON 体。
- 统一返回:封装 Result
使接口响应结构一致,便于前端处理。
实训4.1 @PathVariable 与 @RequestParam 参数绑定
实现 REST 接口:路径参数取用户 id,查询参数指定城市,返回拼接信息。
@PathVariable 绑定 URL 路径占位符,@RequestParam 绑定 ?key=value 查询参数并支持 defaultValue。
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/user/{id}")
public String getUser(
@PathVariable("id") Long id,
@RequestParam(value = "city", defaultValue = "北京") String city) {
return "用户 id=" + id + ",城市=" + city;
}
}
实训4.2 POST JSON 请求体接收
定义 User 实体(id/name/age),使用 @PostMapping 与 @RequestBody 接收 JSON 并返回创建结果。
@RequestBody 将请求体 JSON 自动反序列化为 Java 对象;需要字段具备 getter/setter 或使用 record。
package com.example.demo.entity;
public class User {
private Long id;
private String name;
private Integer age;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserCreateController {
@PostMapping("/users")
public User create(@RequestBody User user) {
user.setId(System.currentTimeMillis());
return user;
}
}
实训4.3 统一返回结果封装
定义 Result
统一返回让前端处理更一致;Result.ok(data) 返回成功,Result.error(code,msg) 返回失败;接口方法返回 Result
package com.example.demo.common;
public class Result<T> {
private int code;
private String message;
private T data;
public Result() {}
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> ok(T data) {
return new Result<>(200, "success", data);
}
public static <T> Result<T> error(int code, String message) {
return new Result<>(code, message, null);
}
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}
单元5 · Spring Data JPA
实体映射、Repository、方法命名查询、@Query 自定义查询、分页排序
- 实体映射:@Entity 映射表,@Id + @GeneratedValue 配置主键自增。
- Repository:继承 JpaRepository
获得通用 CRUD 方法,无需实现。 - 方法命名查询:findByXxx、findByXxxContaining 等按方法名自动生成 JPQL。
- @Query:自定义 JPQL/SQL 查询,@Param 绑定命名参数。
实训5.1 实体类与 Repository 基础 CRUD
定义 Book 实体(@Entity)并创建 JpaRepository 接口,实现按 id 查询与保存。
@Entity 标记实体,@Id + @GeneratedValue 配置主键;接口继承 JpaRepository
package com.example.demo.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String author;
private Double price;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
}
package com.example.demo.repository;
import com.example.demo.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {
}
实训5.2 方法命名规则查询
按作者查找图书列表、按标题模糊查询,使用 Spring Data JPA 方法命名规则。
方法名由 findBy + 属性名 + 关键字组成:findByAuthor、findByTitleContaining 自动生成查询,无需写 SQL。
package com.example.demo.repository;
import com.example.demo.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findByAuthor(String author);
List<Book> findByTitleContaining(String keyword);
List<Book> findByPriceGreaterThan(Double price);
}
实训5.3 @Query 自定义 JPQL 查询
使用 @Query 编写 JPQL 查询按价格区间检索图书,并演示命名参数。
@Query 写 JPQL(面向实体属性),:minPrice 命名参数配合 @Param 绑定;注意 JPQL 操作的是实体类与属性名。
package com.example.demo.repository;
import com.example.demo.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface BookRepository extends JpaRepository<Book, Long> {
@Query("SELECT b FROM Book b WHERE b.price BETWEEN :minPrice AND :maxPrice")
List<Book> findByPriceRange(@Param("minPrice") Double min,
@Param("maxPrice") Double max);
}
单元6 · MyBatis / MyBatis-Plus 集成
Mapper XML、@MapperScan、MyBatis-Plus BaseMapper、QueryWrapper
- Mapper 接口:@Mapper 或 @MapperScan 注册 MyBatis 映射器接口。
- MyBatis-Plus:增强工具包,BaseMapper 提供通用 CRUD,省去重复 XML。
- 条件构造器:QueryWrapper/LambdaQueryWrapper 以链式 API 构建动态条件。
- #{} vs ${}:#{} 预编译参数防注入,${} 直接拼接字符串存在风险。
实训6.1 MyBatis 依赖与 Mapper 接口
在 Spring Boot 中集成 MyBatis:引入依赖、配置数据源、创建 Mapper 接口查询用户。
引入 mybatis-spring-boot-starter,配置 spring.datasource;接口加 @Mapper 或启动类加 @MapperScan;SQL 写在 XML 或注解中。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
username: root
password: 123456
package com.example.demo.mapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Long id);
}
实训6.2 MyBatis-Plus BaseMapper 快速 CRUD
引入 mybatis-plus-boot-starter,创建实体与 Mapper 继承 BaseMapper,演示 selectById 与 insert。
BaseMapper
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
实训6.3 QueryWrapper 条件构造器
使用 MyBatis-Plus 条件构造器 QueryWrapper 实现按名称模糊查询并按年龄排序。
QueryWrapper 通过 lambda 方法(like/orderByDesc)构建条件;LambdaQueryWrapper 支持类型安全写法。
package com.example.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public List<User> search(String keyword) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, keyword)
.orderByDesc(User::getAge);
return userMapper.selectList(wrapper);
}
}
单元7 · 配置管理
application.yml、多环境 profile、@Value、@ConfigurationProperties、随机值
- application.yml:Spring Boot 核心配置文件,层级化书写更清晰。
- 多环境 Profile:application-{profile}.yml + spring.profiles.active 按环境切换配置。
- @Value:注入单个配置值,支持 ${key:default} 默认值语法。
- 随机值:${random.int}、${random.uuid} 生成随机配置。
实训7.1 多环境 Profile 配置
为开发/生产环境分别配置数据源,演示 spring.profiles.active 激活方式。
命名 application-dev.yml、application-prod.yml,通过 spring.profiles.active=dev 激活;也可用 spring.profiles.include 组合加载。
# application.yml
spring:
profiles:
active: dev
# application-dev.yml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo_dev
# application-prod.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://prod-server:3306/demo_prod
实训7.2 @Value 注入配置值
使用 @Value 注入字符串、整数与布尔配置项,演示默认值语法。
@Value("${key:默认值}") 从 Environment 读取配置;适用于单个散配置,批量配置建议用 @ConfigurationProperties。
app:
welcome: 欢迎使用
max-size: 100
debug: true
package com.example.demo.component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppInfo {
@Value("${app.welcome}")
private String welcome;
@Value("${app.max-size:50}")
private int maxSize;
@Value("${app.debug:false}")
private boolean debug;
public String info() {
return welcome + ",maxSize=" + maxSize + ",debug=" + debug;
}
}
实训7.3 随机值配置
使用 ${random.int} 与 ${random.uuid} 生成随机配置,验证配置灵活性。
Spring 提供 random 占位符:${random.int}、${random.int(1,100)}、${random.uuid},常用于测试数据或实例标识。
server:
id: ${random.uuid}
secret: ${random.int(1000, 9999)}
package com.example.demo.component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class RandomProps {
@Value("${server.id}")
private String instanceId;
@Value("${server.secret}")
private int secret;
public String show() {
return "实例=" + instanceId + ",密钥=" + secret;
}
}
单元8 · 日志与监控
Logback 配置、SLF4J 占位符、Actuator 健康检查、自定义指标
- SLF4J:统一日志门面,配合 Logback 实现灵活输出。
- Logback:默认日志实现,支持控制台/滚动文件/异步输出。
- Actuator:生产级监控端点,health/info/metrics 开箱即用。
- HealthIndicator:自定义健康检查项,扩展系统可用性判断。
实训8.1 Logback 日志配置
编写 logback-spring.xml:控制台输出 + 按天滚动文件,日志级别 info。
logback-spring.xml 中 ConsoleAppender 输出控制台,RollingFileAppender 按日期滚动;%d、%level、%logger 为常用格式占位符。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
实训8.2 Actuator 健康检查
引入 spring-boot-starter-actuator,开放 health 与 info 端点并访问查看状态。
引入 actuator 后默认暴露 health;management.endpoints.web.exposure.include 控制暴露端点;/actuator/health 返回 UP 状态。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
实训8.3 自定义健康指示器
实现 HealthIndicator 自定义健康检查,模拟检测磁盘剩余空间是否充足。
实现 HealthIndicator 接口的 health() 方法,通过 Health.up().withDetail(...) 返回状态;Spring Boot 自动收集所有 HealthIndicator。
package com.example.demo.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class DiskHealthIndicator implements HealthIndicator {
@Override
public Health health() {
long free = Runtime.getRuntime().freeMemory();
if (free > 100 * 1024 * 1024) {
return Health.up()
.withDetail("freeMemory", free)
.build();
}
return Health.down().withDetail("reason", "内存不足").build();
}
}
单元9 · 拦截器与过滤器
HandlerInterceptor、WebMvcConfigurer 注册、CORS、Filter
- HandlerInterceptor:Spring MVC 拦截器,preHandle/postHandle/afterCompletion 三阶段。
- 注册拦截器:WebMvcConfigurer.addInterceptors 配置拦截路径与放行路径。
- CORS:跨域资源共享,addCorsMappings 配置来源、方法、凭证。
- Filter:Servlet 过滤器,在拦截器之前执行,适合通用横切逻辑。
实训9.1 登录拦截器 HandlerInterceptor
实现 HandlerInterceptor 拦截未登录请求,并注册到拦截器链。
preHandle 返回 false 表示拦截;通过 WebMvcConfigurer.addInterceptors 注册并配置 excludePathPatterns 放行路径。
package com.example.demo.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if (user == null) {
response.setStatus(401);
response.getWriter().write("请先登录");
return false;
}
return true;
}
}
package com.example.demo.config;
import com.example.demo.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login", "/api/register");
}
}
实训9.2 跨域 CORS 配置
为前端跨域访问配置全局 CORS 规则:允许 localhost:5173 来源、GET/POST 方法。
实现 WebMvcConfigurer.addCorsMappings,allowedOriginPatterns 指定来源,allowedMethods 指定方法,allowCredentials 允许携带凭证。
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
实训9.3 过滤器 Filter 统计耗时
编写 Filter 统计每个请求耗时并打印日志,注册为 @Component 生效。
实现 jakarta.servlet.Filter 接口,doFilter 前后记录 System.currentTimeMillis 差值;@Component 自动被 Servlet 容器注册。
package com.example.demo.filter;
import jakarta.servlet.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class TimeFilter implements Filter {
private static final Logger log =
LoggerFactory.getLogger(TimeFilter.class);
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long cost = System.currentTimeMillis() - start;
log.info("请求耗时:{}ms", cost);
}
}
单元10 · 测试与打包部署
@SpringBootTest、MockMvc、Maven 打包、jar 运行、Dockerfile
- @SpringBootTest:启动完整 Spring 上下文进行集成测试。
- MockMvc:模拟 HTTP 请求测试 Controller,校验状态码与响应体。
- Maven 打包:mvn clean package 生成包含依赖的可执行 fat jar。
- 容器化:Dockerfile 基于 JRE 镜像部署,配合 K8s 实现弹性伸缩。
实训10.1 @SpringBootTest 上下文测试
编写 Spring Boot 测试,注入 BookRepository 验证保存与查询。
@SpringBootTest 启动完整上下文;配合 @Transactional 保证测试数据回滚;断言使用 JUnit5 的 Assertions。
package com.example.demo;
import com.example.demo.entity.Book;
import com.example.demo.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
public class BookRepositoryTest {
@Autowired
private BookRepository bookRepository;
@Test
void shouldSaveAndFindBook() {
Book book = new Book();
book.setTitle("Spring 实战");
book.setAuthor("张三");
bookRepository.save(book);
Book found = bookRepository.findById(book.getId()).orElse(null);
assertNotNull(found);
assertEquals("Spring 实战", found.getTitle());
}
}
实训10.2 MockMvc 接口测试
使用 MockMvc 对 GET /hello 接口发起请求并断言响应状态与内容。
@AutoConfigureMockMvc 注入 MockMvc;perform(get("/hello")).andExpect(status().isOk()) 校验状态码与返回体。
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class HelloApiTest {
@Autowired
private MockMvc mockMvc;
@Test
void helloShouldReturnText() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, Spring Boot!"));
}
}
实训10.3 Maven 打包与容器化部署
给出 Maven 打包命令、启动命令与基础 Dockerfile,说明 jar 部署流程。
mvn clean package 生成可执行 fat jar;java -jar app.jar 启动;Dockerfile 用 eclipse-temurin 基础镜像复制 jar 并 EXPOSE 端口。
# 打包
mvn clean package -DskipTests
# 运行
java -jar target/demo-0.0.1-SNAPSHOT.jar --server.port=8080
# Dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
单元11 · Redis 与缓存
spring-boot-starter-data-redis、RedisTemplate、@Cacheable、缓存更新
- RedisTemplate:Spring Data Redis 提供的操作模板,opsForValue/opsForHash 等。
- @EnableCaching:开启注解缓存支持,配合 @Cacheable/@CachePut/@CacheEvict。
- 缓存一致性:更新用 @CachePut 同步,删除用 @CacheEvict 失效。
- 缓存雪崩:为缓存 key 设置随机过期时间、多级缓存等手段防范。
实训11.1 RedisTemplate 操作字符串
注入 RedisTemplate 保存并读取字符串键值,演示 opsForValue。
引入 starter-data-redis 并配置 redis 地址;RedisTemplate.opsForValue().set/get 操作字符串,key 可加前缀避免冲突。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
data:
redis:
host: localhost
port: 6379
package com.example.demo.service;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class CacheService {
private final RedisTemplate<String, String> redisTemplate;
public CacheService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void save(String key, String value) {
redisTemplate.opsForValue().set("demo:" + key, value);
}
public String get(String key) {
return redisTemplate.opsForValue().get("demo:" + key);
}
}
实训11.2 @Cacheable 方法缓存
为按 id 查询图书的方法添加 @Cacheable 缓存,命中缓存时不执行方法。
@Cacheable(cacheNames="book", key="#id") 在方法执行前查缓存;需开启 @EnableCaching;返回 null 时不缓存。
package com.example.demo.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}
package com.example.demo.service;
import com.example.demo.entity.Book;
import com.example.demo.repository.BookRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Cacheable(cacheNames = "book", key = "#id")
public Book getById(Long id) {
return bookRepository.findById(id).orElse(null);
}
}
实训11.3 缓存更新与删除策略
为修改与删除图书的方法添加缓存同步注解,保证缓存一致性。
修改方法加 @CachePut 更新缓存;删除方法加 @CacheEvict(key="#id") 清除缓存;批量失效可用 allEntries = true。
package com.example.demo.service;
import com.example.demo.entity.Book;
import com.example.demo.repository.BookRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@CachePut(cacheNames = "book", key = "#book.id")
public Book update(Book book) {
return bookRepository.save(book);
}
@CacheEvict(cacheNames = "book", key = "#id")
public void delete(Long id) {
bookRepository.deleteById(id);
}
}
单元12 · 综合项目:员工管理系统
分层架构、员工 CRUD、全局异常处理、统一返回
- 分层架构:Controller-Service-Repository 三层各司其职,职责单一。
- RESTful 设计:资源 + HTTP 方法表达 CRUD 语义,路径名词复数。
- 全局异常:@RestControllerAdvice 统一捕获异常,避免堆栈泄露。
- 统一返回:Result
约定 code/message/data,前后端协作标准。
实训12.1 分层架构搭建(实体/仓库/服务/控制层)
搭建员工管理系统的分层结构:Employee 实体、Repository、Service 接口与实现。
标准三层:Controller 接收请求 → Service 处理业务 → Repository 访问数据;Service 用接口 + 实现类便于扩展与测试。
package com.example.demo.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String dept;
private Double salary;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDept() { return dept; }
public void setDept(String dept) { this.dept = dept; }
public Double getSalary() { return salary; }
public void setSalary(Double salary) { this.salary = salary; }
}
package com.example.demo.repository;
import com.example.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
实训12.2 员工 CRUD 接口实现
实现员工的查询全部、按 id 查询、新增、修改、删除五个 REST 接口。
Controller 层通过 @GetMapping/@PostMapping/@PutMapping/@DeleteMapping 对应 CRUD;Service 层处理业务逻辑并抛出业务异常。
package com.example.demo.controller;
import com.example.demo.entity.Employee;
import com.example.demo.repository.EmployeeRepository;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeRepository repository;
public EmployeeController(EmployeeRepository repository) {
this.repository = repository;
}
@GetMapping
public List<Employee> list() {
return repository.findAll();
}
@GetMapping("/{id}")
public Employee get(@PathVariable Long id) {
return repository.findById(id).orElse(null);
}
@PostMapping
public Employee create(@RequestBody Employee employee) {
employee.setId(null);
return repository.save(employee);
}
@PutMapping("/{id}")
public Employee update(@PathVariable Long id,
@RequestBody Employee employee) {
employee.setId(id);
return repository.save(employee);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
repository.deleteById(id);
}
}
实训12.3 全局异常处理与统一返回
定义全局异常处理器 @RestControllerAdvice,统一处理业务异常与参数错误,返回 Result 结构。
@RestControllerAdvice + @ExceptionHandler 捕获异常;自定义 BusinessException 携带错误码;ExceptionHandler 中返回 Result.error。
package com.example.demo.common;
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() { return code; }
}
package com.example.demo.common;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValid(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors()
.stream().findFirst().map(f -> f.getDefaultMessage())
.orElse("参数校验失败");
return Result.error(400, msg);
}
@ExceptionHandler(Exception.class)
public Result<Void> handleOther(Exception e) {
return Result.error(500, "系统繁忙:" + e.getMessage());
}
}