Skip to content

Spring 从零到生产级掌握

Spring 不能只背“IOC、AOP”。商业项目里真正要会的是:一个类为什么能变成 Bean,Bean 什么时候创建,依赖怎么注入,为什么有时拿到的是代理对象,事务为什么会失效,扩展点为什么能支撑 Spring Boot 自动配置、MyBatis Mapper、Feign Client 这些能力。

一句话建立主线:

Spring 是一个以 IOC 容器为核心、以扩展点为骨架、以 AOP 代理为增强手段的企业应用基础框架。

如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 Spring 从零到精通验收清单 逐项验收。它把 BeanDefinition、Bean 生命周期、依赖注入、循环依赖、AOP、声明式事务、编程式事务、扩展点、商业场景和故障排查拆成可检查的问题。

学习目标

学完这一页,你要能做到:

  1. 解释 Spring 解决了什么问题,不只是会写 @Service@Autowired
  2. 解释 BeanDefinition、BeanFactory、ApplicationContext、单例池之间的关系。
  3. 解释一个 Bean 从扫描、注册、实例化、依赖注入、初始化、代理到销毁的完整过程。
  4. 解释字段注入、构造器注入、Setter 注入的差异和生产建议。
  5. 解释循环依赖为什么只解决部分场景,为什么构造器循环依赖解决不了。
  6. 解释 AOP 为什么依赖代理,为什么同类自调用失效。
  7. 解释声明式事务和编程式事务的原理、差异、失效原因。
  8. 解释 Spring 常见扩展点如何改变 Bean 定义、Bean 对象、配置导入和事件流转。

学习路线

mermaid
flowchart TD
    A["为什么需要 Spring"] --> B["BeanDefinition<br/>把类变成定义"]
    B --> C["BeanFactory<br/>根据定义创建对象"]
    C --> D["依赖注入<br/>把对象组装起来"]
    D --> E["Bean 生命周期<br/>初始化和销毁"]
    E --> F["BeanPostProcessor<br/>修改或包装 Bean"]
    F --> G["AOP 代理<br/>事务、日志、权限"]
    G --> H["扩展点<br/>Boot、MyBatis、Feign"]
    H --> I["生产排查<br/>注入失败、代理失效、事务失效"]

这张图不要横着做很宽,因为用户真正需要的是按学习顺序一步步理解。

第一步:为什么需要 Spring

没有 Spring 时,业务对象通常由业务代码自己创建:

java
public class OrderService {
    private final InventoryService inventoryService = new InventoryService();
    private final PayService payService = new PayService();
}

这样写能跑,但项目一复杂就会出问题:

问题具体表现后果
对象创建分散每个类都自己 new 依赖替换实现困难,测试困难
依赖关系隐藏看构造逻辑才能知道依赖什么代码维护成本高
初始化不可控连接池、线程池、客户端到处初始化资源泄漏或重复创建
横切逻辑侵入每个方法都写日志、事务、权限业务代码变脏,重复代码多

Spring 的做法是:

  1. 业务类只声明“我需要什么”。
  2. 容器负责“创建对象、注入依赖、管理生命周期”。
  3. 横切能力通过代理在方法外层统一增强。
java
@Service
public class OrderService {
    private final InventoryService inventoryService;
    private final PayService payService;

    public OrderService(InventoryService inventoryService, PayService payService) {
        this.inventoryService = inventoryService;
        this.payService = payService;
    }
}

为什么推荐构造器注入:依赖关系在构造方法中一眼可见,对象创建后依赖不可变,单元测试也可以直接传入假实现。

第二步:BeanDefinition 是什么

Spring 不是扫描到一个类就立刻创建对象。它会先把类解析成 BeanDefinition。

BeanDefinition 可以理解为“创建 Bean 的说明书”。

信息例子作用
beanClassOrderService.class后续根据它实例化对象
scopesingleton、prototype决定单例还是多例
constructor args构造参数决定构造器注入
property values属性依赖决定 Setter 或字段注入
init/destroy method初始化和销毁方法管理生命周期
lazyInit是否懒加载决定启动时是否创建
primary/qualifier候选优先级多实现时选择哪个 Bean

完整流程:

mermaid
flowchart TD
    A["扫描 @ComponentScan 路径"] --> B["发现 @Component/@Service"]
    B --> C["读取注解和类信息"]
    C --> D["生成 BeanDefinition"]
    D --> E["注册到 BeanDefinitionMap"]
    E --> F["后续按定义创建 Bean"]

如果没有 BeanDefinition,Spring 就无法在创建对象前统一处理作用域、懒加载、条件注册、代理、属性依赖等信息。

第三步:BeanFactory 和 ApplicationContext

BeanFactory 是基础 IOC 容器,核心职责是保存 BeanDefinition、创建 Bean、获取 Bean。

ApplicationContext 是更完整的应用上下文,在 BeanFactory 之上增加:

能力说明
国际化MessageSource
事件机制ApplicationEventPublisher
资源加载ResourceLoader
环境配置Environment、Profile、PropertySource
自动创建单例启动时预实例化非懒加载单例 Bean

面试里不要只答“ApplicationContext 是 BeanFactory 子接口”。更完整的回答是:

BeanFactory 是 Bean 容器的底层抽象,负责 Bean 的定义、创建和获取。ApplicationContext 是面向应用的上下文,在 BeanFactory 基础上增加事件、资源、环境、国际化等企业应用能力,并通常会在启动时创建非懒加载单例 Bean。

第四步:一个 Bean 的完整生命周期

Bean 生命周期是 Spring 原理的中心。很多问题都能放到这个流程里定位:注入失败、循环依赖、AOP 代理、事务失效、初始化顺序。

mermaid
flowchart TD
    A["BeanDefinition 已注册"] --> B["实例化<br/>调用构造器"]
    B --> C["属性填充<br/>依赖注入"]
    C --> D["Aware 回调<br/>拿容器资源"]
    D --> E["BeanPostProcessor 前置"]
    E --> F["初始化<br/>@PostConstruct/afterPropertiesSet"]
    F --> G["BeanPostProcessor 后置"]
    G --> H["可能生成代理对象"]
    H --> I["放入单例池给业务使用"]
    I --> J["容器关闭时销毁"]

每一步做什么:

阶段做什么典型扩展
BeanDefinition 注册保存 Bean 的元信息ImportBeanDefinitionRegistrar
实例化调构造器创建原始对象InstantiationAwareBeanPostProcessor
属性填充注入依赖对象和值@Autowired@Resource
Aware注入 Spring 容器相关对象ApplicationContextAware
前置后处理初始化前处理 BeanBeanPostProcessor#postProcessBeforeInitialization
初始化执行业务初始化逻辑@PostConstructInitializingBean
后置后处理初始化后处理 BeanAOP 在这里创建代理
销毁释放资源@PreDestroyDisposableBean

注意:AOP 代理通常是在初始化后由 BeanPostProcessor 包装出来的。因此最终放入单例池的可能不是原始对象,而是代理对象。

第五步:依赖注入怎么选择

Spring 常见注入方式:

方式写法优点缺点建议
构造器注入构造方法参数依赖清晰、不可变、易测试参数太多时暴露类职责过重推荐
Setter 注入setter 方法可选依赖友好对象可能处于半初始化状态可选依赖使用
字段注入字段上 @Autowired写法短隐藏依赖、测试不便不推荐生产核心代码

多实现时如何选择:

java
public interface SmsClient {
    void send(String phone, String content);
}

@Service("aliyunSmsClient")
public class AliyunSmsClient implements SmsClient {
    public void send(String phone, String content) {}
}

@Service("tencentSmsClient")
public class TencentSmsClient implements SmsClient {
    public void send(String phone, String content) {}
}
java
@Service
public class NoticeService {
    private final SmsClient smsClient;

    public NoticeService(@Qualifier("aliyunSmsClient") SmsClient smsClient) {
        this.smsClient = smsClient;
    }
}

如果不指定,Spring 发现一个接口有多个候选 Bean,就可能报 NoUniqueBeanDefinitionException

第六步:循环依赖为什么只解决部分场景

循环依赖例子:

java
@Service
public class AService {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

Spring 单例 Bean 有三级缓存思路:

缓存保存什么作用
一级缓存完整单例 Bean正常获取 Bean
二级缓存早期 Bean 引用暂时解决依赖
三级缓存ObjectFactory需要时生成早期引用,兼容 AOP

简化流程:

mermaid
flowchart TD
    A["创建 A 原始对象"] --> B["A 放入三级缓存工厂"]
    B --> C["A 注入 B,开始创建 B"]
    C --> D["B 需要 A"]
    D --> E["从三级缓存拿 A 的早期引用"]
    E --> F["B 创建完成"]
    F --> G["回到 A,注入完成"]
    G --> H["A 初始化完成并放入一级缓存"]

为什么构造器循环依赖解决不了:

java
public AService(BService bService) {}
public BService(AService aService) {}

构造器注入要求“创建 A 之前必须先有 B,创建 B 之前必须先有 A”。对象连原始实例都没有,无法提前暴露引用。

生产建议:不要把循环依赖当能力用。出现循环依赖通常说明职责边界混乱,可以提取编排服务、领域事件、接口回调或中间状态表解耦。

第七步:AOP 为什么依赖代理

AOP 解决的是横切逻辑问题,比如事务、日志、权限、监控、审计。

没有 AOP:

java
public void pay() {
    log.info("start");
    beginTransaction();
    try {
        doPay();
        commit();
    } catch (Exception e) {
        rollback();
        throw e;
    }
}

业务代码被横切逻辑污染。

Spring AOP 的方式:

mermaid
flowchart TD
    A["调用方"] --> B["代理对象"]
    B --> C["前置增强<br/>日志/事务开始"]
    C --> D["目标方法"]
    D --> E["后置增强<br/>提交/记录耗时"]
    D --> F["异常增强<br/>回滚/告警"]

JDK 动态代理和 CGLIB:

方式原理适合场景限制
JDK 动态代理基于接口生成代理类目标类实现接口必须有接口
CGLIB基于继承生成子类没有接口的类final 类或方法不能增强

自调用为什么失效:

java
@Service
public class OrderService {
    public void outer() {
        inner(); // this.inner(),没有经过代理对象
    }

    @Transactional
    public void inner() {}
}

outer() 内部调用 inner() 本质是 this.inner(),绕过了代理对象,事务拦截器没有机会执行。

第八步:Spring 事务完整原理

声明式事务:

java
@Service
public class AssetService {
    @Transactional
    public void createAsset(Asset asset) {
        assetMapper.insert(asset);
        assetLogMapper.insert(asset.toLog());
    }
}

底层流程:

mermaid
flowchart TD
    A["调用代理方法"] --> B["TransactionInterceptor"]
    B --> C["读取 @Transactional 属性"]
    C --> D["获取连接并关闭自动提交"]
    D --> E["执行目标方法"]
    E --> F["正常返回"]
    F --> G["提交事务"]
    E --> H["抛出需回滚异常"]
    H --> I["回滚事务"]
    G --> J["释放连接"]
    I --> J

声明式事务和编程式事务:

类型写法原理适用场景
声明式事务@TransactionalAOP 代理包裹方法常规 Service 写操作
编程式事务TransactionTemplate代码主动控制边界批处理分批提交、局部事务、异常要转返回值

编程式事务 Demo:

java
@Service
public class ImportService {
    private final TransactionTemplate transactionTemplate;
    private final AssetMapper assetMapper;

    public ImportService(TransactionTemplate transactionTemplate, AssetMapper assetMapper) {
        this.transactionTemplate = transactionTemplate;
        this.assetMapper = assetMapper;
    }

    public void importOne(Asset asset) {
        transactionTemplate.executeWithoutResult(status -> {
            assetMapper.insert(asset);
            if (asset.getCode() == null) {
                status.setRollbackOnly();
            }
        });
    }
}

事务失效高频原因:

原因为什么失效怎么修
同类自调用绕过代理对象拆到另一个 Bean,或通过代理调用
方法不是 public默认代理无法按预期增强事务方法用 public
异常被 catch代理看到正常返回重新抛出或 setRollbackOnly()
checked exception默认只回滚 RuntimeException/Error配置 rollbackFor = Exception.class
新线程执行事务上下文绑定当前线程不要跨线程共享事务,改为消息/任务补偿
数据库不支持事务比如 MyISAM使用 InnoDB

第九步:核心扩展点怎么理解

Spring 强大不是因为内置所有能力,而是它提供了很多扩展点。

mermaid
flowchart TD
    A["BeanDefinition 阶段"] --> B["BeanDefinitionRegistryPostProcessor"]
    A --> C["BeanFactoryPostProcessor"]
    D["Bean 实例阶段"] --> E["BeanPostProcessor"]
    D --> F["FactoryBean"]
    G["配置导入阶段"] --> H["ImportSelector"]
    G --> I["ImportBeanDefinitionRegistrar"]
    J["运行期解耦"] --> K["ApplicationEvent"]

常见扩展点:

扩展点发生阶段能做什么商业项目例子
BeanFactoryPostProcessorBean 实例化前修改 BeanDefinition统一修改配置属性
BeanDefinitionRegistryPostProcessorBeanDefinition 注册阶段新增 BeanDefinition扫描自定义 Mapper
BeanPostProcessorBean 初始化前后包装 Bean、生成代理AOP、事务、异步、缓存
FactoryBean创建复杂对象暴露产品对象而不是工厂MyBatis Mapper 代理
ImportSelector配置导入按条件导入配置类Spring Boot 自动配置
ImportBeanDefinitionRegistrar注册 BeanDefinition手动注册复杂 BeanFeign Client、Mapper
ApplicationListener事件监听解耦业务动作订单完成后发通知

扩展点 Demo:事件解耦

java
public record AssetCreatedEvent(Long assetId) {}
java
@Service
public class AssetService {
    private final ApplicationEventPublisher publisher;

    public AssetService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void createAsset(Long assetId) {
        // 1. 写资产表
        // 2. 发布事件,让通知、审计、搜索同步各自处理
        publisher.publishEvent(new AssetCreatedEvent(assetId));
    }
}
java
@Component
public class AssetAuditListener {
    @EventListener
    public void onAssetCreated(AssetCreatedEvent event) {
        System.out.println("record audit log: " + event.assetId());
    }
}

如果所有逻辑都写在 createAsset() 里,资产创建会越来越臃肿。事件可以让“主流程”和“后置动作”解耦。但也要注意:默认同步事件仍在当前线程执行,耗时任务应考虑异步事件或消息队列。

第十步:商业项目怎么落地

以医疗数据采集与资产平台为例:

业务场景Spring 能力为什么这样用
多数据源采集IOC + 策略接口不同数据库、接口、文件采集实现可替换
采集批次落库声明式事务批次状态和明细一致提交
失败补偿记录REQUIRES_NEW 或编程式事务主事务回滚时仍保留失败日志
数据清洗规则Bean 集合注入多个规则按顺序执行
审计日志AOP 或事件不侵入业务主流程
搜索同步事件 + MQ主库写成功后异步同步 ES
自定义 starterImportSelector + 条件注解公司统一日志、鉴权、监控组件

集合注入规则 Demo:

java
public interface CleanRule {
    int order();
    Asset clean(Asset asset);
}

@Service
public class CleanPipeline {
    private final List<CleanRule> rules;

    public CleanPipeline(List<CleanRule> rules) {
        this.rules = rules.stream()
                .sorted(Comparator.comparingInt(CleanRule::order))
                .toList();
    }

    public Asset clean(Asset asset) {
        Asset result = asset;
        for (CleanRule rule : rules) {
            result = rule.clean(result);
        }
        return result;
    }
}

这就是 Spring 在商业项目中的价值:业务代码面向接口和规则扩展,而不是到处写 if elsenew

生产排查总流程

注入失败怎么查

mermaid
flowchart TD
    A["启动报注入失败"] --> B["Bean 是否被扫描到"]
    B --> C["包路径是否在 ComponentScan 下"]
    C --> D["是否有多个候选 Bean"]
    D --> E["是否需要 @Qualifier/@Primary"]
    E --> F["是否条件注解不满足"]
    F --> G["查看启动日志和 BeanDefinition"]

代理或事务失效怎么查

mermaid
flowchart TD
    A["事务/切面没生效"] --> B["调用是否经过 Spring Bean"]
    B --> C["是否同类自调用"]
    C --> D["方法是否 public"]
    D --> E["异常是否被 catch"]
    E --> F["是否多线程切换"]
    F --> G["数据库是否支持事务"]

Bean 创建异常怎么查

现象优先看什么
NoSuchBeanDefinitionExceptionBean 没注册,扫描路径、条件注解、配置类
NoUniqueBeanDefinitionException多个候选 Bean,@Primary@Qualifier
BeanCurrentlyInCreationException循环依赖,尤其构造器循环依赖
BeanCreationException构造器、初始化方法、依赖 Bean 异常
事务没回滚异常类型、自调用、catch、线程、数据库引擎

常见坑

后果正确做法
所有地方字段注入依赖隐藏,测试困难核心业务用构造器注入
过度依赖循环依赖设计耦合严重拆服务、提接口、事件解耦
在构造器里做复杂逻辑依赖未完全就绪放到初始化方法或启动监听
事务方法 private代理无法增强使用 public Service 方法
catch 异常不抛事务提交脏数据重新抛出或标记回滚
事件里做耗时操作主流程变慢异步事件或 MQ
扩展点乱用启动链路难排查只在框架集成和基础设施层使用

面试标准回答

Spring 的核心是什么

Spring 的核心是 IOC 和 AOP。IOC 负责对象创建、依赖注入和生命周期管理,让业务代码不再自己维护复杂依赖。AOP 基于代理对象增强方法调用,用来实现事务、日志、权限、监控等横切能力。它们背后依赖 BeanDefinition、BeanFactory、BeanPostProcessor、代理机制和事务拦截器等基础设施。

Bean 生命周期怎么说

Bean 通常经历 BeanDefinition 注册、实例化、依赖注入、Aware 回调、BeanPostProcessor 前置处理、初始化方法、BeanPostProcessor 后置处理、可能创建代理、放入单例池、容器关闭时销毁。AOP 代理一般就是后置处理器在初始化之后包装出来的。

Spring 事务为什么会失效

Spring 声明式事务基于 AOP 代理。事务失效通常是因为调用没有经过代理,比如同类自调用;或者方法不是 public;或者异常被 catch 后没有抛出;或者抛出 checked exception 但没配置 rollbackFor;或者事务代码跑到了新线程;也可能是数据库表本身不支持事务。

Spring 扩展点怎么回答

可以按阶段回答:BeanDefinition 阶段有 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor;Bean 实例阶段有 BeanPostProcessorFactoryBean;配置导入阶段有 ImportSelectorImportBeanDefinitionRegistrar;运行期解耦有 ApplicationEventApplicationListener。Spring Boot 自动配置、MyBatis Mapper、Feign Client 本质上都大量利用这些扩展点接入 Spring 容器。

关联知识点

本章小结

Spring 学习不要停在注解用法。你要把它看成一条链:类被扫描成 BeanDefinition,容器根据定义创建 Bean,依赖注入把对象组装起来,生命周期扩展点允许框架介入,AOP 代理提供事务和横切增强。能把这条链讲清楚,IOC、AOP、事务、循环依赖、自动配置和各种框架集成才会真正串起来。