Java开发框架Spring学习 零基础入门到实战:全面掌握IoC容器 AOP编程 Spring MVC与Spring Boot企业级项目开发全流程指南
嘿,朋友!欢迎来到Spring的世界。
我知道你现在可能正对着满屏的代码发愁,脑子里全是问号:这个@Autowired到底是啥?为什么我的Bean老是不好使?AOP听着很高深,到底能干嘛用?别慌,咱们一步一步来,我会用最直白的话,把这些概念掰开了揉碎了讲给你听。
先给你吃颗定心丸——Spring确实学起来需要点耐心,但一旦你掌握了它的核心思想,你会发现写代码变得特别自然,特别优雅。
一、先搞懂Spring到底是干嘛的
在你埋头学代码之前,咱们先聊聊”为什么”。
想象一下,你要建一个房子,需要水电工、木工、泥瓦匠……如果你自己既要接电线又要砌墙,这房子啥时候能建成?
早期的Java开发者就是这个状态。每个项目都要自己写数据库连接、自己管理事务、自己写日志,重复劳动多到让人怀疑人生。Spring的出现,就是为了解决这些问题。
它的核心思想可以用一句话概括:让开发者专注于业务,而不是基础架构。
Spring做了几件大事:
- IoC(控制反转):帮你管理对象,你不用再自己
new一大堆东西 - AOP(面向切面编程):帮你处理日志、事务、安全这些跟业务无关但必须做的事
- Spring MVC:帮你处理Web请求,写接口变得超简单
- Spring Boot:把这些东西打包好,一键启动,零配置也能跑起来
二、IoC容器——Spring的心脏
2.1 什么是IoC?
先来看一个不靠谱的例子:
// 这是一个糟糕的设计,你自己感受下
public class UserService {
private UserDao userDao = new UserDao(); // 问题在这里!
public void register(String username, String password) {
// 如果哪天要把数据库换成MySQL,怎么办?
// 如果要做单元测试,想mock一下userDao,怎么办?
userDao.save(username, password);
}
}
public class UserController {
private UserService userService = new UserService(); // 又一个new!
public void handleRegister(String username) {
userService.register(username, "123456");
}
}
你看看这段代码,有多痛苦?
每个类内部都硬编码了依赖,换什么都得改代码。测试的时候更是噩梦——你没法替换掉UserDao去模拟各种场景。
IoC(Inverse of Control,控制反转) 就是来解决这个问题的。
什么意思呢?就是——不再由你主动创建依赖对象,而是交给Spring容器来管理。 你需要什么,告诉Spring,它会帮你准备好。
2.2 用代码理解IoC
我们换个写法,看看IoC是怎么工作的:
// 1. 定义接口,解耦
public interface UserDao {
void save(String username, String password);
}
// 2. 实现类
@Repository // 告诉Spring:我是个组件,帮我管起来
public class MySqlUserDao implements UserDao {
@Override
public void save(String username, String password) {
System.out.println("保存到MySQL数据库: " + username);
}
}
// 3. UserService不再自己new,而是让Spring注入
@Service // 告诉Spring:我是个服务组件
public class UserService {
// 交给Spring来注入!不用自己new了
@Autowired // 自动装配
private UserDao userDao;
public void register(String username, String password) {
userDao.save(username, password);
}
}
// 4. Controller层
@RestController // Spring MVC的注解
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public String register(@RequestParam String username) {
userService.register(username, "123456");
return "注册成功";
}
}
2.3 理解依赖注入的两种方式
Spring提供了几种注入方式,咱们都看看:
// 方式一:字段注入(最常见)
@Service
public class OrderService {
@Autowired
private UserRepository userRepository;
}
// 方式二:构造器注入(推荐!更好测试)
@Service
public class OrderService {
private final UserRepository userRepository;
// Spring会自动调用这个构造器,把依赖传进来
@Autowired
public OrderService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
// 方式三:setter注入
@Service
public class OrderService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
为什么要推荐构造器注入? 因为它是”强制依赖”——对象创建的时候就必须有这些依赖,否则就不完整。而且方便单元测试,你可以直接传Mock对象进去:
// 单元测试时,传入Mock对象
UserRepository mockRepo = mock(UserRepository.class);
OrderService orderService = new OrderService(mockRepo);
2.4 Spring Bean的生命周期
理解IoC,还得知道Bean是怎么被管理起来的:
1. Spring启动,扫描所有@Annotation的类
2. 创建Bean实例(调用构造器)
3. 注入依赖(@Autowired)
4. 执行初始化方法(@PostConstruct)
5. Bean就绪,等待被使用
6. 应用关闭时,执行销毁方法(@PreDestroy)
@Component
public class MyService {
// 初始化后执行
@PostConstruct
public void init() {
System.out.println("Bean已创建并注入完成");
}
// 销毁前执行
@PreDestroy
public void destroy() {
System.out.println("Bean即将销毁");
}
}
2.5 Bean的作用域
Spring默认创建的Bean是单例(Singleton)的,整个应用只有一个实例。但有时候你需要别的:
@Component
@Scope("prototype") // 每次请求都创建新实例
public class RequestBean {
private int count = 0;
public int getCount() { return count++; }
}
@Component
@Scope("request") // 每个HTTP请求一个实例
public class RequestScopedBean {
// ...
}
@Component
@Scope("session") // 每个Session一个实例
public class SessionScopedBean {
// ...
}
| 作用域 | 说明 | 使用场景 |
|---|---|---|
| singleton(默认) | 整个应用一个实例 | 无状态的服务类 |
| prototype | 每次获取都创建新实例 | 需要保持状态的组件 |
| request | 每次HTTP请求一个实例 | Web层请求处理 |
| session | 每次Session一个实例 | 用户会话数据 |
三、AOP编程——让横切关注点不再烦恼
3.1 什么是AOP?
先说个场景。你的项目里有100个Service方法,每个方法都需要记录日志。没有AOP之前,你只能在每个方法里写log.info(),代码到处都是重复的东西。
AOP(Aspect-Oriented Programming,面向切面编程) 就是为了解决这个问题而生的。
它可以把”日志记录”、”事务管理”、”权限校验”这些横切关注点,从业务逻辑中分离出来,统一处理。
业务代码: 只做业务逻辑
public void transferMoney() { ... }
AOP切面: 统一处理横切逻辑
@Before -> 执行前记录日志
@After -> 执行后记录日志
@Around -> 环绕处理(事务管理)
3.2 用代码理解AOP
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect // 标记这是一个切面
public class LogAspect {
// 定义切点:匹配所有public方法
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceMethods() {}
// 前置通知:方法执行前
@Before("serviceMethods()")
public void beforeLog(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println(">>> 开始执行方法: " + methodName);
}
// 后置通知:方法执行后(无论是否异常)
@After("serviceMethods()")
public void afterLog(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("<<< 方法执行完毕: " + methodName);
}
// 返回通知:方法正常返回后
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturnLog(JoinPoint joinPoint, Object result) {
System.out.println("方法返回结果: " + result);
}
// 异常通知:方法抛出异常时
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void afterThrowLog(JoinPoint joinPoint, Exception ex) {
System.out.println("方法异常: " + ex.getMessage());
}
// 环绕通知:最强大!可以控制方法的执行
@Around("serviceMethods()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("开始计时...");
try {
Object result = joinPoint.proceed(); // 执行目标方法
long costTime = System.currentTimeMillis() - startTime;
System.out.println("耗时: " + costTime + "ms");
return result;
} catch (Exception e) {
System.out.println("方法异常,记录日志...");
throw e;
}
}
}
3.3 切入点表达式速查
// 匹配所有类的所有方法
execution(* *.*(..))
// 匹配com.example.service包下所有类的所有public方法
execution(public * com.example.service.*.*(..))
// 匹配com.example.service包及其子包下所有方法
execution(* com.example.service..*.*(..))
// 匹配所有以save开头的方法
execution(* save*(..))
// 匹配参数为String的方法
execution(* *(String))
// 匹配参数以String开头的方法
execution(* *(String,..))
3.4 AOP实战:用@Aspect实现权限校验
@Aspect
@Component
public class AuthAspect {
@Autowired
private UserService userService;
// 匹配所有标注了@RequireLogin的方法
@Around("@annotation(com.example.annotation.RequireLogin)")
public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable {
// 模拟获取当前用户(实际项目中从Session/Token获取)
String currentUser = getCurrentUser();
if (currentUser == null) {
throw new RuntimeException("请先登录!");
}
// 检查权限
if (!hasPermission(currentUser, joinPoint)) {
throw new RuntimeException("权限不足!");
}
// 权限通过,执行目标方法
return joinPoint.proceed();
}
private String getCurrentUser() {
// 从ThreadLocal或Session获取
return UserContext.getCurrentUser();
}
private boolean hasPermission(String user, JoinPoint joinPoint) {
// 检查用户是否有权限执行此方法
return true;
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireLogin {
}
四、Spring MVC——处理HTTP请求的利器
4.1 一个最简单的Controller
@RestController // 相当于 @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// GET请求:获取用户列表
@GetMapping("")
public List<User> getUsers() {
return userService.findAll();
}
// GET请求:获取单个用户
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// POST请求:创建用户
@PostMapping("")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
// PUT请求:更新用户
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
// DELETE请求:删除用户
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
4.2 请求参数绑定的多种方式
@RestController
public class OrderController {
// 1. 路径变量
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
// URL: /orders/123
}
// 2. 查询参数
@GetMapping("/orders/search")
public List<Order> searchOrders(
@RequestParam String status, // ?status=PAID
@RequestParam(defaultValue = "0") int page, // ?page=1
@RequestParam(defaultValue = "10") int size // ?size=20
) {
// URL: /orders/search?status=PAID&page=1&size=20
}
// 3. 请求体(JSON)
@PostMapping("/orders")
public Order createOrder(@RequestBody Order order) {
// 请求体: {"itemId": 1, "quantity": 2}
}
// 4. 表单提交
@PostMapping("/login")
public Result login(
@RequestParam String username,
@RequestParam String password
) {
// 表单提交或application/x-www-form-urlencoded
}
// 5. 复杂对象绑定
@PostMapping("/orders/batch")
public List<Order> batchCreate(@RequestBody List<Order> orders) {
// 请求体: [{"itemId":1,"quantity":2}, {"itemId":3,"quantity":1}]
}
}
4.3 统一响应格式
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}
@RestController
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public Result<?> handleUserNotFound(UserNotFoundException e) {
return Result.error(404, e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
return Result.error(500, "服务器内部错误");
}
}
4.4 拦截器——请求的”安检门”
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 请求到达Controller之前执行
String token = request.getHeader("Authorization");
if (!isValid(token)) {
response.setStatus(401);
return false; // 请求被拦截
}
// 把用户信息放到请求属性中,后续可以获取
request.setAttribute("currentUser", getUserFromToken(token));
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// Controller执行之后、视图渲染之前执行
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 整个请求结束之后执行
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 拦截的路径
.excludePathPatterns("/api/auth/login"); // 排除的路径
}
}
五、Spring Boot——让Spring开发变得简单
5.1 为什么要用Spring Boot?
传统Spring项目有很多痛点:
- XML配置文件满天飞
- 依赖管理混乱,版本冲突
- 需要部署到外部Tomcat
- 配置繁琐,启动慢
Spring Boot的解决方案是:约定优于配置。
传统Spring项目:
- 需要配置applicationContext.xml
- 需要配置spring-mvc.xml
- 需要配置web.xml
- 需要手动管理依赖版本
- 需要部署到Tomcat等容器
Spring Boot项目:
- 一个main方法启动
- 自动配置(你几乎不需要写配置)
- 内嵌Tomcat,直接运行
- starters简化依赖管理
5.2 第一个Spring Boot项目
创建一个最简单的Spring Boot应用:
// 主启动类
@SpringBootApplication // 组合注解:@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 一个简单的Controller
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}
5.3 pom.xml依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<dependencies>
<!-- Web开发 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库 -->
<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-test</artifactId>
<scope>test</scope>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
</project>
5.4 application.yml配置文件
spring:
application:
name: my-app
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# JPA配置
jpa:
hibernate:
ddl-auto: update # 自动创建/更新表结构
show-sql: true # 打印SQL语句
# 序列化配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
# 服务器端口
server:
port: 8080
# 日志配置
logging:
level:
com.example: DEBUG
org.springframework: INFO
5.5 自动配置的原理
Spring Boot最神奇的地方就是自动配置。它是这样工作的:
1. @SpringBootApplication 包含 @EnableAutoConfiguration
2. Spring Boot扫描 classpath 下的 jar 包
3. 根据你引入的 starter 依赖,自动配置相应的Bean
4. 你只需在 application.yml 中配置必要的参数
比如你引入了 spring-boot-starter-web:
- 自动配置嵌入式Tomcat
- 自动配置Spring MVC
- 自动配置Jackson序列化
你引入了 spring-boot-starter-data-jpa:
- 自动配置EntityManagerFactory
- 自动配置TransactionManager
- 自动配置Repository
六、完整项目实战——用户管理系统
6.1 项目结构
src/main/java/com/example/usermanager/
├── UserManagerApplication.java # 启动类
├── config/
│ └── WebMvcConfig.java # Web配置
├── controller/
│ └── UserController.java # 控制器
├── service/
│ ├── UserService.java # 服务接口
│ └── impl/
│ └── UserServiceImpl.java # 服务实现
├── repository/
│ └── UserRepository.java # 数据访问层
├── entity/
│ └── User.java # 实体类
├── dto/
│ ├── UserDTO.java # 数据传输对象
│ └── Result.java # 统一响应
├── aspect/
│ └── LogAspect.java # AOP切面
└── exception/
├── GlobalExceptionHandler.java # 全局异常处理
└── UserNotFoundException.java # 自定义异常
6.2 实体类
package com.example.usermanager.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false)
private String password;
@Column(length = 100)
private String email;
@Column(length = 20)
private String phone;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
public void prePersist() {
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updateTime = LocalDateTime.now();
}
}
6.3 数据访问层
package com.example.usermanager.repository;
import com.example.usermanager.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 根据用户名查找
Optional<User> findByUsername(String username);
// 检查用户名是否存在
boolean existsByUsername(String username);
// 根据邮箱查找
Optional<User> findByEmail(String email);
}
6.4 服务层
package com.example.usermanager.service;
import com.example.usermanager.entity.User;
import java.util.List;
public interface UserService {
List<User> findAll();
User findById(Long id);
User findByUsername(String username);
User save(User user);
User update(Long id, User user);
void delete(Long id);
}
package com.example.usermanager.service.impl;
import com.example.usermanager.entity.User;
import com.example.usermanager.repository.UserRepository;
import com.example.usermanager.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在: " + id));
}
@Override
public User findByUsername(String username) {
return userRepository.findByUsername(username)
.orElse(null);
}
@Override
@Transactional
public User save(User user) {
// 检查用户名是否已存在
if (userRepository.existsByUsername(user.getUsername())) {
throw new RuntimeException("用户名已存在");
}
return userRepository.save(user);
}
@Override
@Transactional
public User update(Long id, User user) {
User existing = findById(id);
existing.setEmail(user.getEmail());
existing.setPhone(user.getPhone());
return userRepository.save(existing);
}
@Override
@Transactional
public void delete(Long id) {
User user = findById(id);
userRepository.delete(user);
}
}
6.5 控制器层
package com.example.usermanager.controller;
import com.example.usermanager.dto.Result;
import com.example.usermanager.entity.User;
import com.example.usermanager.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping
public Result<List<User>> getUsers() {
return Result.success(userService.findAll());
}
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
return Result.success(userService.findById(id));
}
@PostMapping
public Result<User> createUser(@RequestBody User user) {
return Result.success(userService.save(user));
}
@PutMapping("/{id}")
public Result<User> updateUser(@PathVariable Long id,
@RequestBody User user) {
return Result.success(userService.update(id, user));
}
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return Result.success(null);
}
}
6.6 统一响应封装
package com.example.usermanager.dto;
import lombok.Data;
@Data
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}
6.7 AOP日志切面
package com.example.usermanager.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class LogAspect {
@Before("execution(* com.example.usermanager.controller.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
log.info(">>> 开始执行: {}", methodName);
}
@After("execution(* com.example.usermanager.controller.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
log.info("<<< 执行完毕: {}", methodName);
}
}
6.8 全局异常处理
package com.example.usermanager.exception;
import com.example.usermanager.dto.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public Result<?> handleRuntimeException(RuntimeException e) {
return Result.error(400, e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
return Result.error(500, "服务器内部错误");
}
}
6.9 启动类
package com.example.usermanager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserManagerApplication {
public static void main(String[] args) {
SpringApplication.run(UserManagerApplication.class, args);
}
}
七、项目运行与测试
7.1 启动项目
# 方式一:命令行启动
mvn spring-boot:run
# 方式二:IDEA直接运行 main 方法
# 方式三:打成jar包运行
mvn package
java -jar target/user-manager-1.0.0.jar
7.2 测试接口
# 获取用户列表
curl http://localhost:8080/api/users
# 创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"username":"zhangsan","password":"123456","email":"zhangsan@example.com"}'
# 获取单个用户
curl http://localhost:8080/api/users/1
# 更新用户
curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{"email":"newemail@example.com"}'
# 删除用户
curl -X DELETE http://localhost:8080/api/users/1
八、进阶学习路线
学完基础之后,你可以往这些方向深入:
8.1 Spring Security——安全认证
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll() // 登录接口放行
.anyRequest().authenticated() // 其他接口需要认证
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
8.2 缓存——Redis集成
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(CacheConfiguration.defaultCacheConfig())
.build();
}
}
@Service
public class UserServiceImpl implements UserService {
@Cacheable(value = "users", key = "#id")
@Override
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
@CacheEvict(value = "users", key = "#id")
@Override
public void delete(Long id) {
userRepository.deleteById(id);
}
}
8.3 消息队列——Kafka集成
@Component
public class MessageProducer {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void sendMessage(String topic, String message) {
kafkaTemplate.send(topic, message);
}
}
@Component
public class MessageConsumer {
@KafkaListener(topics = "user-events", groupId = "user-group")
public void consume(String message) {
System.out.println("收到消息: " + message);
}
}
九、常见问题排查
9.1 Bean找不到
NoSuchBeanDefinitionException: No qualifying bean of type 'UserService' available
检查:
- 是否添加了
@Service注解 - 包扫描路径是否正确
- 是否在同一个启动类的子包下
9.2 循环依赖
BeanCurrentlyInCreationException: Error creating bean with name 'xxx'
解决:
- 重构代码,打破循环依赖
- 使用
@Lazy延迟加载 - 改用构造器注入
9.3 事务不生效
@Transactional
public void method() { ... }
检查:
- 方法是否是
public - 是否是从外部类调用的(AOP代理问题)
- 是否在同一个类内部调用(自调用不生效)
9.4 端口被占用
BindException: Address already in use
解决:
server:
port: 8081 # 换个端口
或者找到占用端口的进程:
# Linux/Mac
lsof -i :8080
# Windows
netstat -ano | findstr :8080
十、学习建议
10.1 学习路径
第一阶段(1-2周):
- 理解IoC和DI的基本概念
- 能手写简单的Spring项目
- 理解Bean的生命周期
第二阶段(2-3周):
- 学习Spring MVC
- 能写RESTful接口
- 理解拦截器和异常处理
第三阶段(2-3周):
- 学习AOP
- 能写切面处理日志、事务
- 理解切入点表达式
第四阶段(持续):
- Spring Boot全家桶
- 安全、缓存、消息队列
- 微服务架构
10.2 实践建议
- 不要只看不动手——每个概念都要写代码验证
- 从小项目开始——先做用户管理系统,再做购物车,再做电商系统
- 看官方文档——spring.io 是最权威的参考资料
- 读优秀开源项目——看看别人是怎么写Spring代码的
- 遇到问题先看日志——99%的问题日志里都有线索
Spring是一个生态极其丰富的框架,学它就像打开了一扇大门,门后面是一个精彩的世界。不要急于求成,一步一步来,每个知识点都要动手实践。当你能够独立完成一个完整的项目时,你就真的掌握了Spring。
有问题随时来问,祝你学习顺利!
