Spring框架从零开始学习路径:核心概念与Spring Boot企业级开发实战
先聊聊为什么要学Spring
说实话,我刚开始接触Java的时候也被各种框架搞懵过。记得当时学SMBMS、网上商城这些项目,每次看到new出来的各种对象满天飞,头疼得不行。后来才知道,有人专门干了一件事——把这些东西管起来,那就是Spring。
Spring简单来说,就是一个帮你管理对象的容器。以前你需要自己new对象、自己管理依赖,现在Spring帮你全部搞定。而且它的生态太丰富了,从Web开发到数据库访问,从消息队列到分布式,基本上你想干啥都有对应的Spring项目。
学Spring之前,你至少需要掌握这些东西:
- Java基础扎实(面向对象、集合、泛型、注解这些)
- 了解Maven或Gradle这种构建工具
- 懂一点前端基础知识(后面要用)
- 会写简单的HTTP请求(理解Tomcat、Servlet)
第一部分:Spring的核心思想——IoC与DI
控制反转(IoC)到底是什么?
别被名字吓到,说人话就是:你不再自己创建对象了,而是交给Spring来创建。
传统做法是这样的:
public class UserService {
private UserDao userDao = new UserDaoImpl(); // 你自己new出来
public void addUser(String name) {
userDao.add(name);
}
}
问题在哪?UserService和UserDaoImpl耦合得太死了。哪天你要换一种实现方式,或者想测试的时候,代码全得改。
Spring的做法是:
public class UserService {
private UserDao userDao; // 不再new,交给Spring注入
// Spring会自动调用这个方法,把UserDaoImpl传进来
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
你告诉Spring”我需要谁”,Spring就会帮你找到合适的实现类塞进来。这个过程叫依赖注入(DI),本质上是IoC的一种实现方式。
Bean——Spring的”宝贝疙瘩”
Spring管理的对象都叫Bean。你可以把Bean理解为被Spring容器托管的对象,Spring负责创建、配置、管理它们的生命周期。
// 最简单的Bean定义方式——用注解
@Component
public class UserService {
public void sayHello() {
System.out.println("Hello, I'm a Spring Bean!");
}
}
// 或者用XML配置(老项目常用,了解就行)
// applicationContext.xml
<bean id="userService" class="com.example.UserService"/>
Bean的作用域
Spring里有5种作用域,常用的就两个:
- singleton(默认):整个应用只有一个实例,单例模式
- prototype:每次获取都创建新的实例
@Component
@Scope("prototype") // 改成原型模式
public class UserService {
// ...
}
依赖注入的三种方式
1. 构造器注入(最推荐)
@Component
public class UserService {
private final UserRepository repository;
// Spring会通过这个构造器注入依赖
// final保证不可变,线程安全
@Autowired
public UserService(UserRepository repository) {
this.repository = repository;
}
}
2. Setter注入
@Component
public class UserService {
private UserRepository repository;
@Autowired
public void setRepository(UserRepository repository) {
this.repository = repository;
}
}
3. 字段注入(不推荐,测试麻烦)
@Component
public class UserService {
@Autowired // 直接在字段上注入
private UserRepository repository;
}
第二部分:Spring的AOP——面向切面编程
为什么需要AOP?
想象一下,你有100个方法,每个方法都需要记录日志、需要事务管理、需要权限检查。如果没有AOP,你得在每个方法里都写一遍这些代码,重复又容易出错。
AOP的思想是:把那些重复的、跟业务无关的代码抽离出来,形成一个”切面”,然后在需要的地方自动织入。
AOP核心概念
- 切面(Aspect):就是那些横切逻辑,比如日志、事务
- 连接点(Join Point):程序执行过程中可以插入切面的位置(通常是方法调用)
- 切入点(Pointcut):通过表达式指定哪些连接点需要织入切面
- 通知(Advice):切面在特定时间点执行的动作(前置、后置、异常、最终)
- 目标对象(Target):被代理的原始对象
- 代理(Proxy):AOP之后生成的对象,增强了目标对象的功能
实战:用AOP实现日志记录
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect // 标记为切面类
public class LogAspect {
// 定义切入点:com.example.service包下所有类的public方法
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceMethod() {}
// 前置通知:方法执行前打印
@Before("serviceMethod()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("[日志] 方法开始: " + joinPoint.getSignature().getName());
}
// 后置通知:方法执行后打印
@After("serviceMethod()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("[日志] 方法结束: " + joinPoint.getSignature().getName());
}
// 返回通知:方法正常返回后执行
@AfterReturning(pointcut = "serviceMethod()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("[日志] 方法返回: " + result);
}
// 异常通知:方法抛出异常时执行
@AfterThrowing(pointcut = "serviceMethod()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println("[日志] 方法异常: " + exception.getMessage());
}
}
// 使用这个切面
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
控制台输出:
[日志] 方法开始: findById
[日志] 方法返回: User{id=1, name='张三'}
[日志] 方法结束: findById
AOP的底层原理:动态代理
Spring AOP默认使用动态代理来实现:
- 如果目标对象实现了接口,用JDK动态代理
- 如果目标对象没有实现接口,用CGLIB(生成子类)
// 强制使用CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
第三部分:Spring的事务管理
事务是什么?
简单说,事务就是一系列操作,要么全部成功,要么全部失败回滚。比如银行转账:A转100块给B,A扣100块和B加100块必须同时成功,不能出现A扣了B没收到的情况。
声明式事务管理
Spring用@Transactional注解来管理事务,这是最优雅的方式。
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
// 加了@Transactional,这个方法就是一个事务
// 如果中间抛出异常,整个事务回滚
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 1. 扣钱
accountRepository.decrease(fromId, amount);
// 2. 加钱(如果这步抛异常,第1步的扣钱会回滚)
accountRepository.increase(toId, amount);
}
}
事务传播行为
有时候一个事务方法调用了另一个事务方法,这时候怎么处理?这就涉及传播行为:
@Service
public class OrderService {
// REQUIRED(默认):如果当前有事务就加入,没有就新建
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
orderRepository.save(order);
// 调用下面这个方法
inventoryService.deductStock(order.getProductId(), order.getQuantity());
}
// REQUIRES_NEW:挂起当前事务,开启一个新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductStock(Long productId, int quantity) {
// 这里如果异常,只回滚自己的事务,不影响外层createOrder
inventoryMapper.deduct(productId, quantity);
}
}
常见传播行为:
| 行为 | 说明 |
|---|---|
| REQUIRED | 加入当前事务,没有则新建 |
| REQUIRES_NEW | 挂起当前事务,新建独立事务 |
| SUPPORTS | 有事务就加入,没有就以非事务方式执行 |
| NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 |
| MANDATORY | 必须在事务中执行,否则抛异常 |
| NEVER | 不能在事务中执行 |
| NESTED | 嵌套事务,使用保存点 |
事务隔离级别
// 最常用:READ_COMMITTED(读已提交)
@Transactional(isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED,
rollbackFor = Exception.class,
timeout = 30)
public void someMethod() {
// ...
}
| 隔离级别 | 说明 | 可能问题 |
|---|---|---|
| READ_UNCOMMITTED | 读未提交 | 脏读 |
| READ_COMMITTED | 读已提交 | 不可重复读 |
| REPEATABLE_READ | 可重复读 | 幻读(MySQL默认) |
| SERIALIZABLE | 串行化 | 性能差 |
第四部分:Spring MVC——Web层开发
请求是怎么处理的?
Spring MVC是Spring框架的Web模块,基于前端控制器模式,工作流程大致是这样的:
客户端请求 → DispatcherServlet → HandlerMapping → Controller方法 →
ViewResolver → View → 响应返回
基础Controller写法
@RestController // 同时标注@Controller和@ResponseBody,直接返回数据
@RequestMapping("/api/users") // 类级别的路径前缀
public class UserController {
@Autowired
private UserService userService;
// GET请求,获取单个用户
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return Result.success(user);
}
// GET请求,获取用户列表(支持分页)
@GetMapping
public Result<PageResult<User>> listUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
PageResult<User> result = userService.list(page, size, keyword);
return Result.success(result);
}
// POST请求,创建用户
@PostMapping
public Result<User> createUser(@RequestBody @Valid CreateUserDTO dto) {
User user = userService.create(dto);
return Result.success(user);
}
// PUT请求,更新用户
@PutMapping("/{id}")
public Result<Void> updateUser(
@PathVariable Long id,
@RequestBody @Valid UpdateUserDTO dto) {
userService.update(id, dto);
return Result.success(null);
}
// DELETE请求,删除用户
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return Result.success(null);
}
}
参数绑定的几种方式
@GetMapping("/search")
public Result<List<User>> search(
// 1. 简单类型参数,直接绑定
@RequestParam String name,
// 2. 带默认值
@RequestParam(defaultValue = "1") int page,
// 3. URL路径变量
@PathVariable Long id,
// 4. 请求体JSON,自动绑定到对象
@RequestBody UserDTO dto,
// 5. 请求头
@RequestHeader("Authorization") String token,
// 6. Cookie
@CookieValue("sessionId") String sessionId,
// 7. 表单数据
@ModelAttribute UserForm form
) {
// ...
}
DTO(数据传输对象)
// 创建用户时用的DTO
public class CreateUserDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20个字符")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度6-20个字符")
private String password;
// getter/setter省略
}
全局异常处理
@RestControllerAdvice // 全局异常处理器
public class GlobalExceptionHandler {
// 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.fail(400, message);
}
// 业务异常
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
// 其他异常兜底
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "服务器内部错误");
}
}
第五部分:Spring Data JPA——数据访问层
为什么用JPA?
以前用MyBatis需要手写SQL,虽然灵活但重复劳动多。JPA让你用面向对象的方式操作数据库,Spring Data JPA更是把这些样板代码都省掉了。
实体类定义
@Entity // 标记为JPA实体
@Table(name = "sys_user") // 对应数据库表
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增主键
private Long id;
@Column(name = "username", length = 50, nullable = false, unique = true)
private String username;
@Column(name = "email", length = 100)
private String email;
@Column(name = "phone", length = 20)
private String phone;
@Column(name = "password", length = 255)
private String password;
@Column(name = "status", nullable = false)
private Integer status = 1; // 1启用 0禁用
@Column(name = "created_time", nullable = false, updatable = false)
private LocalDateTime createdTime;
@Column(name = "updated_time", nullable = false)
private LocalDateTime updatedTime;
// 插入前自动设置时间
@PrePersist
public void prePersist() {
LocalDateTime now = LocalDateTime.now();
this.createdTime = now;
this.updatedTime = now;
}
// 更新前自动更新时间
@PreUpdate
public void preUpdate() {
this.updatedTime = LocalDateTime.now();
}
// getter/setter省略
}
Repository——CRUD操作
// 继承JpaRepository,自动获得大量常用方法
public interface UserRepository extends JpaRepository<User, Long> {
// 自动根据方法名生成SQL
User findByUsername(String username);
List<User> findByStatus(Integer status);
Page<User> findByStatus(Integer status, Pageable pageable);
// 复杂查询用@Query
@Query("SELECT u FROM User u WHERE u.email LIKE %:keyword% OR u.phone LIKE %:keyword%")
Page<User> searchUsers(@Param("keyword") String keyword, Pageable pageable);
// 统计
@Query("SELECT COUNT(u) FROM User u WHERE u.status = :status")
Long countByStatus(@Param("status") Integer status);
}
Service层完整示例
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder; // 密码加密
@Transactional(rollbackFor = Exception.class)
public User create(CreateUserDTO dto) {
// 检查用户名是否已存在
if (userRepository.findByUsername(dto.getUsername()) != null) {
throw new BusinessException(400, "用户名已存在");
}
User user = new User();
user.setUsername(dto.getUsername());
user.setEmail(dto.getEmail());
user.setPhone(dto.getPhone());
// 密码加密存储
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setStatus(1);
return userRepository.save(user);
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new BusinessException(404, "用户不存在"));
}
public PageResult<User> list(int page, int size, String keyword) {
Pageable pageable = PageRequest.of(page - 1, size, Sort.by("createdTime").descending());
Page<User> userPage;
if (StringUtils.isNotBlank(keyword)) {
userPage = userRepository.searchUsers(keyword, pageable);
} else {
userPage = userRepository.findAll(pageable);
}
return PageResult.of(userPage);
}
@Transactional(rollbackFor = Exception.class)
public void update(Long id, UpdateUserDTO dto) {
User user = findById(id);
if (dto.getEmail() != null) {
user.setEmail(dto.getEmail());
}
if (dto.getPhone() != null) {
user.setPhone(dto.getPhone());
}
if (dto.getStatus() != null) {
user.setStatus(dto.getStatus());
}
userRepository.save(user);
}
@Transactional(rollbackFor = Exception.class)
public void delete(Long id) {
userRepository.deleteById(id);
}
public boolean login(String username, String password) {
User user = userRepository.findByUsername(username);
if (user == null) {
return false;
}
return passwordEncoder.matches(password, user.getPassword());
}
}
第六部分:Spring Boot入门——项目快速搭建
Spring Boot解决了什么问题?
Spring虽然强大,但配置繁琐。你需要写XML、配Web.xml、手动搭建Spring环境。Spring Boot的目标是约定优于配置,让你快速起步,开箱即用。
创建第一个Spring Boot项目
用最简单的方式——Spring Initializr,或者直接创建Maven项目后加依赖:
<!-- pom.xml核心依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
</parent>
<dependencies>
<!-- Web启动器:包含Spring MVC + Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA启动器:包含Spring Data JPA + Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</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>
主启动类
@SpringBootApplication // 组合注解:@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
就这一行注解,Spring Boot自动帮你:
- 扫描当前包及子包下的所有组件
- 自动配置Spring环境
- 内嵌Tomcat,直接运行
配置文件application.yml
server:
port: 8080
spring:
application:
name: spring-boot-demo
datasource:
url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update # 开发环境自动建表,生产环境用none
show-sql: true # 打印SQL
properties:
hibernate:
format_sql: true # 格式化SQL
logging:
level:
root: INFO
com.example: DEBUG
第七部分:Spring Boot企业级开发——完整项目实战
项目结构(推荐的分层架构)
src/main/java/com/example/demo/
├── DemoApplication.java # 主启动类
├── common/ # 通用模块
│ ├── result/
│ │ └── Result.java # 统一响应封装
│ └── exception/
│ ├── BusinessException.java # 业务异常
│ └── GlobalExceptionHandler.java # 全局异常处理
├── config/ # 配置类
│ ├── JacksonConfig.java
│ ├── CorsConfig.java
│ └── MybatisPlusConfig.java
├── entity/ # 实体类
│ ├── User.java
│ └── Order.java
├── dto/ # 数据传输对象
│ ├── CreateUserDTO.java
│ └── UpdateUserDTO.java
├── repository/ # 数据访问层
│ └── UserRepository.java
├── service/ # 业务逻辑层
│ ├── UserService.java
│ └── impl/UserServiceImpl.java
├── controller/ # 控制层
│ └── UserController.java
└── util/ # 工具类
└── DateUtil.java
统一响应封装
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code; // 状态码
private String message; // 提示信息
private T data; // 数据
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
public static <T> Result<T> fail(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> fail(String message) {
return new Result<>(500, message, null);
}
}
完整Controller示例
@RestController
@RequestMapping("/api/users")
@Validated // 开启参数校验
public class UserController {
@Autowired
private UserService userService;
/**
* 创建用户
*/
@PostMapping
public Result<User> create(@Valid @RequestBody CreateUserDTO dto) {
User user = userService.create(dto);
return Result.success(user);
}
/**
* 获取用户列表(分页)
*/
@GetMapping
public Result<PageResult<User>> list(
@RequestParam(defaultValue = "1") @Min(1) int page,
@RequestParam(defaultValue = "10") @Min(1) @Max(100) int size,
@RequestParam(required = false) String keyword) {
PageResult<User> result = userService.list(page, size, keyword);
return Result.success(result);
}
/**
* 获取用户详情
*/
@GetMapping("/{id}")
public Result<User> get(@PathVariable @Min(1) Long id) {
User user = userService.findById(id);
return Result.success(user);
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public Result<Void> update(
@PathVariable @Min(1) Long id,
@Valid @RequestBody UpdateUserDTO dto) {
userService.update(id, dto);
return Result.success();
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable @Min(1) Long id) {
userService.delete(id);
return Result.success();
}
/**
* 用户登录
*/
@PostMapping("/login")
public Result<LoginVO> login(@Valid @RequestBody LoginDTO dto) {
LoginVO vo = userService.login(dto);
return Result.success(vo);
}
/**
* 修改密码
*/
@PutMapping("/password")
public Result<Void> updatePassword(
@RequestHeader("Authorization") String token,
@Valid @RequestBody UpdatePasswordDTO dto) {
userService.updatePassword(token, dto);
return Result.success();
}
}
统一分页结果
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult<T> {
private List<T> list; // 数据列表
private Long total; // 总记录数
private Integer page; // 当前页
private Integer size; // 每页大小
private Integer totalPages; // 总页数
public static <T> PageResult<T> of(Page<T> page) {
PageResult<T> result = new PageResult<>();
result.setList(page.getContent());
result.setTotal(page.getTotalElements());
result.setPage(page.getNumber() + 1); // 转为1-based
result.setSize(page.getSize());
result.setTotalPages(page.getTotalPages());
return result;
}
}
第八部分:Spring Security——安全认证与授权
为什么需要安全框架?
手动写登录认证、权限判断很麻烦,而且容易有安全隐患。Spring Security提供了完整的安全解决方案。
基础配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public PasswordEncoder passwordEncoder() {
// BCrypt是业界标准的密码加密算法
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 禁用CSRF(前后端分离不需要)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
// 公开接口,不需要认证
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
// 其他接口需要认证
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOriginPattern("*");
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
JWT工具类
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
// 生成Token
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
// 验证Token
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
// 从Token中提取用户名
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
private boolean isTokenExpired(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getExpiration()
.before(new Date());
}
}
JWT过滤器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
try {
String username = jwtUtil.getUsernameFromToken(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
} catch (Exception e) {
// Token无效,返回401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
chain.doFilter(request, response);
}
}
第九部分:Redis缓存——提升性能的关键
为什么要用缓存?
数据库查询再快也有上限,高频访问的数据如果每次都查数据库,性能瓶颈很快就出现了。Redis作为内存数据库,读性能可以达到10万+ QPS。
Spring Boot集成Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: localhost
port: 6379
password: ""
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
缓存用户数据
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private StringRedisTemplate redisTemplate;
private static final String USER_KEY = "user:";
private static final long CACHE_EXPIRE = 30; // 分钟
public User findById(Long id) {
// 1. 先查缓存
String cacheKey = USER_KEY + id;
String cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return JSON.parseObject(cached, User.class);
}
// 2. 缓存没有,查数据库
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException(404, "用户不存在"));
// 3. 写入缓存
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user),
CACHE_EXPIRE, TimeUnit.MINUTES);
return user;
}
@Transactional(rollbackFor = Exception.class)
public void update(Long id, UpdateUserDTO dto) {
// ... 更新逻辑 ...
userRepository.save(user);
// 更新后删除缓存(缓存更新策略:Cache Aside)
redisTemplate.delete(USER_KEY + id);
}
@Transactional(rollbackFor = Exception.class)
public void delete(Long id) {
userRepository.deleteById(id);
// 删除缓存
redisTemplate.delete(USER_KEY + id);
}
}
缓存穿透、击穿、雪崩解决方案
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,每次请求都打到数据库 | 缓存空值、布隆过滤器 |
| 缓存击穿 | 热点key过期瞬间,大量请求打到数据库 | 永不过期、互斥锁 |
| 缓存雪崩 | 大量key同时过期,数据库压力剧增 | 过期时间加随机值、多级缓存 |
第十部分:消息队列——异步解耦
用RabbitMQ做什么?
订单创建后需要发通知、扣库存、写日志,这些操作可以异步执行,不用阻塞主流程。
Spring Boot集成RabbitMQ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
消息发送方
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private OrderRepository orderRepository;
@Transactional(rollbackFor = Exception.class)
public Order createOrder(CreateOrderDTO dto) {
// 1. 创建订单
Order order = new Order();
order.setProductId(dto.getProductId());
order.setQuantity(dto.getQuantity());
order.setTotalAmount(dto.getTotalAmount());
order.setStatus(OrderStatus.PENDING);
orderRepository.save(order);
// 2. 发送消息(异步通知库存扣减)
rabbitTemplate.convertAndSend(
"order.exchange",
"order.created",
order.getId()
);
return order;
}
}
消息接收方
@Component
@RabbitListener(queues = "order.created.queue")
public class OrderMessageConsumer {
@Autowired
private InventoryService inventoryService;
@RabbitHandler
public void handleOrderCreated(Long orderId) {
// 异步扣减库存
inventoryService.deductStock(orderId);
}
@RabbitHandler
public void handleOrderTimeout(Long orderId) {
// 超时订单处理
orderService.cancelOrder(orderId);
}
}
第十一部分:测试——保障代码质量
单元测试
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void testCreateUser() {
CreateUserDTO dto = new CreateUserDTO();
dto.setUsername("testuser");
dto.setEmail("test@example.com");
dto.setPassword("test123456");
User user = userService.create(dto);
assertNotNull(user.getId());
assertEquals("testuser", user.getUsername());
assertNotNull(user.getPassword()); // 密码已加密
}
@Test
void testFindById_NotFound() {
assertThrows(BusinessException.class, () -> {
userService.findById(999L);
});
}
}
Mock测试
@SpringBootTest
class UserControllerTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
void testGetUser() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/users/1"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.id").value(1));
}
@Test
void testCreateUser_InvalidParams() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"\"}"))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
第十二部分:部署与运维
打成JAR包运行
# 编译打包
mvn clean package -DskipTests
# 运行
java -jar target/demo-0.0.1-SNAPSHOT.jar
# 指定配置文件
java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
Docker部署
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
- redis
- rabbitmq
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: demo_db
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7-alpine
rabbitmq:
image: rabbitmq:3-management
学习路线建议
如果你是从零开始,建议按这个顺序学习:
Week 1-2:Java基础复习 + Maven/Gradle构建工具
↓
Week 3-4:Spring核心(IoC、DI、AOP、事务)
↓
Week 5-6:Spring MVC + 基础CRUD项目
↓
Week 7-8:Spring Boot入门 + JPA数据访问
↓
Week 9-10:Spring Security + JWT认证
↓
Week 11-12:Redis缓存 + 消息队列
↓
Week 13-14:项目实战(完整企业级项目)
↓
Week 15+:微服务、分布式等进阶内容
几个实战建议:
- 每学一个知识点,立刻动手写代码验证
- 找一个完整的项目跟着做,比如用户管理系统、博客系统、电商系统
- 学会看官方文档,Spring官方文档非常详细
- 不要死记硬背,理解原理更重要
- 多去GitHub上看开源项目,学习别人的代码风格
Spring生态非常庞大,这篇文章只是帮你打开了大门。真正掌握它需要大量的实践,遇到问题不要怕,Spring社区的文档和资源都非常丰富。加油,你在路上!
