一、起源:Spring 解决什么问题
Spring 诞生于 EJB 2.x 主导的 Java EE 时代。当时的企业级开发存在四个根本矛盾:
| 矛盾 | EJB 2.x 的实现方式 | 实际痛点 |
|---|---|---|
| 业务代码的侵入性 | 业务类必须实现 EJB 接口(SessionBean、EntityBean),继承容器基类 | 业务代码与容器强绑定,脱离 EJB 容器无法独立运行,单元测试需要启动完整容器,耗时可达数十分钟 |
| 依赖关系的管理 | 依赖通过 JNDI 查找写在业务方法内部,如 ctx.lookup("jdbc/MyDB") | 类之间的依赖关系散落在代码各处,耦合关系不可见,修改依赖需要改动业务代码并重新编译 |
| 横切逻辑的重复 | 事务开启提交、安全校验、日志记录等逻辑以模板代码方式写入每个方法 | 同一横切关注点散布在数百个业务方法中,新增一个监控维度需要批量修改代码 |
| 配置与运行的复杂度 | ejb-jar.xml 配合各厂商专有描述符,类加载层级深 | 新手入门周期长,排障需要理解容器内部机制,小团队难以承受维护成本 |
Spring 针对上述矛盾给出的对应解法是两个核心思想:
- 控制反转(IoC) 与 依赖注入(DI):将对象的创建权与依赖关系从业务代码剥离,交给容器管理,使业务对象回归纯粹的 POJO(Plain Old Java Object),不依赖任何框架 API。
- 面向切面编程(AOP):将散布在业务方法中的横切逻辑提取为独立模块,以声明方式织入目标方法,业务方法不再感知切面的存在。
二、控制反转与依赖注入
2.1 IoC 的本质
IoC(Inversion of Control)不是一种具体技术,而是一种设计思想:对象不再主动创建自己的依赖,而是声明所需依赖的抽象,由外部实体把依赖注入进来。控制权从业务代码侧转移到了容器侧,故称"控制反转"。
传统方式与 IoC 方式的对比:
传统方式(对象自己找依赖):
UserService(调用方)
└── new UserDao() ──→ 直接创建具体实现,耦合 UserDao
IoC 方式(容器送来依赖):
容器(持有所有对象实例)
├── UserService(仅声明:我需要一个 UserDao)
└── UserDao(容器先创建它,再注入给 UserService)
2.2 DI 的三种注入方式
DI(Dependency Injection)是 IoC 思想的具体实现手段。注入方式分三种:
| 注入方式 | 执行机制 | 适用定位 |
|---|---|---|
| 构造器注入 | 依赖通过构造方法参数传入,对象实例化时依赖即完整 | 强制依赖与不可变依赖,Spring 官方推荐优先采用 |
| Setter 注入 | 依赖通过 setter 方法赋值,对象创建完成后可重新设置 | 可选依赖、需要运行时动态变更的依赖 |
| 字段注入 | 通过反射直接对字段赋值,典型注解为 @Autowired | 快速开发场景;不利于编写纯单元测试(需要容器或反射手段才能注入) |
构造器注入能够保证对象完全初始化后才能被使用,且依赖字段可声明为 final,天然具备不可变性。从 Spring 4.3 起,单构造器的类即使不加 @Autowired 也会被容器自动识别注入。
2.3 与依赖倒置原则的关系
DI 的理论根源是面向对象七大原则中的 依赖倒置原则(DIP):
- 高层模块不应依赖低层模块,两者都应依赖抽象
- 抽象不应依赖细节,细节应当依赖抽象
DI 通过容器把具体实现注入给面向抽象的消费方,将代码间的硬编码依赖替换为抽象接口的关系。业务代码只知道"我需要一个 UserDao 接口",具体实现是 JdbcUserDao、HibernateUserDao 还是 MyBatisUserMapper,由容器在装配阶段决定。
依赖倒置原则的详细说明见 面向对象编程 中 DIP 章节。
三、面向切面编程
3.1 横切关注点
在一个典型的业务系统中,以下逻辑通常横跨大量业务方法:
- 事务管理(方法前开事务、结束提交、异常回滚)
- 安全校验(方法前校验当前用户权限)
- 日志记录(方法前后记录入参、耗时、异常)
- 性能监控(上报执行耗时)
- 异常兜底(捕获异常后返回降级结果)
- 幂等校验(方法前校验请求是否重复)
这类逻辑在业务层面与核心功能无关,但在技术层面又必须执行,称之为 横切关注点(cross-cutting concern)。如果直接写进每个业务方法,会造成代码重复和耦合——调整某个横切逻辑需要修改全部涉及的方法。
AOP(Aspect-Oriented Programming)的目标就是将横切逻辑从业务方法中剥离,集中封装为独立模块,再以声明方式织入需要被干预的方法集合。业务代码本身不感知切面存在。
3.2 核心术语体系
| 术语 | 含义 | 举例 |
|---|---|---|
| Aspect(切面) | 封装横切逻辑的独立模块 | 事务切面、日志切面、权限切面 |
| JoinPoint(连接点) | 切面可介入的执行位置 | 方法调用、字段访问、构造调用、异常抛出。Spring AOP 仅支持"方法执行"一种连接点 |
| Pointcut(切入点) | 一组 JoinPoint 的匹配规则,决定切面作用于哪些位置 | execution(* com.example.service.*.*(..)) 匹配 service 包下所有方法 |
| Advice(通知) | 在 JoinPoint 执行的具体逻辑,以及执行时机 | Before(前置)、AfterReturning(成功返回)、AfterThrowing(抛出异常)、Around(环绕,可控制是否执行原方法)、After(无论成功失败都执行) |
| Weaving(织入) | 将切面逻辑融入目标对象生命周期的过程 | 编译期(AspectJ 编译器)、类加载期(AspectJ Agent)、运行期(Spring AOP 的动态代理) |
| Introduction(引介) | 在不修改原有类代码的前提下,为其在运行时增加新方法或接口 | 为任意 Bean 动态添加一个 Monitorable 接口,暴露监控方法 |
3.3 动态代理与内部调用失效
Spring AOP 采用运行期织入,通过为目标对象生成代理类实现增强。代理分两种:
| 代理方式 | 原理 | 触发条件 |
|---|---|---|
| JDK 动态代理 | 基于 java.lang.reflect.Proxy,为目标类实现的接口生成代理类 | 目标类至少实现一个接口 |
| CGLIB 代理 | 基于 ASM 字节码框架,为目标类生成子类并重写非 final 的 public/protected 方法 | 目标类本身非 final,被代理方法非 final、非 static。Spring Boot 2.x 起默认采用 CGLIB |
两种代理存在共通的约束:目标类内部的 this 调用不会经过代理。例如 A 类的 method1() 调用自身的 method2(),method2() 上的 @Transactional 或 @Async 不会生效,原因是调用链直接走目标对象自身,跳过了代理对象的拦截层。
常见的处理方式有三种:自注入本类 Bean、通过 ApplicationContext.getBean() 获取代理再调用、使用 AopContext.currentProxy() 取得当前代理对象。
反射、动态代理如何支撑起 Spring IoC 与 AOP 的运行时基础,见 反射。
四、Bean 生命周期契约
Spring 容器对 Bean 的管理遵循完整的生命周期契约。开发者可以通过接口、注解或配置在各个阶段插入自定义逻辑。Bean 从创建到销毁的完整流程如下:
启动容器,加载 BeanDefinition
↓
Bean 实例化阶段
├── InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
│ (有机会在实例化前返回代理对象,短路后续流程)
└── 调用构造器或工厂方法,得到原始 Bean 实例
↓
属性填充阶段
├── InstantiationAwareBeanPostProcessor.postProcessPropertyValues
│ (在此阶段可修改属性值,如 @Value/@Autowired 解析)
└── 依赖注入:setter/字段注入完成
↓
感知回调阶段
├── BeanNameAware.setBeanName
├── BeanClassLoaderAware.setBeanClassLoader
├── BeanFactoryAware.setBeanFactory
├── EnvironmentAware / EmbeddedValueResolverAware
├── ResourceLoaderAware / ApplicationEventPublisherAware
├── MessageSourceAware / ApplicationContextAware
└── ServletContextAware(Web 环境)
↓
初始化前处理
└── BeanPostProcessor.postProcessBeforeInitialization
(@PostConstruct、@Autowired、@Value 等注解的处理在此之前已完成,
此阶段可对 Bean 做进一步包装)
↓
初始化回调
├── @PostConstruct(CommonAnnotationBeanPostProcessor 处理)
├── InitializingBean.afterPropertiesSet
└── init-method(XML 或 @Bean(initMethod) 指定)
↓
初始化后处理(AOP 代理在此阶段生成)
└── BeanPostProcessor.postProcessAfterInitialization
(AnnotationAwareAspectJAutoProxyCreator 判断 Bean 是否需要被切,
需要则返回代理对象替代原始 Bean)
↓
Bean 就绪,可被使用
↓
容器关闭
↓
销毁回调
├── @PreDestroy
├── DisposableBean.destroy
└── destroy-method(XML 或 @Bean(destroyMethod) 指定)
生命周期中各回调方式的执行顺序遵循"注解优先、接口其次、配置最后"的原则。@PostConstruct 比 InitializingBean 先执行,@PreDestroy 比 DisposableBean 先执行。
五、IoC 容器分层
Spring 的容器从下至上分为两层,分别对应不同的使用场景。
5.1 BeanFactory
BeanFactory 是最底层的容器接口,核心职责仅围绕 Bean 的定义、创建、依赖注入和生命周期管理:
- 典型实现为
DefaultListableBeanFactory,同时实现了BeanDefinitionRegistry接口,具备 BeanDefinition 的注册能力 - 懒加载策略:仅在调用
getBean()时才真正实例化 Bean,启动速度快、内存占用低 - 不具备企业级能力:不自动注册 BeanPostProcessor、不支持国际化、没有事件机制
- 常见于资源受限的嵌入式场景,或作为构建上层容器的内部组件
5.2 ApplicationContext
ApplicationContext 在 BeanFactory 的基础上扩展了企业级特性,是日常开发中主要使用的容器形态:
| 扩展能力 | 说明 |
|---|---|
| 后处理器自动识别 | 自动识别并注册 BeanFactoryPostProcessor 和 BeanPostProcessor 类型的 Bean |
| 预实例化 | 容器启动时即完成所有非懒加载单例 Bean 的创建,启动时即可暴露配置错误 |
| 国际化 | 通过 MessageSource 接口提供多语言消息解析 |
| 事件发布 | 基于 ApplicationEventPublisher 的观察者模式事件机制 |
| 资源抽象 | 统一的资源加载接口,支持 classpath、文件系统、URL 等多种来源 |
| 环境抽象 | Environment 统一管理 profile 与属性源(properties/yml/环境变量/命令行参数) |
常用的 ApplicationContext 实现:
| 实现类 | 配置来源 | 典型场景 |
|---|---|---|
| ClassPathXmlApplicationContext | classpath 下的 XML 文件 | 传统 XML 配置的遗留系统 |
| FileSystemXmlApplicationContext | 文件系统路径下的 XML 文件 | 外部配置化加载 |
| AnnotationConfigApplicationContext | @Configuration 注解类 + 包扫描 | Spring 注解驱动的纯 Java 配置应用 |
| AnnotationConfigServletWebServerApplicationContext | Spring Boot Web 应用 | 内嵌 Servlet Web 容器,Spring Boot Web 默认上下文 |
| AnnotationConfigReactiveWebServerApplicationContext | Spring Boot WebFlux 应用 | 内嵌响应式 Web 服务器 |
六、BeanDefinition:Bean 的配方
BeanDefinition 是 IoC 容器的核心抽象,代表一个 Bean 的"配方"。容器在真正实例化 Bean 之前,首先将各种来源的配置解析为 BeanDefinition 对象集合,然后统一基于配方创建 Bean。
BeanDefinition 中包含的关键元信息:
| 属性 | 含义 |
|---|---|
| beanClass | Bean 的全限定类名,运行时通过反射创建实例 |
| scope | 作用域(singleton / prototype / request / session 等) |
| constructorArgumentValues | 构造器参数列表,用于构造器注入 |
| propertyValues | 属性值列表,用于 setter 注入 |
| initMethodName / destroyMethodName | 自定义初始化与销毁方法名 |
| lazyInit | 是否懒加载(仅对单例 Bean 生效) |
| dependsOn | 创建该 Bean 前必须先完成初始化的其他 Bean |
| autowireMode | 自动装配模式(按名称 / 按类型 / 构造器) |
| factoryBeanName / factoryMethodName | 工厂 Bean 名称与工厂方法名(用于非直接构造的 Bean) |
这一设计实现了"配置来源"与"创建逻辑"的彻底解耦:XML 解析、注解扫描、@Bean 方法处理、Groovy DSL 等只是不同的 BeanDefinition 读取器,最终产出的 BeanDefinition 结构完全一致,底层创建逻辑无需关心配置来源。
七、作用域体系
Spring 为 Bean 定义了多种作用域,决定 Bean 实例的存在范围与创建时机:
| 作用域 | 实例数量与生命周期 | 典型使用场景 |
|---|---|---|
| singleton(默认) | 整个容器内一个 Bean 定义对应一个实例 | 无状态服务、工具类、配置对象、数据源 |
| prototype | 每次获取 Bean 都创建新实例 | 有状态对象、每次调用需要独立上下文的组件 |
| request | 一次 HTTP 请求内一个实例 | Web 应用中与单次请求绑定的上下文对象 |
| session | 一个 HTTP Session 内一个实例 | 用户级购物车、会话级临时缓存 |
| application | 整个 ServletContext 生命周期内一个实例 | Web 应用级全局共享对象 |
| websocket | 一条 WebSocket 连接生命周期内一个实例 | WebSocket 会话级上下文 |
自定义作用域通过实现 Scope 接口并注册到容器中完成。Spring Cloud 基于自定义作用域实现了 refresh(配置刷新作用域)和 request 的扩展版本。
八、两大扩展点:BeanFactoryPostProcessor 与 BeanPostProcessor
这两个后处理器接口是 Spring 具备高扩展性的根本原因。它们分别作用于 BeanDefinition 阶段和 Bean 实例阶段。
8.1 BeanFactoryPostProcessor
作用时机:BeanDefinition 全部加载完成后、任何 Bean 实例化开始之前。
作用对象:BeanDefinition 的元信息,可以修改、新增、删除 BeanDefinition。
典型应用:
| 后处理器 | 功能 |
|---|---|
| PropertySourcesPlaceholderConfigurer | 将 BeanDefinition 中 ${key} 占位符替换为属性源中的实际值 |
| ConfigurationClassPostProcessor | 解析 @Configuration、@Bean、@ComponentScan、@Import,产出对应 BeanDefinition |
| MapperScannerConfigurer | 扫描指定包下的 MyBatis Mapper 接口,注册为 MapperFactoryBean 类型的 BeanDefinition |
8.2 BeanPostProcessor
作用时机:Bean 实例化完成并填充属性后,在初始化回调前后各执行一次。
作用对象:已经实例化的 Bean 实例,可以做包装、修改、甚至替换为代理。
典型应用:
| 后处理器 | 功能 |
|---|---|
| AutowiredAnnotationBeanPostProcessor | 处理 @Autowired、@Value,完成依赖注入 |
| CommonAnnotationBeanPostProcessor | 处理 @Resource、@PostConstruct、@PreDestroy |
| AnnotationAwareAspectJAutoProxyCreator | 匹配 @Aspect 切面,为目标 Bean 生成 AOP 代理对象 |
| AsyncAnnotationBeanPostProcessor | 为 @Async 标注的方法生成异步执行代理 |
九、声明式事务
9.1 事务抽象
Spring 的事务体系建立在 PlatformTransactionManager 抽象之上,不同底层技术对应不同实现:
| 底层技术 | 事务管理器实现 |
|---|---|
| JDBC / MyBatis | DataSourceTransactionManager |
| Hibernate 5 | HibernateTransactionManager |
| JPA | JpaTransactionManager |
| JTA 分布式事务 | JtaTransactionManager(配合 Atomikos、Bitronix 等) |
业务代码通过声明式注解使用事务,不直接依赖具体实现。
9.2 传播行为
传播行为定义了当一个事务方法被另一个事务方法调用时,事务边界如何组合。@Transactional(propagation = ...) 支持七种传播行为:
| 传播行为 | 行为描述 | 典型场景 |
|---|---|---|
| REQUIRED(默认) | 已有事务则加入,没有则新开事务 | 绝大多数 CRUD 操作 |
| REQUIRES_NEW | 始终新开独立事务,外层事务挂起 | 审计日志、积分记录等必须成功的子操作,外层回滚不影响它 |
| SUPPORTS | 有事务则加入,没有则非事务执行 | 查询类方法 |
| NOT_SUPPORTED | 总是以非事务方式执行,外层事务挂起 | 发消息、写日志等无需事务参与的操作 |
| MANDATORY | 必须在已有事务中调用,否则抛出异常 | 必须作为大事务一部分的内部子方法 |
| NEVER | 必须在非事务环境调用,否则抛出异常 | DDL 语句、纯非事务操作 |
| NESTED | 有外层事务则开启保存点嵌套,没有则等效 REQUIRED | 子操作失败仅回滚子操作,不影响外层主事务(需要 JDBC 驱动支持 Savepoint) |
9.3 回滚规则与注意事项
- 默认回滚条件:方法抛出
RuntimeException或Error时回滚;受检异常(Exception及其子类,非 RuntimeException)不回滚 - 自定义回滚:
rollbackFor = Exception.class可扩大回滚范围;noRollbackFor可指定某些异常不回滚 @Transactional仅在 public 方法上生效(动态代理只能拦截 public 方法)- 同类内部方法调用不触发事务(同 AOP 内部调用失效的原因)
- 多数据源场景需要显式指定对应的
transactionManager,否则使用默认主数据源
十、事件机制
Spring 的事件机制基于观察者模式,是模块间解耦通知的轻量级手段。
10.1 核心角色
| 角色 | 接口/类 | 作用 |
|---|---|---|
| 事件 | ApplicationEvent 抽象类 | 自定义事件的基类,携带事件源与业务数据 |
| 发布者 | ApplicationEventPublisher 接口 | 发布事件,ApplicationContext 本身即为发布者 |
| 监听者 | ApplicationListener<E> 接口或 @EventListener 注解 | 接收并处理匹配类型的事件 |
10.2 使用流程
1. 自定义事件继承 ApplicationEvent,携带业务数据
2. 需要时调用 applicationEventPublisher.publishEvent(customEvent)
3. 监听方通过 @EventListener 标注方法,参数类型决定接收的事件
4. 若监听方法需要处理后续事件的发布顺序,可使用 @Order 控制执行顺序
默认情况下事件同步发布,发布线程会阻塞直到所有监听器执行完成。如需异步处理,可在监听方法上追加 @Async 注解(需启用异步支持),或为 SimpleApplicationEventMulticaster 配置线程池。
十一、Spring 生态全景
Spring Framework 本身是 IoC、AOP、上下文与基础设施的集合。围绕该核心,Spring 生态以子项目形式覆盖了企业开发的各类场景:
| 子项目 | 定位 | 核心能力 |
|---|---|---|
| Spring Core / Beans | 核心容器 | Bean 工厂、依赖注入、资源加载、工具类 |
| Spring Context | 上下文层 | 注解驱动、国际化、事件机制、属性解析 |
| Spring AOP | 切面基础设施 | JDK/CGLIB 代理、切入点表达式、AspectJ 集成 |
| Spring JDBC / DAO | 数据访问基础 | JdbcTemplate 模板方法、异常转译、DataSource 管理 |
| Spring ORM | ORM 集成 | Hibernate / JPA 集成,统一事务管理 |
| Spring Transaction | 事务抽象 | 声明式事务、传播行为、多数据源事务管理 |
| Spring Web 基础 | Web 支持层 | WebApplicationContext、Multipart 解析、远程调用封装 |
| Spring MVC | 传统 Web MVC | DispatcherServlet、注解驱动 Controller、视图解析、参数绑定 |
| Spring WebFlux | 响应式 Web | 非阻塞 IO、Reactor 编程模型、Netty/Servlet 3.1 支持 |
| Spring Security | 安全框架 | 认证授权、会话管理、CSRF/XSS 防御、OAuth2 |
| Spring Data | 数据访问统一 | 统一 Repository 抽象,适配 JPA/MongoDB/Redis/ES/Cassandra 等 |
| Spring Integration | 企业集成 | EIP 模式实现、消息通道、端点适配器、外部系统连通 |
| Spring Batch | 离线批处理 | Job/Step 管道、读-处理-写三步模型、重启与跳过策略 |
| Spring AMQP / Kafka | 消息集成 | 与 RabbitMQ / Apache Kafka 的生产消费封装 |
| Spring for GraphQL | GraphQL 支持 | 服务端 Query/Mutation 处理、订阅、Schema 解析 |
| Spring Boot | 开发脚手架 | 自动装配、内嵌容器、Starter 依赖、约定优于配置 |
| Spring Cloud | 分布式治理 | 注册发现、配置中心、网关、熔断器、链路追踪、分布式事务基础 |
Spring Boot 作为脚手架层的整体架构、自动装配原理、Fat JAR 结构与启动时序,见 Spring Boot 主脉络;具体的 Starter 选型、配置系统与环境隔离,见 Spring Boot Starter 汇总、application.yml 核心配置、Profiles 环境隔离。
Spring MVC 与 WebFlux 在 Java Web 体系中的位置见 Java Web 技术体系。
十二、总结
Spring Framework 的出现重新定义了 Java 企业级开发的范式。在它之前,业务代码被容器规范侵入、依赖关系隐藏在 JNDI 查找中、横切逻辑散落在各处。Spring 通过 IoC 与 AOP 两项核心思想,让业务对象回归 POJO,让依赖关系从不可见变为显式声明,让横切逻辑从分散变为集中管理。
支撑这一设计落地的是层层递进的机制:BeanDefinition 将配置来源与创建逻辑解耦,BeanFactory 与 ApplicationContext 提供了从简到全的两档容器能力,BeanFactoryPostProcessor 与 BeanPostProcessor 两个扩展点构成了 Spring 极高扩展性的骨架,生命周期契约为开发者提供了可插入的标准钩子。
在这个核心之上,Spring 生态以子项目的形式向外延伸,从数据访问、事务、Web 到安全、集成、批处理,再到 Spring Boot 的脚手架化与 Spring Cloud 的分布式治理。每一层子项目都建立在同一套容器抽象之上,共享一致的配置模型与扩展规范,使得 Spring 成为 Java 领域覆盖面最广的开发框架。