资讯动态

Spring Aware机制深度解析:回调原理、执行顺序与自定义实现

发布时间:2026/10/14 15:10:07 来源:尧图企业网站定制
几年前带过一个转岗过来的 Java 开发他指着基础工程里一段 Spring 代码问这个实现 ApplicationContextAware 的接口到底是干嘛的我直接 Autowired 一个 ApplicationContext 不也一样吗我当时愣了一下——不是因为他问得幼稚而是这个问题我最早接触 Spring 时也想过只是后来用多了就习以为常从没认真把这件事拆开讲清楚。Spring 里带 Aware 后缀的接口一抓一大把BeanNameAware、BeanFactoryAware、EnvironmentAware、EmbeddedValueResolverAware……看起来都是感知某个资源的意思但很多人分不清它们什么时候回调、和依赖注入有什么本质区别、Spring 又是怎么把资源塞给 Bean 的。这篇文章就把 Spring 中 Aware 的用法和实现机制一次性讲透先说明它解决什么问题再把常用接口逐个盘点接着从源码看触发链路然后用完整示例验证执行顺序最后给出自定义 Aware 的实现方案和我在实际项目里踩过的坑。适合想真正搞懂 Spring 生命周期的人也适合面试前梳理这种大家都用过但说不清的知识点。1. Aware到底是什么它解决的痛点和设计意图1.1 把Bean想象成舞台演员Aware就是导演递过来的台本Spring 容器管理着所有 Bean 的完整生命周期创建、属性填充、初始化、销毁。在这个体系里Bean 是被动的它不知道自己在哪个容器里也不知道容器给自己起了什么名字更不知道容器外面还站着多少兄弟 Bean。大多数业务 Bean 根本不需要知道这些——但有那么一小部分代码确实需要在某个时刻拿到容器的某种资源比如想知道自己的 Bean 名称、想读环境变量、想发布事件。这就是 Aware 存在的意义。Aware 在英文里就是感知Spring 用它做了一组回调约定如果你的类实现了某个 Aware 接口容器在初始化该 Bean 的特定阶段就会主动调用接口里定义的 setter 方法把对应的资源作为参数传进来。用舞台打比方演员不需要自己跑去找灯光师、音响师导演容器在上场前会按名单把这些资源依次递给他。演员只需要提前声明我需要知道舞台布局导演就保证在合适的时候告诉你。这个提前声明就是实现 Aware 接口导演递资源就是容器回调。理解了这个模型后面再看源码和自定义 Aware 都很顺。1.2 为什么不能只用Autowired非要搞一套回调这是个很自然的问题。ApplicationContext 本身也是个 Bean按说 Autowired 直接注入就行何必绕一圈实现接口关键差别在于两点。第一时机不同。Autowired 属于属性填充populateBean阶段的处理它解决的是静态依赖——Bean 的字段需要某个引用。而 Aware 回调发生在初始化流程中其中一部分比如 BeanNameAware、BeanClassLoaderAware甚至在任何 BeanPostProcessor 之前就会触发。如果你的初始化逻辑必须依赖这些资源在 Autowired 的场景下很难处理因为你不一定能保证赋值顺序。第二解耦程度不同。实现 BeanNameAware 的类只知道容器把名字传给自己代码里不会依赖 ApplicationContext也不会依赖注解处理器。而 Autowired 一个 ApplicationContext 字段你的类就永久地和 Spring 的容器 API 绑在一起。Aware 这种小接口 回调的模式更克制更容易被框架复用来做扩展点。可以理解为Autowired 是自己伸手去拿Aware 是容器主动递给你。对业务代码来说伸手拿更省事但对框架基础设施来说被动接收才能保证时机可控、依赖最少。这也是 Spring 内部大量组件宁愿实现一堆 Aware 也不写一堆 Autowired 的原因。2. 常用Aware接口速查回调方法、时机与典型用途2.1 一份速查表哪些Aware在你手边先给一份能直接收藏的速查表。Spring 里常见的 Aware 接口其实就这些记住它们的回调方法和能拿到什么用的时候就不会愣神。接口回调方法获取的资源典型场景BeanNameAwaresetBeanName(String)当前 Bean 在容器中的名字日志前缀、内部标识BeanClassLoaderAwaresetBeanClassLoader(ClassLoader)当前 Bean 使用的类加载器动态类加载、SPI 扩展BeanFactoryAwaresetBeanFactory(BeanFactory)所在 BeanFactory 实例早期容器操作、嵌套容器ApplicationContextAwaresetApplicationContext(ApplicationContext)应用上下文getBean、事件、资源、环境EnvironmentAwaresetEnvironment(Environment)环境配置访问器读取自定义配置项EmbeddedValueResolverAwaresetEmbeddedValueResolver(StringValueResolver)占位符解析器运行时解析 ${...} 表达式ResourceLoaderAwaresetResourceLoader(ResourceLoader)资源加载器读取 classpath 资源ApplicationEventPublisherAwaresetApplicationEventPublisher(ApplicationEventPublisher)事件发布器发布业务事件MessageSourceAwaresetMessageSource(MessageSource)国际化消息源获取本地化消息2.2 最常用的三个BeanNameAware、ApplicationContextAware、EnvironmentAwareBeanNameAware 门槛最低只有一个 setBeanName。用在哪最常见的是日志和标识。比如某个通用组件同时被多个模块以不同 Bean 名注册你想让日志里带上我是谁实现它就行。这类需求用 Autowired 注入一个字符串反而奇怪Aware 是更自然的方式。ApplicationContextAware 是业务代码里见得最多的一个。getBean、publishEvent、getResource、getEnvironment 全都能干。不过能力越大责任越大正因为它什么都能拿到最容易把业务类写得依赖容器。后文会专门讲怎么权衡。EnvironmentAware 在 Spring Boot 里很实用。读配置不必死磕 Value你可以在回调里动态拼 key、做条件判断比如根据某个前缀一次性抓取所有配置项。我曾经做一个多数据源动态切换组件就是在 setEnvironment 里把各个数据源的前缀配置统一解析出来的比写几十个 Value 字段清爽得多。2.3 相对冷门但关键的两个占位符解析与事件发布EmbeddedValueResolverAware 知道的人不多但能力很实用。它给你一个 StringValueResolver可以在运行时解析 ${...} 占位符。比如规则引擎里存了一段模板字符串想动态替换成配置里的真实值直接用 resolver.resolveStringValue(${timeout}) 就能搞定。这比手动解析 properties 文件优雅得多。ApplicationEventPublisherAware 则适合那种我只想发事件不想把整个容器拖进来的场景。Spring 的内置事件机制本身就依赖 ApplicationEventPublisher与其实现 ApplicationContextAware 拿整个容器再去 publishEvent不如直接实现这个更精准的接口——语义清晰也方便测试时用一个轻量实现替换。这两个接口背后正好体现了 Aware 设计的一个原则能按需拿到最小资源就不要抛给你整个容器。写框架时如果也能按这个原则设计回调你的使用者会轻松很多。3. 触发机制源码拆解两条回调链其实截然不同3.1 第一条触发链invokeAwareMethods的硬编码处理打开 spring-beans 模块的AbstractAutowireCapableBeanFactory#initializeBean最先看到的是一个叫invokeAwareMethods的私有方法。private void invokeAwareMethods(final String beanName, final Object bean, Nullable final BeanFactory beanFactory) { if (bean instanceof Aware) { if (bean instanceof BeanNameAware) { ((BeanNameAware) bean).setBeanName(beanName); } if (bean instanceof BeanClassLoaderAware) { ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader()); } if (bean instanceof BeanFactoryAware) { ((BeanFactoryAware) bean).setBeanFactory(beanFactory); } } }注意三点。其一这个方法只处理 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 这三个接口。其二它是直接硬编码判断没有经过任何扩展机制。其三BeanFactoryAware 传的是当前 Bean 所在的 BeanFactory——如果你跑在 ApplicationContext 场景下这个 beanFactory 参数通常是容器内部的 DefaultListableBeanFactory而不是 ApplicationContext 本身。很多人实现完 BeanFactoryAware 之后发现拿到的对象和 ApplicationContext 不是同一个就是这个原因。为什么硬编码因为这三个资源属于 BeanFactory 级别只要是 Spring 容器就一定具备没必要再通过 BeanPostProcessor 绕一圈。把它们放在 initializeBean 的最前面处理也保证了执行顺序在其它 BeanPostProcessor 还没开始干活的时候Bean 就已经知道自己的名字了。3.2 第二条触发链ApplicationContextAwareProcessor 如何介入那 ApplicationContextAware、EnvironmentAware、ResourceLoaderAware、MessageSourceAware、ApplicationEventPublisherAware 这些去哪了它们走的是另一条完全不同的路BeanPostProcessor。ApplicationContext 容器在刷新准备阶段会注册一个内置的ApplicationContextAwareProcessor它的postProcessBeforeInitialization方法会对所有 Bean 逐个做 instanceof 判断命中了就调用对应的 Aware 回调。源码骨架大致如下if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); }注意一个容易踩雷的点如果你用的是纯粹的 BeanFactory 实现比如 XmlBeanFactoryApplicationContextAware 是不会生效的因为 ApplicationContextAwareProcessor 是 ApplicationContext 容器才注册的处理器。现在虽然基本没人直接操作 BeanFactory但读异常栈时这个知识能帮你快速定位为什么回调没触发。再看一个细节上面的代码里EnvironmentAware 的入参是 environment 对象而 ResourceLoaderAware 和 ApplicationContextAware 的入参直接就是 applicationContext 本身。ApplicationContext 本身就实现了 ResourceLoader 和 ApplicationEventPublisher所以 Spring 在这把同一个对象以不同身份投递给了不同接口。这种接口收窄的写法很值得学习。3.3 两条链在完整生命周期中的位置把两条链放进 initializeBean 的完整流程里看protected Object initializeBean(String beanName, Object bean, Nullable RootBeanDefinition mbd) { // ... invokeAwareMethods(beanName, bean, factory); // 第一链三个基础 Aware // ... wrappedBean applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); // └─ ApplicationContextAwareProcessor 在这里执行 第二链 // └─ CommonAnnotationBeanPostProcessor 在这里执行 PostConstruct invokeInitMethods(beanName, wrappedBean, mbd); // └─ InitializingBean.afterPropertiesSet // └─ 自定义 init-method wrappedBean applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }结论很清楚initializeBean 之前还有 createBeanInstance实例化、populateBean属性填充。Aware 回调整体发生在属性填充之后、初始化方法之前。而第一链的 invokeAwareMethods 又先于第二链的所有 BeanPostProcessor。也就是说 BeanNameAware 永远比 ApplicationContextAware 先回调这一点下面用示例直接验证。4. 完整示例利用Aware观察Spring Bean的初始化顺序4.1 一个能打印执行顺序的示例Bean实践出真知。定义这样一个 Bean把关键阶段全打上日志public class AwareOrderDemo implements BeanNameAware, ApplicationContextAware, InitializingBean { private Environment environment; Autowired public void setEnvironment(Environment environment) { this.environment environment; System.out.println(0. Autowired setter 属性注入 - environment.getClass().getSimpleName()); } Override public void setBeanName(String name) { System.out.println(1. BeanNameAware.setBeanName - name); } Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println(2. ApplicationContextAware.setApplicationContext - applicationContext.getClass().getSimpleName()); } PostConstruct public void postConstruct() { System.out.println(3. PostConstruct 执行); } Override public void afterPropertiesSet() { System.out.println(4. InitializingBean.afterPropertiesSet 执行); } public void customInitMethod() { System.out.println(5. 自定义 init-method 执行); } }再用配置类注册它指定 initMethodConfiguration public class AwareOrderConfig { Bean(initMethod customInitMethod) public AwareOrderDemo awareOrderDemo() { return new AwareOrderDemo(); } }启动一个只包含这个配置的容器public class AwareOrderDemoTest { public static void main(String[] args) { AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(AwareOrderConfig.class); context.getBean(AwareOrderDemo.class); context.close(); } }4.2 从输出日志看生命周期的三个结论控制台依次输出0. Autowired setter 属性注入 - StandardEnvironment 1. BeanNameAware.setBeanName - awareOrderDemo 2. ApplicationContextAware.setApplicationContext - AnnotationConfigApplicationContext 3. PostConstruct 执行 4. InitializingBean.afterPropertiesSet 执行 5. 自定义 init-method 执行这个输出基本就是 Spring Bean 初始化阶段的一张完整时间线。第一个结论属性注入最早发生在 populateBean 阶段早于所有 Aware。第二个结论BeanNameAware 先于 ApplicationContextAware对应源码里 invokeAwareMethods 在 BeanPostProcessor 之前执行。第三个结论PostConstruct 先于 InitializingBean 和 init-method原因是 PostConstruct 也是通过 BeanPostProcessor 的 postProcessBeforeInitialization 实现的而后两个是在 invokeInitMethods 里处理的。把这个时间线背下来Spring 生命周期相关的面试题基本就稳了。面试候选人时只要让他大概说出这几个阶段的先后再追问一句PostConstruct 和 afterPropertiesSet 哪个先执行、为什么就能看出他是真读过源码还是只会背结论。4.3 用Aware拿容器后真正值得做的三件事实现 ApplicationContextAware 之后我实际用过且觉得靠谱的是这三件事一是按需 getBean 获取原型 Bean。业务里确实有某段逻辑需要动态获取一个非单例 Bean的需求比如状态机需要根据当前状态创建不同的策略对象用静态注入的方式不太方便。二是 publishEvent 发布业务事件。模块之间要解耦又不想引入消息中间件容器内置事件就是一个轻量方案。通过 ApplicationContext.publishEvent 发布事件监听端用 EventListener 接收整套链路干净利落。三是 getResource 加载 classpath 资源。比如读取 jar 包里的模板文件、校验规则用 applicationContext.getResource(classpath:xxx.json) 比手工拼路径稳妥得多。注意这些能力原则上只在绕不开的时候用。如果某个业务 Bean 只是偶尔需要 getBean优先考虑用 ObjectProvider 注入不要实现 ApplicationContextAware。我平时还会准备一个 SpringContextHolder 静态工具类把容器存成静态变量方便在非 Spring 管理的场景定时任务、工具类、异常处理器里拿到 BeanComponent public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; Override public void setApplicationContext(ApplicationContext context) { applicationContext context; } public static T T getBean(ClassT requiredType) { return applicationContext.getBean(requiredType); } }不过这个类在第六节会重点提醒它能解决入口问题也容易制造设计垃圾。5. 自定义Aware接口把Spring这套设计搬到自己的框架里5.1 设计一个业务化的回调接口Spring 的 Aware 机制不是只能被 Spring 自己用这套接口 回调 BeanPostProcessor 装配的组合完全可以平移到自己的框架里。场景很常见你在做一套内部链路追踪组件希望业务 Bean 如果想生成 TraceId就实现一个回调接口来获得统一的生成器而不是让业务方直接 new 一个。先定义业务接口public interface TraceIdGeneratorAware { void setTraceIdGenerator(TraceIdGenerator traceIdGenerator); }这个接口就是你和业务方之间的约定契约。业务方不需要知道生成器怎么来的、从哪注册的他只需要实现 setTraceIdGenerator容器就会在合适的时机把这个依赖送上门。这正是 Spring 官方 Aware 的原始姿态。5.2 用BeanPostProcessor完成回调装配接下来定义一个 BeanPostProcessor对所有正在初始化的 Bean 做 instanceof 判断命中就把 TraceIdGenerator 设进去Component public class TraceIdGeneratorAwareProcessor implements BeanPostProcessor { private final TraceIdGenerator traceIdGenerator new DefaultTraceIdGenerator(); Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof TraceIdGeneratorAware) { ((TraceIdGeneratorAware) bean).setTraceIdGenerator(traceIdGenerator); } return bean; } }选 postProcessBeforeInitialization 而不是 After理由很简单要和 Spring 内置 Aware 保持同一个时机。这样业务方在 PostConstruct、afterPropertiesSet 里就一定能用上 traceIdGenerator不会出现初始化方法执行时还是 null的尴尬。有一个容易被忽略的细节BeanPostProcessor 本身也是一个 Bean它会在所有普通 Bean 之前被创建并注册。所以不要在这个处理器里写过重的东西更不要让它依赖其他尚未初始化的业务 Bean否则容易触发早期的循环依赖问题。5.3 让自定义Aware能拿到底层容器资源很多时候自定义 Aware 回调的对象本身需要依赖 Spring 容器能力。比如你的框架要提供一个业务上下文上下文里包含容器里的其他 Bean、环境配置等。这时可以让处理器实现 ApplicationContextAware先把容器拿到再在回调时构建业务上下文。public class BizContext { private final ApplicationContext applicationContext; private final String beanName; public BizContext(ApplicationContext applicationContext, String beanName) { this.applicationContext applicationContext; this.beanName beanName; } public ApplicationContext getApplicationContext() { return applicationContext; } public String getBeanName() { return beanName; } }Component public class BizContextAwareProcessor implements BeanPostProcessor, ApplicationContextAware { private ApplicationContext applicationContext; Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext applicationContext; } Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof BizContextAware) { BizContext context buildBizContext(beanName); ((BizContextAware) bean).setBizContext(context); } return bean; } private BizContext buildBizContext(String beanName) { return new BizContext(applicationContext, beanName); } }注意处理器本身是 Aware 的接收者它又是一个 BeanPostProcessor看起来有点绕但 Spring 的初始化顺序能保证ApplicationContextAwareProcessor 在容器刷新早期就已经把 applicationContext 传给了目标对象等到业务 Bean 被处理时处理器里的容器引用早已就位。这种组合用法在 Spring 内部实现里比比皆是。5.4 用一段测试验证自定义回调生效自定义机制同样可以用一个极简测试验证。准备一个实现了 BizContextAware 的测试 Bean通过配置类注册刷新容器后断言回调已经执行class BizContextAwareProcessorTest { Test void awareShouldInjectContext() { AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(); context.register(BizContextAwareProcessor.class, MyBizBean.class); context.refresh(); MyBizBean bean context.getBean(MyBizBean.class); assertNotNull(bean.getBizContext()); assertEquals(myBizBean, bean.getBizContext().getBeanName()); context.close(); } }测试里注意一点如果 MyBizBean 没有被任何扫描路径覆盖就要手动注册进来如果想让回调生效BizContextAwareProcessor 也必须在当前容器中存在。注册顺序不重要但两个 Bean 都不能漏。跑通这个测试后你的自定义 Aware 机制就完全闭环了——本质上你已经复刻了 Spring 那套接口 后处理器的扩展模型。6. 踩坑复盘Aware使用中容易翻车的几个场景6.1 构造器里读Aware资源必踩的NPE最典型也最容易犯的错误是在构造器里访问 Aware 回调设置的字段或容器资源。构造器执行时Bean 实例刚从 new 出来Spring 还在执行 createBeanInstance后面的 populateBean、Aware 回调都没开始此时字段必然为 null。我见过一个真实事故某同事写了一个配置校验组件构造器里调用 setEnvironment 传进来的配置做校验结果每次启动都报空指针排查了很久才发现是回调时机问题。正确做法是构造器只做纯字段初始化需要外部资源的逻辑一律放到 PostConstruct 或 InitializingBean 里。如果一定要在构造器阶段做点什么请改用构造器注入让 Spring 在创建实例的时候就把依赖传进来。6.2 原型作用域的Aware回调会反复执行Singleton Bean 的 Aware 回调在整个容器生命周期只执行一次。但 prototype 作用域的 Bean 不一样每次 getBean 都会走一遍完整的创建与初始化流程Aware 回调自然也会执行多次。这个特性在多数场景下无害但如果你在回调里做了有状态的操作麻烦就来了。曾经有个项目在某个 prototype 的辅助处理类里实现 ApplicationContextAware回调里给静态列表注册了监听处理器结果每次 getBean 都往列表里塞一条记录日志越打越多内存也跟着涨。排查到根因后把注册动作移到了 PostConstruct 或干脆放到 getBean 之后的业务方法里才解决。记住Aware 回调只适合做赋值不适合做一次性注册。6.3 动态代理参与时Aware资源可能出现空值当 Bean 被 AOP 切面拦截时Spring 可能创建 JDK 动态代理或 CGLIB 代理。容器创建的代理对象和原始目标对象是两回事。Spring 在处理 Aware 回调时通常作用于原始目标 Bean但如果你在代码里手动创建代理、或者在切面里试图从代理对象访问目标类的 Aware 字段很容易拿到 null。举个例子某个过滤器实现类实现了 ApplicationContextAware但它还被一个切面拦截。如果切面逻辑里强制转换目标对象去读容器字段却发现字段是空的多半是回调应用在了另一个对象上。对这个坑我的建议是不要在 Aware 回调里保存需要对外暴露的公开状态更不要让切面通过字段直取容器资源。如果切面本身需要容器访问让切面自己实现 Aware而不是从拦截的目标里取。6.4 静态持有容器的工具类是把双刃剑前面推荐的 SpringContextHolder 确实方便但它的危害同样不小。它让模块之间的依赖从容器装配时的可见关系变成了运行时随时可查的全局单例注册表管理者根本不知道谁在用谁单测里也没法方便地 mock。我在老项目里清理过二三十处这种 getBean 调用基本都是当年图省事埋下的雷。我的经验是SpringContextHolder 只留给那些真的无法通过注入获取 Bean的场景比如非 Spring 管理的线程、框架启动器、工具类里的兜底调用。业务类之间的依赖一律用构造器注入让依赖关系在方法签名里明明白白。6.5 强转具体容器类型导致的兼容性事故setApplicationContext 收到的容器类型在不同运行环境下完全不同普通 Spring 工程可能是 AnnotationConfigApplicationContextSpring Boot 里可能是 ConfigurableApplicationContext 的某个子类。如果你为了图方便强转成某个具体类型换一种启动方式就炸。正确做法是面向 ApplicationContext 接口编程只有在确实需要子类能力时才用 instanceof 判断后转换而且转换失败要有兜底逻辑。这个原则在任何框架代码里都适用Aware 也不例外。7. 实战决策到底该用Aware、注入还是监听器7.1 一张决策表遇到需求先选对姿势平时写代码时遇到我要拿容器/资源的念头可以按这张表快速决策需求场景推荐方式不建议的方式业务类想拿容器做 getBean构造器注入 / Autowired 注入 ApplicationContext大量实现 ApplicationContextAware初始化完成后需要容器资源PostConstruct 依赖注入在构造器里访问容器只是想知道自己的 Bean 名BeanNameAware注入一组奇奇怪怪的元数据读取配置项Value / EnvironmentAware自己解析 properties重复造轮子启动完成后做一次性逻辑ApplicationRunner / SmartInitializingSingletonApplicationContextAware 自定义标记位发布业务事件注入 ApplicationEventPublisher实现 Aware 拿整个容器再发事件非 Spring 管理的对象要拿 BeanSpringContextHolder且严格限制使用面到处 static getBean这张表背后的原则只有一句话业务代码尽量少碰容器 API把 Aware 留给基础设施。你越少依赖容器代码的可测试性和可替换性就越好。7.2 为什么Spring没有废弃Aware机制可能有人会问都到注解时代了Autowired、Value 这么好用为什么 Spring 没有把 Aware 废掉因为它根本不是历史包袱而是容器基础设施的一部分。在注解处理器、AOP 基础设施、事务回调这些内部组件的初始化流程里存在大量必须是这个时机、必须是这套回调的传递需求不能依赖注解扫描和字段注入的复杂度。BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 这三个在 BeanFactory 级别就硬编码处理了这意味着只要你用 Spring这套机制就在核心路径上占有一席之地。翻 Spring 源码时你会发现很多内部类都是一身 Aware又拿 ApplicationContext又拿 BeanFactory又拿 ClassLoader。它们这么写不是因为作者喜欢接口而是因为这是容器早期阶段可用、可靠、低开销的传递方式。业务代码可以少用 Aware但理解这套机制能帮你读懂 Spring 内部真正在发生什么。7.3 把Aware和生命周期串成一条时间线最后把整篇文章浓缩成一条值得背下来的时间线实例化构造器→ 属性填充Autowired、Value 等注入→ BeanNameAware / BeanClassLoaderAware / BeanFactoryAware 回调 → BeanPostProcessor 前置处理ApplicationContextAware、EnvironmentAware 等 Aware 回调以及 PostConstruct→ 初始化InitializingBean.afterPropertiesSet、init-method→ BeanPostProcessor 后置处理 → 使用 → 销毁。我在实际带人的时候发现能讲清这条时间线的同学不管在排查循环依赖、自定义注解处理器还是定位初始化顺序问题上都明显比背结论的人快。Aware 就是理解这条时间线最舒服的一把钥匙因为它本身就是容器主动、Bean 被动这个事实最直接的体现。以上就是我在项目里接触 Aware 的完整记录。如果你最近在梳理 Spring 生命周期建议找一段休息时间自己写一遍文中那个打印顺序的 Demo亲眼看到控制台输出比看我写十遍都管用。等你自己也实现了两三个自定义 Aware再回头看 Spring 源码里的那些内部处理器会觉得亲切很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑