Spring 是 Java 生态中最流行的应用框架,提供了 IoC 容器、AOP、MVC、事务管理等一系列功能,极大地简化了企业级应用开发。
Spring 核心模块
Spring 框架由多个模块组成,可以按需引入:
| 模块 | 说明 |
|---|---|
| spring-core | IoC 容器的核心基础 |
| spring-beans | BeanFactory,负责 Bean 的创建与管理 |
| spring-context | ApplicationContext,在 beans 基础上提供更丰富的功能 |
| spring-aop | 面向切面编程支持 |
| spring-webmvc | Spring MVC 框架 |
| spring-tx | 事务管理 |
| spring-data-jpa | JPA 集成 |
| spring-boot | 约定优于配置,自动装配 |
IoC(控制反转)与 DI(依赖注入)
什么是 IoC
传统模式下,对象自己负责创建所依赖的对象:
public class UserService {
// 自己 new,耦合度高
private UserRepository repo = new UserRepositoryImpl();
}
IoC(Inversion of Control)把对象的创建和依赖关系的管理交给 Spring 容器,对象只声明自己需要什么,由容器注入:
@Service
public class UserService {
private final UserRepository repo;
// Spring 自动注入
public UserService(UserRepository repo) {
this.repo = repo;
}
}
控制权从对象本身"反转"给了容器,这就是 IoC 的核心思想。
DI 的三种方式
1. 构造器注入(推荐)
@Service
public class OrderService {
private final UserRepository userRepo;
private final ProductRepository productRepo;
// 只有一个构造器时,@Autowired 可以省略
public OrderService(UserRepository userRepo, ProductRepository productRepo) {
this.userRepo = userRepo;
this.productRepo = productRepo;
}
}
优点:依赖可以声明为 final,不可变,便于测试,且在对象构造完成时依赖就已经就绪。
2. Setter 注入
@Service
public class NotificationService {
private EmailSender emailSender;
@Autowired
public void setEmailSender(EmailSender emailSender) {
this.emailSender = emailSender;
}
}
适合可选依赖,或需要在运行时动态替换实现的场景。
3. 字段注入(不推荐)
@Service
public class UserService {
@Autowired
private UserRepository userRepo; // 无法声明 final,不易单元测试
}
代码简洁,但无法使用 final,且绕过了容器直接实例化时依赖为 null,不推荐在生产代码中使用。
Bean 管理
Bean 的定义方式
注解方式(最常用)
@Component // 通用组件
@Service // 业务层
@Repository // 数据访问层(同时开启 JPA 异常转换)
@Controller // MVC 控制器
@RestController // = @Controller + @ResponseBody
Java Config 方式
@Configuration
public class AppConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public EmailSender emailSender() {
return new SmtpEmailSender();
}
}
XML 方式(遗留项目仍在使用)
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userRepository"/>
</bean>
Bean 的作用域
@Component
@Scope("singleton") // 默认,容器中只有一个实例
public class AppConfig { }
@Component
@Scope("prototype") // 每次 getBean() 都创建新实例
public class RequestProcessor { }
// Web 环境专用
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class RequestContext { }
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class UserSession { }
| 作用域 | 说明 |
|---|---|
| singleton | 默认,容器中唯一实例 |
| prototype | 每次注入 / getBean() 创建新实例 |
| request | 每次 HTTP 请求一个实例(Web 环境) |
| session | 每个 HTTP Session 一个实例(Web 环境) |
Bean 的生命周期
实例化
→ 属性注入
→ BeanNameAware / BeanFactoryAware / ApplicationContextAware
→ BeanPostProcessor#postProcessBeforeInitialization
→ @PostConstruct
→ InitializingBean#afterPropertiesSet
→ init-method
→ BeanPostProcessor#postProcessAfterInitialization
→ 【Bean 可用】
→ @PreDestroy
→ DisposableBean#destroy
→ destroy-method
→ 销毁
常用的生命周期钩子:
@Component
public class DataSourcePool {
@PostConstruct
public void init() {
// Bean 初始化完成后执行,适合做连接池预热
System.out.println("连接池初始化完成");
}
@PreDestroy
public void destroy() {
// 容器关闭前执行,适合做资源释放
System.out.println("释放连接池资源");
}
}
常用注解
注入与配置
// 按类型自动注入
@Autowired
private UserRepository userRepo;
// 当有多个同类型 Bean 时,按名称指定
@Autowired
@Qualifier("mysqlUserRepo")
private UserRepository userRepo;
// 等价于 @Autowired + @Qualifier,更简洁(JSR-250 标准)
@Resource(name = "mysqlUserRepo")
private UserRepository userRepo;
// 注入配置文件中的值
@Value("${app.name}")
private String appName;
// 注入时附带默认值
@Value("${app.timeout:30}")
private int timeout;
// 注入 SpEL 表达式
@Value("#{systemProperties['user.home']}")
private String userHome;
配置类常用注解
@Configuration // 标识配置类
@ComponentScan("com.example") // 指定扫描包
@PropertySource("classpath:app.properties") // 加载额外配置文件
@Import(SecurityConfig.class) // 导入其他配置类
public class AppConfig { }
条件装配(Spring Boot 常用)
// 配置项存在且为指定值时才装配
@ConditionalOnProperty(name = "feature.cache.enabled", havingValue = "true")
@Bean
public CacheManager cacheManager() {
return new RedisCacheManager(...);
}
// 容器中没有指定 Bean 时才装配(提供默认实现)
@ConditionalOnMissingBean(DataSource.class)
@Bean
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder().build();
}
// 指定类在 classpath 上时才装配
@ConditionalOnClass(RedisTemplate.class)
@Configuration
public class RedisAutoConfig { }
AOP(面向切面编程)
AOP 允许在不修改业务代码的情况下,统一处理横切关注点,如日志、权限校验、性能监控、事务等。
核心概念
| 概念 | 说明 |
|---|---|
| Aspect(切面) | 封装横切逻辑的类,用 @Aspect 标注 |
| Pointcut(切入点) | 定义在哪些方法上执行,用表达式描述 |
| Advice(通知) | 切入点触发时执行的代码 |
| JoinPoint(连接点) | 可以被切入的位置(方法执行、异常抛出等) |
| Weaving(织入) | 将切面逻辑应用到目标对象的过程 |
通知类型
@Aspect
@Component
public class LoggingAspect {
// 切入点表达式,匹配 service 包下所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 方法执行前
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("调用方法:" + joinPoint.getSignature().getName());
}
// 方法执行后(无论是否抛出异常)
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行完毕");
}
// 方法正常返回后
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("返回值:" + result);
}
// 方法抛出异常后
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void logAfterThrowing(Exception ex) {
System.out.println("异常:" + ex.getMessage());
}
// 环绕通知(最强大,可以控制方法是否执行)
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed(); // 执行目标方法
long elapsed = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature() + " 耗时:" + elapsed + "ms");
return result;
} catch (Exception e) {
System.out.println("发生异常:" + e.getMessage());
throw e;
}
}
}
切入点表达式
// 精确匹配某个方法
execution(public String com.example.service.UserService.findById(Long))
// 匹配某个类的所有方法
execution(* com.example.service.UserService.*(..))
// 匹配某个包及其子包的所有方法(.. 表示任意子包)
execution(* com.example.service..*.*(..))
// 匹配带有某个注解的方法
@annotation(com.example.annotation.Log)
// 匹配某个包内所有类型
within(com.example.service.*)
// 组合(排除 get 开头的方法)
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")
AOP 实战:统一操作日志
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String value() default "";
}
// 切面实现
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private LogService logService;
@Around("@annotation(operationLog)")
public Object record(ProceedingJoinPoint pjp, OperationLog operationLog) throws Throwable {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String operation = operationLog.value();
try {
Object result = pjp.proceed();
logService.save(username, operation, "SUCCESS");
return result;
} catch (Exception e) {
logService.save(username, operation, "FAILED: " + e.getMessage());
throw e;
}
}
}
// 使用注解
@Service
public class UserService {
@OperationLog("删除用户")
public void deleteUser(Long id) {
// 业务代码无需关心日志
}
}
Spring MVC
请求处理流程
客户端请求
→ DispatcherServlet(前端控制器)
→ HandlerMapping(找到对应 Controller 方法)
→ HandlerAdapter(执行 Controller 方法)
→ 返回 ModelAndView 或 @ResponseBody 数据
→ ViewResolver(如果是视图名,找到对应视图)
→ 渲染 → 响应客户端
Controller 常用注解
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// GET /api/users
@GetMapping
public List<User> list() {
return userService.findAll();
}
// GET /api/users/1
@GetMapping("/{id}")
public ResponseEntity<User> getById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// POST /api/users
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User create(@RequestBody @Valid CreateUserRequest req) {
return userService.create(req);
}
// PUT /api/users/1
@PutMapping("/{id}")
public User update(@PathVariable Long id, @RequestBody @Valid UpdateUserRequest req) {
return userService.update(id, req);
}
// DELETE /api/users/1
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
userService.delete(id);
}
// GET /api/users?page=0&size=10&keyword=Alice
@GetMapping("/search")
public Page<User> search(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword
) {
return userService.search(keyword, PageRequest.of(page, size));
}
}
参数绑定
// 路径参数
@GetMapping("/{id}")
public User get(@PathVariable Long id) { ... }
// 查询参数(带默认值)
@GetMapping
public List<User> list(
@RequestParam String name,
@RequestParam(defaultValue = "0") int page
) { ... }
// 请求体(JSON → 对象)
@PostMapping
public User create(@RequestBody CreateUserRequest req) { ... }
// 请求头
@GetMapping
public String auth(@RequestHeader("Authorization") String token) { ... }
// Cookie
@GetMapping
public String session(@CookieValue("sessionId") String sid) { ... }
// 查询参数自动绑定到对象(?name=Alice&age=25 → UserSearchRequest)
@GetMapping
public Page<User> search(UserSearchRequest req) { ... }
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(EntityNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult().getFieldErrors()
.stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.toList());
return new ErrorResponse("VALIDATION_FAILED", errors.toString());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneral(Exception ex) {
return new ErrorResponse("INTERNAL_ERROR", "服务器内部错误");
}
}
Spring Boot
自动装配原理
Spring Boot 通过 @SpringBootApplication 开启自动装配,它是三个注解的组合:
@SpringBootConfiguration // 等价于 @Configuration
@EnableAutoConfiguration // 核心:开启自动装配
@ComponentScan // 扫描当前包及子包
public @interface SpringBootApplication { }
@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中列出的所有自动配置类,再根据 @ConditionalOnXxx 注解判断条件是否成立,选择性地装配 Bean。
整个流程可以简化为:
启动
→ 扫描 AutoConfiguration.imports
→ 逐个检查 @ConditionalOnXxx 条件
→ 条件满足 → 注册 Bean
→ 条件不满足 → 跳过
配置文件
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: secret
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: false
server:
port: 8080
servlet:
context-path: /api
app:
jwt:
secret: my-secret-key
expiration: 86400
多环境配置
# application.yml
spring:
profiles:
active: dev # 默认激活 dev 环境
---
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:h2:mem:testdb
---
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-server:3306/mydb
绑定配置到类
@ConfigurationProperties(prefix = "app.jwt")
@Component
public class JwtProperties {
private String secret;
private long expiration;
// getters & setters
}
事务管理
基础使用
@Service
public class TransferService {
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepo.findById(fromId).orElseThrow();
Account to = accountRepo.findById(toId).orElseThrow();
from.deduct(amount);
to.add(amount);
accountRepo.save(from);
accountRepo.save(to);
// 方法正常结束 → 自动提交
// 抛出 RuntimeException → 自动回滚
}
}
事务传播行为
// REQUIRED(默认):有事务就加入,没有就新建
@Transactional(propagation = Propagation.REQUIRED)
// REQUIRES_NEW:总是新建事务,将当前事务挂起
@Transactional(propagation = Propagation.REQUIRES_NEW)
// SUPPORTS:有事务就加入,没有就以非事务方式运行
@Transactional(propagation = Propagation.SUPPORTS)
// NOT_SUPPORTED:以非事务方式运行,有事务则挂起当前事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// NEVER:不允许在事务中运行,有事务则抛出异常
@Transactional(propagation = Propagation.NEVER)
// MANDATORY:必须在事务中运行,没有事务则抛出异常
@Transactional(propagation = Propagation.MANDATORY)
// NESTED:嵌套事务(使用保存点),外层回滚会回滚内层,内层回滚不影响外层
@Transactional(propagation = Propagation.NESTED)
事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| READ_COMMITTED | ❌ 不会 | ✅ 可能 | ✅ 可能 |
| REPEATABLE_READ | ❌ 不会 | ❌ 不会 | ✅ 可能 |
| SERIALIZABLE | ❌ 不会 | ❌ 不会 | ❌ 不会 |
@Transactional(isolation = Isolation.READ_COMMITTED) // Oracle 默认
@Transactional(isolation = Isolation.REPEATABLE_READ) // MySQL InnoDB 默认
@Transactional(isolation = Isolation.SERIALIZABLE) // 最严格,性能最低
注意事项
@Service
public class UserService {
// ❌ 自调用无法触发事务(this 不是代理对象,AOP 代理失效)
public void outer() {
this.inner();
}
@Transactional
public void inner() { ... }
// ✅ 通过容器拿到代理对象再调用
@Autowired
private ApplicationContext context;
public void outer2() {
context.getBean(UserService.class).inner();
}
// ❌ 默认只回滚 RuntimeException,受检异常不会触发回滚
@Transactional
public void riskyOp() throws IOException {
// IOException 默认不回滚
}
// ✅ 显式指定所有异常都回滚
@Transactional(rollbackFor = Exception.class)
public void safeOp() throws IOException { ... }
// ❌ private 方法上的 @Transactional 无效(AOP 无法代理 private)
@Transactional
private void internalUpdate() { ... }
}
Spring Data JPA
Repository 层
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 方法命名约定,Spring 自动生成实现
List<User> findByEmail(String email);
List<User> findByAgeGreaterThan(int age);
Optional<User> findByEmailAndStatus(String email, UserStatus status);
boolean existsByEmail(String email);
long countByStatus(UserStatus status);
// 分页与排序
Page<User> findByStatus(UserStatus status, Pageable pageable);
List<User> findAll(Sort sort);
// 自定义 JPQL
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
List<User> search(@Param("keyword") String keyword);
// 原生 SQL
@Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
List<User> findCreatedAfter(@Param("date") LocalDate date);
// 更新 / 删除操作需要 @Modifying
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") UserStatus status);
}
实体定义
@Entity
@Table(name = "users", indexes = {
@Index(name = "idx_email", columnList = "email", unique = true)
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Enumerated(EnumType.STRING)
private UserStatus status;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
// 懒加载,避免 N+1 问题
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
}
常见问题与最佳实践
循环依赖
Spring Boot 2.6+ 默认禁止循环依赖,触发时会抛出 BeanCurrentlyInCreationException。
// ❌ 循环依赖(A 依赖 B,B 依赖 A)
@Service class A { @Autowired B b; }
@Service class B { @Autowired A a; }
// ✅ 方案 1:重新设计,提取公共逻辑到新的 C(推荐)
@Service class C { /* 共同逻辑 */ }
@Service class A { @Autowired C c; }
@Service class B { @Autowired C c; }
// ✅ 方案 2:@Lazy 延迟注入,打破初始化闭环
@Service class A {
@Autowired @Lazy B b;
}
// ✅ 方案 3:Setter 注入(不推荐,只是规避而非解决)
@Service class A {
private B b;
@Autowired
public void setB(B b) { this.b = b; }
}
多实例 Bean 的选择
// 两个 UserRepository 实现
@Component("mysqlUserRepo")
public class MysqlUserRepository implements UserRepository { ... }
@Component("redisUserRepo")
public class RedisUserRepository implements UserRepository { ... }
// 方式 1:@Qualifier 按名称指定
@Autowired
@Qualifier("mysqlUserRepo")
private UserRepository userRepo;
// 方式 2:@Primary 标记默认实现
@Primary
@Component
public class MysqlUserRepository implements UserRepository { ... }
// 方式 3:注入全部实现,自行选择
@Autowired
private List<UserRepository> allRepos;
@Autowired
private Map<String, UserRepository> repoMap; // key 是 bean name
常见坑
// 1. @Transactional 加在 private 方法上无效
@Transactional // ❌ 无效,AOP 无法代理 private
private void internalUpdate() { ... }
// 2. 静态方法上的注解无效(AOP 基于实例代理)
@Cacheable("users") // ❌ 无效
public static User staticGet(Long id) { ... }
// 3. prototype Bean 注入 singleton,每次拿到的是同一个实例
// ✅ 用 @Lookup 让 Spring 在每次调用时新建 prototype
@Component
public class SingletonBean {
@Lookup
public PrototypeBean getPrototype() {
return null; // Spring 会覆盖此方法,实际每次返回新实例
}
}
// 4. JPA 懒加载在 Session 关闭后访问报 LazyInitializationException
// ✅ 方案:在 Service 层使用 @Transactional 保持 Session,或使用 JOIN FETCH
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);
Fonnpo