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 MVCWeb MVC 框架(基于 Servlet)
Spring WebFlux响应式 Web 框架(基于 Reactor)
Spring Data数据访问抽象(JPA/Redis/MongoDB)
Spring Security认证授权
Spring Cloud微服务全家桶(网关/注册/配置)
Spring Batch批处理
Spring IntegrationEIP 集成模式

我们这一章聚焦 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 失效的坑

  1. 自调用 —— this.method() 走原始对象,绕过代理。要把方法放到另一个 Bean 调用,或注入自己。
  2. 方法非 public —— 默认只代理 public 方法。
  3. 异常被吞 —— try-catch 捕获异常不抛出,事务以为正常,不回滚。
  4. 异常类型不对 —— 默认只回滚 RuntimeException,受检异常不回滚。配 rollbackFor = Exception.class
  5. 类不是 Bean —— @Transactional 加在没被 Spring 管理的类上,没效果。

七、实战:模拟 Spring IoC + AOP

由于 Spring 框架无法在 Piston 运行,下面用纯 Java SE 模拟一个迷你 Spring——演示 IoC 容器、依赖注入、AOP 代理的核心机制。本地用真 Spring 时,原理完全一致。

Java · 在线运行

观察重点

  • 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 真正”开箱即用”。