资讯动态

Spring Aware机制深度解析:从回调原理到自定义实现

发布时间:2026/9/10 0:33:11 来源:尧图企业网站定制
刚接触Spring那会儿我一直有个困惑一个平平无奇的Bean凭什么能拿到ApplicationContext、BeanFactory这些容器的内部对象明明我在代码里什么都没写它却像长了眼睛一样知道自己的BeanName知道自己在哪个容器里甚至在容器销毁时还能收到通知。后来才搞明白这些都是Aware机制在背后起作用。这个接口可以说是Spring里最容易被忽略、但又最值得琢磨的设计之一。面试的时候经常被问“Spring有哪些Aware接口”但很多人背完列表就完了根本不知道它底层是怎么实现的也不知道自己该怎么扩展一个自定义的Aware。这篇文章我不打算给你列一堆枯燥的接口清单就算了而是把Aware从“是什么”到“怎么实现”到“底层原理”到“坑在哪”完整捋一遍。如果你是个想真正吃透Spring的Java开发这篇值得你花十分钟看完。1. 从一次困惑说起Bean凭什么能拿到Spring容器内部的东西先回到我最初那个困惑的场景。假设你有一个UserService想在启动的时候读取某个配置文件里的值然后做一些初始化。你自然会想到用Value注解这个没问题。但如果你的需求不是拿到某个配置项而是要拿到整个容器的引用——比如你要手动从容器里拿一个不依赖注入的Bean或者你要在运行时注册一个新的单例Bean——怎么办大多数人第一反应是用Autowired注入ApplicationContext。这能行而且很常见。但你有没有想过Autowired是Spring 2.5之后才有的注解Spring最早期是怎么做的答案就是Aware接口。在注解驱动还没流行的年代让Bean感知容器的方式就是实现一个特定的Aware接口Spring会在初始化Bean的过程中把对应的资源通过回调方法塞给Bean。1.1 Aware到底是什么Aware本身是个标记接口英文意思就是“感知”。它的源码极其简单里面一个方法都没有public interface Aware { }就这么个空壳子。真正的逻辑全在各个子接口里它们都定义了一个setXxx方法比如BeanNameAware接口长这样public interface BeanNameAware extends Aware { void setBeanName(String name); }ApplicationContextAware这样public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }你会发现这些子接口的命名和设计高度统一你要感知什么就实现对应的接口Spring在合适的时机调用你定义的setXxx方法把那个东西传给你。我的理解是这本质上是Spring和Bean之间的一种“回调契约”。Spring容器负责管理Bean的生命周期但Bean在某些时候也需要知道容器里发生了什么、容器给了它什么。Aware就是这条信息通道。它和依赖注入的区别在于依赖注入是你主动声明“我要什么”容器给你什么而Aware更像容器主动通知你“这个东西现在给你”你被动接收。1.2 为什么设计成接口回调而不是构造器注入这里有个值得琢磨的问题Spring为什么不用构造器注入或者Autowired来处理这些内部资源非要搞一套专门的Aware接口核心原因在于时机。你仔细想一下Spring创建Bean的流程是实例化new出来→ 填充属性给字段赋值→ 初始化执行afterPropertiesSet等。而像BeanName这种东西根本不是一个“属性”它是容器在创建Bean时才知道的一个元数据压根不存在于你的类里你怎么用Autowired注入总不可能每个类都声明一个name字段吧那也太蠢了。再比如ApplicationContext虽然理论上可以用Autowired注入但在Spring早期版本根本没有注解这种东西。而且Aware回调的时机非常靠前在常规的Autowired属性填充之前就会触发。这意味着你在实现setApplicationContext方法里做的任何事情都会影响到后面Bean的整个初始化流程。这个能力是普通依赖注入给不了的。还有一个更实际的原因Aware回调是接口方法调用编译期就能确定性能上比反射驱动的注解注入更优。虽然现代Spring大量使用注解但底层骨架依然保留了Aware这套回调机制很多内置逻辑比如ApplicationContextAwareProcessor就是基于它工作的。2. Spring内置的Aware接口全家桶各自用途与触发时机Spring内置的Aware接口非常多我粗略数了一下光org.springframework.*包下的就有十几个。盲目去背没意义关键是要知道每类接口什么时候触发、拿来干嘛。我把它们分成几个族群这样好记。2.1 容器感知族BeanNameAware、BeanFactoryAware、ApplicationContextAware这三个是面试中最常被问到的“铁三角”也是我们最常用的三个。BeanNameAware回调方法setBeanName(String name)让你知道自己Bean在Spring容器里注册的ID是什么。注意如果你在XML里配置了id就是那个id如果用了Bean(xxx)就是xxx如果完全没指定就是默认的类名首字母小写。BeanFactoryAware回调方法setBeanFactory(BeanFactory beanFactory)让你拿到当前Bean所属的BeanFactory。在Spring中ApplicationContext本质上也是一个BeanFactory但它多了很多企业级功能。直接拿BeanFactory的场景相对少一般出现在你需要极简容器操作的时候。ApplicationContextAware回调方法setApplicationContext(ApplicationContext applicationContext)让你拿到完整的ApplicationContext。这是最常用的一个Aware因为它能拿到的能力最强事件发布、资源加载、国际化消息、环境配置全都能干。这个族群的特点我就不重复了核心就一句拿到的时机按“Bean创建早期到完整可用”排序BeanNameAware最早ApplicationContextAware最晚但能力最强。2.2 环境配置族EnvironmentAware、EmbeddedValueResolverAware、ResourceLoaderAware这一族主要用于配置和环境信息的感知。EnvironmentAware回调方法setEnvironment(Environment environment)让你拿到当前应用的环境配置也就是application.properties/application.yml经Spring解析后的全部配置。EmbeddedValueResolverAware回调方法setEmbeddedValueResolver(StringValueResolver resolver)这个接口比较冷门但它给了一个很实用的能力让你能动态解析${...}占位符。很多框架内部就是用它来实现运行时占位符替换的。ResourceLoaderAware回调方法setResourceLoader(ResourceLoader resourceLoader)让你拿到一个ResourceLoader用来加载类路径下的资源文件。同样的能力通过Autowired注入ResourceLoader也能实现但Aware接口方式更统一。2.3 生命周期感知族InitializingBean、DisposableBean、SmartInitializingSingleton严格来说InitializingBean和DisposableBean并不是Aware接口的子接口它们没有继承Aware标记。但它们的用法和思想跟Aware完全一致都是在某个生命周期节点由容器回调。所以我建议你直接把它们归到Aware这个“感知”家族里理解。InitializingBean回调方法afterPropertiesSet()在Bean属性填充完成后执行初始化逻辑作用等同init-method和PostConstruct。DisposableBean回调方法destroy()在容器关闭前执行销毁逻辑。SmartInitializingSingleton回调方法afterSingletonsInstantiated()这个更高级一点要等所有单例Bean都创建完成后才回调适合做全局的延迟初始化后置处理。这个家族提醒我们Spring的核心生命周期本质上就是“感知回调”这套模式在驱动。2.4 更多扩展感知接口还有一批Aware接口是Spring生态里的组件在用的但理解它们有助于你更全面地看懂Spring在干什么ServletContextAware在Web应用中拿到ServletContextWeb容器环境下触发。MessageSourceAware拿到国际化消息源。ApplicationEventPublisherAware拿到事件发布器可以发布应用事件。BeanClassLoaderAware拿到加载当前Bean类的ClassLoader这在做类加载隔离、字节码增强时很有用。LoadTimeWeaverAware拿到AspectJ加载时织入器主要用于实现类加载期的AOP增强。这些接口你不用全记住但至少要知道Spring体系里存在这么一套“感知回调”的统一设计哲学。以后接第三方框架时你要是看到别人实现了某个XxxAware立刻就能明白对方是想让Spring把什么资源传给他的框架。这就是理解框架代码的钥匙。3. 实践必备自定义Aware接口的完整实现理解内置Aware之后我估计你会问如果我的项目也想用Aware这套机制让某个Bean能够感知我们自己框架里的组件该怎么做这个需求很常见比如你写了一个自己的配置中心希望实现了某个接口的Bean能自动拿到配置中心的客户端实例。3.1 自定义Aware的两种姿势对比先说结论**在绝大多数场景下你不应该自己通过BeanPostProcessor去实现Aware的触发逻辑而应该直接用Spring已经做好的那个Aware接口或者干脆用Autowired。**只有当你明确需要在“某个特定回调时机”触发且Autowired做不到的时候才需要考虑自定义Aware。这点一定要想清楚再动手。但仍然值得动手做一遍因为这会让你彻底理解Aware的底层机制。自定义Aware主要有两种姿势第一种实现BeanFactoryAware或ApplicationContextAware在回调里获取容器然后手动对容器里所有Bean做检查。这种方式比较简单但有两个不舒服的地方一是它破坏了Aware的“被动感知”语义变成了主动遍历二是性能较差每次都要扫描全部Bean。我实际项目中很少用这个方法。第二种通过BeanPostProcessor在Bean初始化前检查并注入。这个更优雅也更接近Spring自己的处理方式。3.2 手把手实现一个自定义Aware假设你的项目里有个ConfigClient类用来连接配置中心。你想让那些需要读取远端配置的Bean实现一个ConfigClientAware接口Spring在初始化时自动把ConfigClient注入进去。第一步定义你自己的Aware接口public interface ConfigClientAware extends Aware { void setConfigClient(ConfigClient configClient); }这里必须继承Aware因为Spring在处理时通常会先判Aware类型继承它才能保证语义统一。第二步写一个实现ConfigClientAware接口的业务BeanComponent public class ConfigClientDemo implements ConfigClientAware { private ConfigClient configClient; Override public void setConfigClient(ConfigClient configClient) { this.configClient configClient; } public String getRemoteConfig(String key) { return configClient.getConfig(key); } }到这里为止完全是个普通的Bean没有任何特殊处理。如果项目里只有这两步setConfigClient永远不会被调用。你必须让Spring容器感知到这个接口并触发射回调。第三步实现一个BeanPostProcessor专门处理ConfigClientAwareComponent public class ConfigClientAwareProcessor implements BeanPostProcessor { private final ConfigClient configClient; public ConfigClientAwareProcessor(ConfigClient configClient) { this.configClient configClient; } Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ConfigClientAware) { ((ConfigClientAware) bean).setConfigClient(configClient); } return bean; } }这个BeanPostProcessor会在每一个Bean初始化之前被Spring自动调用。它检查当前Bean是否实现了ConfigClientAware如果是就把ConfigClient实例传过去。第四步测试验证。这样你任何一个Bean只要实现ConfigClientAware都会被自动注入ConfigClient不需要再手动写Autowired。看起来很简单对不对但你注意一个细节ConfigClientAwareProcessor本身也是一个BeanSpring创建它的时候会把所有BeanPostProcessor先收集起来然后在创建普通Bean的过程中逐个调用。这里有个微妙的循环依赖问题——ConfigClient的创建又在什么时候如果ConfigClient本身也依赖了ConfigClientAwareProcessor就可能出问题。我的习惯是让这些基础设施组件尽量独立不去依赖业务Bean。3.3 实操中需要留意的几个细节第一个细节postProcessBeforeInitialization和postProcessAfterInitialization的选择。Spring内置的ApplicationContextAwareProcessor用的是postProcessBeforeInitialization也就是在执行PostConstruct和afterPropertiesSet之前触发。这符合Spring“先给基础设施再让Bean初始化”的设计逻辑。所以自定义Aware时我建议也放在postProcessBeforeInitialization里这样可以保证你的setXxx方法在Bean自己玩自己的初始化逻辑之前已经拿到资源。第二个细节是否要实现PriorityOrdered或Ordered接口。如果你系统里有多个BeanPostProcessor它们之间是有执行顺序的。Spring内置的处理器优先级很高自定义的处理器如果想在某些内置处理之前执行就需要实现Ordered接口并返回很小的顺序值。我从实践中得到的经验是除非你有明确的顺序依赖否则不要随便改顺序容易引入各种怪问题。第三个细节命名规范。Aware接口的命名一定是以Aware结尾回调方法名是set加资源类型。这么做不是死板而是为了可读性和Spring生态的一致性。如果你写出一个setConfig不叫setConfigClient别人看到的时候还要猜测这个方法是干嘛的相当不优雅。4. 底层原理解密Spring是如何触发Aware回调的了解了怎么用、怎么写接下来就得看看Spring内部到底是怎么把Aware回调触发的。这部分是面试的重头戏也是你排查诡异问题的根基。4.1 两条触发线BeanFactoryAware系列与ApplicationContextAware系列很多人以为Spring对所有Aware接口的处理方式是相同的拿到源码一看才发现完全不是。Spring对Aware回调的触发实际上是分两拨的第一拨是BeanFactoryAware族由AbstractAutowireCapableBeanFactory直接处理。在Bean实例化并完成属性填充后AbstractAutowireCapableBeanFactory的initializeBean方法里有一段直接调用的代码private void invokeAwareMethods(String beanName, Object bean) { 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(this); } } }注意这里用的是instanceof硬编码匹配只处理BeanNameAware、BeanClassLoaderAware、BeanFactoryAware这三个。而且它没有走后置处理器管线是直接内联调用的。这说明这三个接口的处理时机比所有BeanPostProcessor都要早。第二拨是ApplicationContextAware族由ApplicationContextAwareProcessor这个BeanPostProcessor处理。ApplicationContextAwareProcessor是在应用上下文刷新时注册的后置处理器它专门处理ApplicationContextAware这一族的接口。它的核心逻辑在invokeAwareInterfaces方法里private void invokeAwareInterfaces(Object bean) { 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); } }这段逻辑通过后置处理器机制在Bean初始化之前触发顺序排在PostConstruct和InitializingBean的afterPropertiesSet之前。所以你要记住一个时间线BeanNameAware/BeanFactoryAware最先触发在填充属性后、其他初始化逻辑前→ 各类BeanPostProcessor的postProcessBeforeInitialization其中ApplicationContextAware类在这里触发→ PostConstruct → InitializingBean.afterPropertiesSet → 自定义init-method → BeanPostProcessor的postProcessAfterInitialization。这张时间线图就是Aware的灵魂。理解了它你就能解释很多怪异现象。比如为什么你在PostConstruct里用ApplicationContext没问题把逻辑挪到构造函数里就拿到的是null——因为构造的时候Aware回调还没发生。4.2 为什么ApplicationContextAware背后要多绕一圈看到这里你可能会问BeanFactoryAware直接内联调用就行了为什么ApplicationContextAware非要通过一个BeanPostProcessor处理答案在于ApplicationContext和BeanFactory的定位差异。BeanFactory是最基础的IoC容器它的职责就是管理Bean生命周期自己在调用链最深处可以硬编码处理。而ApplicationContext是BeanFactory的增强版多了事件、资源、国际化等功能它需要依赖很多内部组件并且希望开发者可以在应用上下文刷新的不同阶段插入自定义逻辑。如果把ApplicationContextAware的处理也硬编码在AbstractAutowireCapableBeanFactory里就违背了开闭原则而且处理逻辑会和上层容器高度耦合Spring框架本身的模块边界也就模糊了。所以Spring把ApplicationContextAware族抽成一个独立的BeanPostProcessor让底层IoC容器不用关心上层ApplicationContext的特性。你在AbstractApplicationContext的prepareBeanFactory方法里可以看到它显式地把ApplicationContextAwareProcessor注册到了BeanFactory的处理器列表里。这就是典型的设计模式应用底层保持简单通过回调机制向上层开放扩展点。这个解读也是我面试时候比较喜欢的切入点因为它体现出你看过源码之后能自己分析“为什么这样设计”而不是背结论。4.3 和BeanPostProcessor一起看Aware不是孤立的Aware机制跟BeanPostProcessor的关系非常紧密。你看上面的时间线就明白除了三个极早的Aware之外剩下几乎所有的Aware回调本质上都是某个BeanPostProcessor在起作用。你以为你在单独使用Aware实际你在使用Spring的后置处理器管线。这里有个很常见的面试追问Autowired是Spring什么时候注入的答案也是后置处理器。AutowiredAnnotationBeanPostProcessor会在postProcessProperties阶段完成自动装配。而postProcessProperties的执行时机在postProcessBeforeInitialization之后、PostConstruct之前。所以完整顺序其实是Bean实例化完成填充属性XML/注解配置的常规属性内联触发BeanNameAware、BeanClassLoaderAware、BeanFactoryAwarepostProcessBeforeInitializationApplicationContextAware族在这里触发Autowired的注入也在附近PostConstructInitializingBean.afterPropertiesSetinit-methodpostProcessAfterInitialization你可以把这个时间线当成一块积木来记遇到任何关于“什么时候执行”的问题就拿它来套。包括Spring三级缓存解决循环依赖的问题本质上也发生在这个流程的早期。4.4 一个真实的问题排查为什么setBeanName里拿不到注入的依赖有一次我在项目里写了个公共组件想让组件实现BeanNameAware在setBeanName里做一些注册操作。结果发现我在setBeanName方法里尝试使用Autowired注入依赖的服务时依赖是null程序直接空指针。排查了很久才意识到这个现象在上面的时间线里早就有了定论BeanNameAware是内联触发的早于所有后置处理器所以Autowired压根还没来得及执行。这个教训很实用。如果你在某个Aware回调方法里使用其他依赖属性一定要确认那些依赖是通过构造器注入的还是字段注入的。如果你把它们放在字段上Autowired在早期Aware回调里大概率是拿不到的。规避方式有两种一是在setBeanName里只做不依赖其他Bean的轻量操作二是在afterPropertiesSet或PostConstruct里做真正的初始化因为那时所有依赖都已就位。5. 实战排坑Aware使用中我踩过的坑和总结的建议Aware虽然设计简洁但实际用起来坑还真不少。我把这两年遇到的比较典型的坑整理一下希望对后来者有帮助。5.1 坑一无脑实现ApplicationContextAware导致的内存泄漏这是最容易被忽视的问题。很多人图省事让所有需要容器的Bean都实现ApplicationContextAware然后在一个随处可见的静态变量里存储ApplicationContext。这样做表面上方便实际上这是典型的静态持有可能造成类加载器泄漏尤其在Web应用热部署场景下老的应用上下文无法被回收最终引发OutOfMemoryError。Spring官方文档其实有明确说明ApplicationContext会保存所有Bean的引用如果一个Bean在静态字段里保存了ApplicationContext那么该Bean的生命周期就和整个应用一样长了即使它的业务上已经死了。这是我没打招呼就坑过同事的经典案例。我的建议是除非你确实需要在静态工具方法里访问容器比如框架封装否则尽量用Autowired实例字段注入让容器来管理生命周期。5.2 坑二搞混Aware回调与构造函数的执行顺序我在面试时经常问候选人一个问题“一个Bean实现了ApplicationContextAware它的构造函数能安全使用注入的ApplicationContext吗”很多人犹豫半天。答案是不能。构造函数执行在new出来的瞬间这时候Aware回调都没发生连BeanFactoryAware都没触发。任何Aware回调提供的资源在构造函数里都是null。如果你确实需要在创建对象最早期就使用ApplicationContext那么只会有一种办法把ApplicationContext作为构造器参数传进去使用Autowired构造器注入。这不是Aware的能力范围。5.3 坑三自定义Aware时BeanPostProcessor被提前实例化如果你像我前面那样写了一个ConfigClientAwareProcessor它在Spring容器里的创建时机其实非常早。因为所有BeanPostProcessor都会在Spring容器启动的过程中优先实例化而这时候如果ConfigClient还没创建好就可能在注入ConfigClient时遇到Bean创建顺序问题。我实际遇到过一次ConfigClient依赖于数据源数据源配置又依赖Environment的某个属性但我那个ConfigClientAwareProcessor被Spring提前创建导致ConfigClient在Environment准备完成之前就初始化了结果属性全是null。这个问题的解法是让ConfigClientAwareProcessor自己延迟获取ConfigClient不要用构造器注入而是通过实现ApplicationContextAware在回调里懒加载或者在处理器里存一个ObjectProviderConfigClient。这算是一个比较高级的坑了一般写业务代码很少遇到但只要你尝试自定义Aware相关组件就一定要知道依赖创建顺序带来的风险。5.4 坑四注册了自定义Aware接口却在回调里拿不到值有一种情况比较隐蔽你在XML或Bean方式里定义了一个Bean它实现了你的自定义Aware接口但你的BeanPostProcessor根本不生效。大概率原因是你把BeanPostProcessor定义在了子容器里而你的业务Bean却是在父容器中。在Spring MVC中DispatcherServlet子容器和根容器的BeanPostProcessor不会互相干扰。这个排查思路我建议从以下几个方面检查你的BeanPostProcessor是普通Spring Bean还是在Web子容器中注册的。ApplicationContextAwareProcessor是否被你自己覆盖或屏蔽。检查Bean是否是代理对象因为postProcessBeforeInitialization收到的可能是原始Bean而postProcessAfterInitialization收到的是代理。如果你在postProcessAfterInitialization里用instanceof判断Aware接口代理对象没实现这个接口就会失败。这个坑我帮人排查过非常多次这里提醒一下如果目标Bean被AOP代理了那么postProcessBeforeInitialization阶段拿到的还是原始Bean原始Bean通常实现了Aware接口但代理Bean不一定。所以你应该优先在postProcessBeforeInitialization里做Aware注入。5.5 一些实用建议在框架封装中把Aware用得更优雅说完了坑最后给点实际操作建议。如果你要在一个内部框架项目里大量使用Aware机制有几点可以让你的框架用起来更舒服第一Aware接口和普通接口的区别要明确。你的框架对外提供的一定是扩展接口比如ConfigClientAware、FeignClientAware等这些接口要放在一个稳定的API模块里业务开发者只需要面向这个接口编程根本不需要知道Spring容器怎么触发它。这种解耦是Aware机制最大的价值。第二结合Java配置类来替代XML导入。如果你的框架提供了自定义的BeanPostProcessor记得提供一个Configuration类来注册它让使用者通过Import一行集成。这也是现在主流Spring Boot Starter的做法。第三回调方法内尽量只做赋值和简单校验不做重型逻辑。因为Aware回调发生在Bean初始化早期此时很多Bean尚未准备好你在这个阶段做依赖复杂的事情很容易触发循环依赖。第四注意并发和代理问题。Aware回调可能是被多个线程调用的吗单例Bean在Spring容器中默认是单例的所以回调方法只会执行一次而原型Bean每次获取都会重新创建回调就会触发多次。如果你的Aware回调里有累积状态的逻辑一定要考虑到这个差异。另外Spring中对Bean的AOP代理会影响Aware接口的识别这也是我在特殊场景下踩过的坑一旦代理介入targetClass上的接口可能丢失。6. 写在最后用Aware的思路去看Spring的其他机制学习Aware最大的收获不是记住那几个接口名字而是理解Spring这套“生命周期回调”的设计哲学。你会发现Spring里几乎处处都是回调Bean的生命周期回调、事件监听回调、BeanPostProcessor回调、BeanFactoryPostProcessor回调、Transactional的环绕回调……本质上都是“在适当的时机把控制权交给框架或交给开发者”。Aware机制只是这套哲学里最基础、最容易被忽略的一块。我个人感受是当你把Aware的时间线、触发逻辑、设计动机都吃透了再去看Spring的自动装配、Conditional条件装配、甚至Spring Boot的启动流程都会轻松很多。因为它们都是同一套思想在不同层面的表现容器管理对象生命周期并在精确的时间点向对象暴露它该知道的信息。最后再分享一个实战技巧吧。如果你在项目里遇到“某个Bean初始化时想拿某个容器资源但拿不到”这类怪问题别急着翻代码先拿起笔把那个Bean的生命周期时间线画出来标注它要拿的资源属于哪一步触发。绝大多数Aware问题画完时间线基本就水落石出了。这个小习惯帮我省了不知道多少排查时间希望对你也有用。

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

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

免费获取报价