Spring 核心
Spring 是 Java 后端的事实标准框架——2003 年由 Rod Johnson 发布,至今统治企业级 Java 开发。它解决了三个核心问题:
- IoC/DI —— 对象创建和依赖管理交给容器,不用自己
new。 - AOP —— 横切关注点(事务、日志、权限)从业务代码剥离。
- 模板封装 —— JDBC/事务/ORM 等样板代码封装好,让业务聚焦。
这一章我们看 Spring 的两大核心——IoC 和 AOP,理解它们的原理。
一、Spring 生态全景
Spring 不是单一框架,是个生态:
| 项目 | 用途 |
|---|---|
| Spring Framework | 核心(IoC/AOP/事务),所有项目基础 |
| Spring Boot | 自动配置 + 起步依赖,开箱即用 |
| Spring MVC | Web MVC 框架(基于 Servlet) |
| Spring WebFlux | 响应式 Web 框架(基于 Reactor) |
| Spring Data | 数据访问抽象(JPA/Redis/MongoDB) |
| Spring Security | 认证授权 |
| Spring Cloud | 微服务全家桶(网关/注册/配置) |
| Spring Batch | 批处理 |
| Spring Integration | EIP 集成模式 |
我们这一章聚焦 Spring Framework 的核心——IoC 和 AOP。
二、IoC 与 DI
2.1 传统方式 vs IoC
传统开发——对象自己 new 依赖:
public class UserService {
private UserRepository repo = new UserRepositoryImpl(); // 自己 new
private EmailService email = new EmailServiceImpl();
// 紧耦合: 换实现要改代码, 难测试
}
问题:
- 紧耦合 ——
UserService写死了UserRepositoryImpl,换实现要改代码。 - 难测试 —— 单元测试时想 mock
UserRepository做不到。 - 生命周期管理乱 —— 每个
new都自己管,对象爆炸。
IoC(Inversion of Control,控制反转)——把”创建对象”的控制权反转给容器:
public class UserService {
private final UserRepository repo; // 只声明, 不 new
private final EmailService email;
// 构造器注入: 容器注入依赖
public UserService(UserRepository repo, EmailService email) {
this.repo = repo;
this.email = email;
}
}
容器负责 new 对象、注入依赖、管理生命周期——这就是”控制反转”。DI(Dependency Injection,依赖注入)是 IoC 的实现方式——容器把依赖注入到对象里。
2.2 IoC 容器与 Bean
Spring 容器(ApplicationContext)管理一堆 Bean——Bean 就是被容器管理的对象。
声明 Bean 的方式:
// 1. 组件扫描: @Component 及其派生
@Component
public class UserRepositoryImpl implements UserRepository { ... }
@Service // @Component 的派生, 语义化
public class UserService { ... }
@Repository // @Component 的派生, 用于 DAO
public class UserDao { ... }
@Controller // @Component 的派生, 用于控制器
public class UserController { ... }
// 2. @Bean 方法: 在 @Configuration 类里
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource(...);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Component 系列——类上加注解,Spring 扫描包创建实例。@Bean——方法上加注解,方法返回值成为 Bean,适合引入第三方库的类(你改不了源码加不了 @Component)。
2.3 依赖注入三种方式
// 1. 构造器注入 (推荐!)
@Service
@RequiredArgsConstructor // Lombok 自动生成构造器
public class UserService {
private final UserRepository repo; // final, 不可变
private final EmailService email;
}
// 2. Setter 注入
@Service
public class UserService {
private UserRepository repo;
@Autowired
public void setRepo(UserRepository repo) { this.repo = repo; }
}
// 3. 字段注入 (不推荐)
@Service
public class UserService {
@Autowired
private UserRepository repo; // 不能 final, 难测试
}
为什么推荐构造器注入?
- 不可变 ——
final字段,注入后不能改,线程安全。 - 明确依赖 —— 一眼看出这个类需要什么。
- 易测试 —— 单元测试时直接
new UserService(mockRepo, mockEmail)。 - 避免空指针 —— 容器创建时就必须传齐依赖,不会出现”忘记注入”。
字段注入的反模式——不能 final、不能脱离 Spring 测试、循环依赖更隐蔽。Spring 6+ 已经不推荐字段注入。
2.4 Bean 作用域
| 作用域 | 含义 |
|---|---|
singleton(默认) | 整个容器一个实例 |
prototype | 每次注入都新建 |
request | 每个 HTTP 请求一个(Web 应用) |
session | 每个 HTTP Session 一个 |
application | 每个 ServletContext 一个 |
@Component
@Scope("prototype")
public class Counter { ... } // 每次注入都新对象
绝大多数 Bean 是 singleton——无状态服务、DAO、Repository。有状态的(用户购物车、会话数据)才用 prototype/session。
三、Bean 生命周期
Bean 从创建到销毁经历这些阶段:
1. 实例化 -- 容器调构造器 new 出对象
2. 属性赋值 -- 注入依赖 (@Autowired)
3. BeanNameAware / BeanFactoryAware 等感知接口
4. BeanPostProcessor.postProcessBeforeInitialization -- 初始化前回调
5. @PostConstruct / InitializingBean.afterPropertiesSet / init-method -- 初始化
6. BeanPostProcessor.postProcessAfterInitialization -- 初始化后回调 (AOP 代理在这!)
7. Bean 就绪 -- 可被使用
8. @PreDestroy / DisposableBean.destroy / destroy-method -- 销毁
@Component
public class MyBean {
@PostConstruct
public void init() {
System.out.println("Bean 初始化 (依赖已注入)");
}
@PreDestroy
public void cleanup() {
System.out.println("Bean 销毁前清理");
}
}
@PostConstruct 在依赖注入完成后执行——常用于校验配置、启动后台线程。@PreDestroy 在容器关闭前执行——释放资源。
3.1 AOP 代理与生命周期
AOP 代理是在 BeanPostProcessor.postProcessAfterInitialization 阶段生成的——容器发现 Bean 有 @Transactional/@Async 等注解,就把原始 Bean 包装成代理 Bean 注入。这就是为什么 @Transactional 自调用不生效——this.method() 走原始对象,绕过了代理。
四、注解配置
4.1 组件扫描
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig { ... }
@ComponentScan 让 Spring 扫描指定包下所有 @Component/@Service/@Repository/@Controller,自动注册为 Bean。Spring Boot 的 @SpringBootApplication 默认包含 @ComponentScan。
4.2 @Autowired
@Service
public class UserService {
@Autowired
private UserRepository repo; // 按类型注入
@Autowired
@Qualifier("userRepoImpl") // 多实现时按名字选
private UserRepository repo2;
}
// 多个 Bean 时, @Primary 标主选
@Primary
@Repository
public class UserRepoImpl implements UserRepository { ... }
@Autowired 默认按类型注入。多个实现时用 @Qualifier 指定名字,或 @Primary 标记默认实现。
4.3 @Bean 方法
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "app.datasource") // 从配置注入属性
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource ds) {
return new DataSourceTransactionManager(ds); // 参数 ds 自动注入
}
}
@Bean 方法可以带参数——参数自动从容器注入。这是引入第三方库类的标准方式。
五、AOP:面向切面编程
AOP(Aspect-Oriented Programming)——把”横切关注点”(事务、日志、权限、缓存)从业务代码剥离,用”切面”统一处理。
5.1 AOP 核心概念
| 术语 | 含义 | 例子 |
|---|---|---|
| 切面(Aspect) | 横切逻辑的模块化 | LogAspect 日志切面 |
| 连接点(Join Point) | 程序执行的点 | 方法调用 |
| 切入点(Pointcut) | 切面作用的位置(表达式) | execution(* com.example..*.*(..)) |
| 通知(Advice) | 在切入点做什么 | @Before/@After/@Around |
| 目标对象(Target) | 被代理的原始对象 | UserService |
| 代理(Proxy) | 包装目标的代理对象 | CGLIB/JDK 动态代理 |
| 织入(Weaving) | 把切面应用到目标 | 运行时(Spring)/ 编译时(AspectJ) |
5.2 五种通知
@Aspect
@Component
public class LogAspect {
// 切入点: 匹配 com.example.service 下所有类的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
public void serviceMethods() {}
// 1. 前置通知
@Before("serviceMethods()")
public void before(JoinPoint jp) {
System.out.println("[Before] " + jp.getSignature());
}
// 2. 后置通知 (无论是否异常都执行)
@After("serviceMethods()")
public void after(JoinPoint jp) {
System.out.println("[After] " + jp.getSignature());
}
// 3. 返回通知 (正常返回后)
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
System.out.println("[AfterReturning] " + jp.getSignature() + " -> " + result);
}
// 4. 异常通知
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void afterThrowing(JoinPoint jp, Exception ex) {
System.out.println("[AfterThrowing] " + jp.getSignature() + " throws " + ex);
}
// 5. 环绕通知 (最强, 控制是否执行原方法)
@Around("serviceMethods()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed(); // 执行原方法
return result;
} finally {
long cost = System.currentTimeMillis() - start;
System.out.println("[Around] " + pjp.getSignature() + " 耗时 " + cost + "ms");
}
}
}
@Around 是最强大的——能控制是否执行原方法、修改参数、修改返回值、捕获异常。@Transactional 就是 @Around 实现的——try { proceed(); commit(); } catch { rollback(); }。
5.3 切入点表达式
// execution(修饰符? 返回类型 包名.类名.方法名(参数类型) 异常?)
execution(public * com.example.service.*.*(..)) // service 包所有类的 public 方法
execution(* com.example..*.*(..)) // com.example 及子包所有方法
execution(* UserService.*(..)) // UserService 所有方法
execution(* UserService.find*(..)) // UserService 所有 find 开头方法
execution(* save*(String, ..)) // save 开头, 第一参 String, 后面任意
// @annotation: 匹配带某注解的方法
@Pointcut("@annotation(com.example.Loggable)")
public void loggableMethods() {}
// @within: 匹配带某注解的类
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceClasses() {}
5.4 AOP 实现:动态代理
Spring AOP 底层就是上一阶段学的动态代理:
- 类实现了接口 → JDK 动态代理
- 类没实现接口 → CGLIB(生成子类)
- Spring Boot 2.0+ 默认 CGLIB
第十阶段我们手写过动态代理 AOP——Spring AOP 的本质就是把它做规范、做强大。
六、Spring 事务管理
事务是 AOP 的典型应用——@Transactional 让方法自动包在事务里。
6.1 编程式 vs 声明式
// 编程式 (手动管, 不推荐)
@Transactional
public void transfer(Long from, Long to, BigDecimal amount) {
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
txManager.commit(status);
} catch (Exception e) {
txManager.rollback(status);
throw e;
}
}
// 声明式 (推荐, @Transactional 一行搞定)
@Transactional
public void transfer(Long from, Long to, BigDecimal amount) {
accountDao.debit(from, amount); // 自动在事务里
accountDao.credit(to, amount);
}
@Transactional 本质是个 AOP 切面——@Around 把方法包在”开启事务→执行→提交/回滚”里。
6.2 @Transactional 配置
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.DEFAULT, // 隔离级别
timeout = 30, // 超时 (秒)
readOnly = false, // 只读事务
rollbackFor = Exception.class, // 哪些异常回滚
noRollbackFor = BusinessException.class // 哪些异常不回滚
)
public void doSomething() { ... }
传播行为(Propagation) ——方法嵌套调用时事务怎么传播:
| 传播 | 行为 |
|---|---|
REQUIRED(默认) | 有事务加入,没事务新建 |
REQUIRES_NEW | 总是新建,挂起当前事务 |
NESTED | 嵌套事务(Savepoint) |
SUPPORTS | 有事务加入,没事务非事务执行 |
NOT_SUPPORTED | 非事务执行,挂起当前事务 |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须无事务,否则抛异常 |
隔离级别(Isolation) ——并发事务互相影响程度:
| 隔离 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
READ_UNCOMMITTED | 可能 | 可能 | 可能 |
READ_COMMITTED | 不可能 | 可能 | 可能 |
REPEATABLE_READ(MySQL 默认) | 不可能 | 不可能 | 可能 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 |
6.3 @Transactional 失效的坑
- 自调用 ——
this.method()走原始对象,绕过代理。要把方法放到另一个 Bean 调用,或注入自己。 - 方法非 public —— 默认只代理 public 方法。
- 异常被吞 ——
try-catch捕获异常不抛出,事务以为正常,不回滚。 - 异常类型不对 —— 默认只回滚
RuntimeException,受检异常不回滚。配rollbackFor = Exception.class。 - 类不是 Bean ——
@Transactional加在没被 Spring 管理的类上,没效果。
七、实战:模拟 Spring IoC + AOP
由于 Spring 框架无法在 Piston 运行,下面用纯 Java SE 模拟一个迷你 Spring——演示 IoC 容器、依赖注入、AOP 代理的核心机制。本地用真 Spring 时,原理完全一致。
观察重点:
UserService没写new,依赖被容器自动注入——这就是 IoC/DI 的核心。- Bean 默认单例——两次
getBean返回同一对象。- AOP 代理在事务前后插逻辑——
@Transactional的本质就是这个。- 异常时事务回滚——AOP 捕获异常触发回滚。
- JDK 动态代理要接口——没接口的类 Spring 用 CGLIB。
八、本章小结
| 概念 | 核心要点 |
|---|---|
| IoC | 控制反转,对象创建交给容器 |
| DI | 依赖注入,容器把依赖注入对象 |
| Bean | 容器管理的对象,默认 singleton |
@Component 系列 | 声明 Bean,组件扫描注册 |
@Autowired | 注入依赖,推荐构造器注入 |
@Bean | 方法返回值成为 Bean |
| Bean 生命周期 | 实例化 → 注入 → 初始化 → 使用 → 销毁 |
| AOP | 切面/切入点/通知 |
| 五种通知 | Before/After/AfterReturning/AfterThrowing/Around |
@Transactional | 声明式事务,AOP 实现 |
记忆口诀:
- IoC 反转控制权——对象不自己 new,容器管。
- 构造器注入最稳——final、易测、明确依赖。
- Bean 默认单例——别有可变状态字段。
- AOP 五通知——Before 前、After 后、Returning 正常、Throwing 异常、Around 包裹。
@Transactional是 AOP——自调不生效、只代理 public、异常要抛。- JDK 代理要接口,CGLIB 走继承——Spring Boot 默认 CGLIB。
结语:从 Servlet 到 Spring
这一章我们看了 Spring 的两大核心——IoC 让对象管理解耦,AOP 让横切逻辑剥离。Spring 把第十二阶段的”反射 + 动态代理 + 注解”做成了企业级框架。下一章看 Spring Boot——让 Spring 真正”开箱即用”。