1. postProcessBeanFactory 是干什么的:从容器启动流程说起
很多同学看 Spring 的AbstractApplicationContext源码时,目光总是被refresh()里的invokeBeanFactoryPostProcessors()和finishBeanFactoryInitialization()吸引,而postProcessBeanFactory(beanFactory)这个方法,名字看着跟BeanFactoryPostProcessor很像,却又不是同一个东西,非常容易忽略过去。
先说结论:postProcessBeanFactory(beanFactory)是AbstractApplicationContext提供给子类的一个模板回调钩子,它在 BeanFactory 刚刚创建完成、BeanDefinition 已经加载完毕之后触发,目的是让子类有机会对 BeanFactory 做“基础设施层面的定制”。注意,这里定制的是 BeanFactory 本身,不是去改业务 Bean,也不是去修改任何一个 BeanDefinition 的属性——尽管你确实可以在这个方法里拿到全部 BeanDefinition 的名字。
如果你喂给 Spring 的是ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext,那么postProcessBeanFactory执行时,容器里的所有<bean>标签已经解析成BeanDefinition了,但还没有任何一个 Bean 被实例化。换句话说,这是所有单例 Bean 诞生之前,容器给子类留下的最后一个“干净的”干预窗口。
这个方法对大多数业务开发者来说可能一辈子都用不上,但如果你在做框架封装、内部中间件、公共组件,或者需要在自己公司的容器启动阶段统一注册一些基础设施,那么它是比BeanFactoryPostProcessor更早、更贴近容器底层的一个扩展点。搞懂它,你对 Spring 启动顺序的认知就完整了一块。
1.1 refresh() 里那个容易被忽略的钩子
打开AbstractApplicationContext.refresh(),启动流程大概是这样的:
prepareRefresh(); obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); postProcessBeanFactory(beanFactory); // 注意,这里也有一处调用 invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); ...等一下,refresh()方法体里确实直接调用了postProcessBeanFactory(beanFactory),但这里调用的时机已经是在prepareBeanFactory()之后了。那跟AbstractRefreshableApplicationContext里在refreshBeanFactory()内部调用的那次是同一处吗?不是。
这里有个非常关键、也是大家最容易搞混的点:postProcessBeanFactory在refresh()主流程里存在一次显式调用,而在AbstractRefreshableApplicationContext的refreshBeanFactory()内部还藏着一处调用。也就是说,如果你用的是基于AbstractRefreshableApplicationContext的上下文,这个方法会被触发两次吗?不会,需要仔细看:
AbstractRefreshableApplicationContext.refreshBeanFactory()内部创建完 BeanFactory、加载完 BeanDefinition 后,会调用一次postProcessBeanFactory(beanFactory),这是子类覆写的机会。- 随后
refresh()里obtainFreshBeanFactory()返回后,prepareBeanFactory()执行完,又会调用一次postProcessBeanFactory(beanFactory)。但注意,这一次调用的是AbstractApplicationContext里的默认实现(空方法),而AbstractRefreshableApplicationContext覆写时,它的实现里并没有调用super.postProcessBeanFactory()——所以实际执行的是覆写逻辑,但只会执行一次有效逻辑,具体看下面源码解析。
其实两者的调用点是同一个方法,只是从不同路径到达,但由于子类覆写时大多不调super,所以不会重复执行额外逻辑。这个细节太绕,后面源码解读段我会展开。
1.2 谁在调用它,调用链长什么样
我们把调用链捋直。以ClassPathXmlApplicationContext为例:
new ClassPathXmlApplicationContext("application.xml") -> refresh() -> obtainFreshBeanFactory() -> refreshBeanFactory() // AbstractRefreshableApplicationContext -> createBeanFactory() -> loadBeanDefinitions(beanFactory) -> postProcessBeanFactory(beanFactory) // 子类扩展点 -> prepareBeanFactory(beanFactory) -> postProcessBeanFactory(beanFactory) // AbstractApplicationContext 默认空实现 -> invokeBeanFactoryPostProcessors(beanFactory) -> registerBeanPostProcessors(beanFactory) -> ...所以真正的扩展时机是obtainFreshBeanFactory()阶段,也就是refreshBeanFactory()内部,loadBeanDefinitions()之后的那一次调用。ClassPathXmlApplicationContext本身没有覆写postProcessBeanFactory,因此默认啥也不干。
1.3 它和 prepareBeanFactory 的分工
prepareBeanFactory和postProcessBeanFactory很容易被人混为一谈,因为两个方法都接收一个ConfigurableListableBeanFactory,而且都在容器启动早期执行。
prepareBeanFactory是AbstractApplicationContext自己的方法,属于“亲儿子”逻辑,干的是基础环境配置:设置 Bean 的类加载器、注册StandardEnvironment相关的解析器、注册ApplicationContextAwareProcessor、注册ApplicationListenerDetector、忽略EnvironmentAware等接口的自动装配依赖、注册ResourceLoaderAware等等。这些属于所有 Spring 上下文都必须具备的公共能力,写死在框架里,不允许子类干预。
postProcessBeanFactory则是留给子类按需覆写的“后手”,是在prepareBeanFactory的公共能力之上,追加子类特有的定制。比如 Web 环境下要多注册ServletContextAwareProcessor,要多注册request、session、application三个作用域。没有这个方法,Web 容器里你就没法注入HttpServletRequest,也根本用不了session作用域的 Bean。从这个角度看,postProcessBeanFactory就是框架为主类扩展预留的“插座口”。
2. 源码逐行拆解:默认实现与子类覆写
2.1 默认的空实现不是白写的
AbstractApplicationContext里的默认实现如下:
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { }方法体是空的。很多人第一次看到这里会疑惑:一个空方法,有什么好讲的?
这恰恰是模板方法模式的精髓。父类定义好流程骨架,留给子类一个可覆写的扩展点,默认啥也不做,子类按需覆写。你把postProcessBeanFactory当做一个“事件”来理解:时机到了,Spring 通知一声,有兴趣参与的子类自然会响应。
而且注意,AbstractApplicationContext.postProcessBeanFactory是protected方法,不是public。这意味着你没办法从外部调用它,只有子类或者同一个包下的代码才能触及。这也是刻意为之——这个扩展点是给上下文实现者用的,不是给外部调用者用的。如果你在自己的业务代码里想调用它,说明设计上已经走偏了。
2.2 AbstractRefreshableApplicationContext 里的标准姿势
AbstractRefreshableApplicationContext覆写了postProcessBeanFactory,这是经典实现:
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this)); }就一行,却承担着很重要的职责:注册ApplicationListenerDetector。
ApplicationListenerDetector是一个BeanPostProcessor,它的作用是检测容器里所有实现了ApplicationListener接口的 Bean,把它们登记到事件广播器里。任何业务类只要实现了ApplicationListener,Spring 都不需要你手动注册,它能自动被识别、注册成事件监听器,靠的就是这个探测器。
这里有个细节要注意:ApplicationListenerDetector是在postProcessBeanFactory阶段直接addBeanPostProcessor加进去的,而不是作为一个普通 Bean 定义注册到容器里。为什么?
因为如果把它声明成普通 Bean,那么在registerBeanPostProcessors阶段才会被实例化并注册,时间点太晚,很可能错过之前某些 Bean 的实例化过程。而postProcessBeanFactory阶段所有 Bean 都没实例化,此时直接通过addBeanPostProcessor注册,就能保证后续所有 Bean 的创建过程都能被它检查到。这就是“基础设施要趁早”的典型例子。
2.3 Web 环境下的两份“额外作业”
AbstractRefreshableWebApplicationContext继承了AbstractRefreshableApplicationContext,覆写了postProcessBeanFactory,做的工作更多:
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); registerScopes(beanFactory); }第一个:注册ServletContextAwareProcessor。这个处理器的作用是让实现了ServletContextAware接口的 Bean 能够拿到ServletContext。没有它,你在 Spring MVC 项目里的 Bean 想注入ServletContext就得绕道。
第二个:调用ignoreDependencyInterface(ServletContextAware.class)。这表示ServletContextAware这种接口不应该被 Spring 的自动装配机制处理,因为它的赋值工作交给ServletContextAwareProcessor来做。类似地,ApplicationContextAwareProcessor在prepareBeanFactory阶段也已经注册过,并忽略了ApplicationContextAware。这类配套操作是 Spring 容器里非常经典的一对组合:某个专用 BeanPostProcessor 负责给特定接口的实现类赋值,同时这个接口被排除在通用依赖注入之外。
第三个:registerScopes(beanFactory),往容器里注册 Web 专用作用域:
this.scopes = new HashMap<>(); this.scopes.put(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); this.scopes.put(WebApplicationContext.SCOPE_SESSION, new SessionScope()); this.scopes.put(WebApplicationContext.SCOPE_APPLICATION, new ServletContextScope());这三个作用域就是@RequestScope、@SessionScope、@ApplicationScope注解背后的实现。没有postProcessBeanFactory里的这次注册,你启动一个 Spring MVC 项目时只要用了@SessionScope注解,容器启动就会直接报错,提示找不到对应的 Scope。
2.4 为什么 AnnotationConfigApplicationContext 不走这条路
这是一个特别容易被问到的坑:AnnotationConfigApplicationContext继承了GenericApplicationContext,而GenericApplicationContext并没有使用AbstractRefreshableApplicationContext的那套refreshBeanFactory()流程。
GenericApplicationContext的refreshBeanFactory()方法是直接抛异常的,因为它压根不走“重新创建 BeanFactory + 加载 BeanDefinition”的路径,而是把 BeanFactory 作为自己的一个字段,在构造函数里就创建好了:
public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); }因此,postProcessBeanFactory在AnnotationConfigApplicationContext中默认不会被调用。这就是为什么你在AnnotationConfigApplicationContext上覆写postProcessBeanFactory经常会发现它根本没执行——这个扩展点压根就不存在于这条调用链里。
那注解配置类是怎么被处理的?答案是ConfigurationClassPostProcessor。它是一个BeanDefinitionRegistryPostProcessor,在refresh()流程的invokeBeanFactoryPostProcessors()阶段被调用,负责解析@Configuration、@Bean、@ComponentScan等注解。换句话说,注解配置的落地靠的是BeanDefinitionRegistryPostProcessor,不是postProcessBeanFactory。这两种上下文的设计差异,让不少做组件封装的人踩过坑,后面常见问题板块我会专门展开。
3. 实战用例:用 postProcessBeanFactory 扩展容器
了解了原理,接下来看几个实际能落地的用法。这里我提供几个我在工作中确实用过的场景,代码可以直接抄。
3.1 注册基础设施 BeanPostProcessor 的正确姿势
假设你要做一个公司内部的统一日志组件,要求在每次 Service 方法执行前后打印参数与耗时。实现自然是基于 AOP 或者MethodInterceptor,但如果你不想引入 Spring AOP 的自动代理机制,也可以简单写一个BeanPostProcessor:
public class ServiceLogPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 只对 service 包下的 Bean 做代理增强 if (bean.getClass().getName().startsWith("com.company.service")) { return Proxy.newProxyInstance( bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new LogInvocationHandler(bean)); } return bean; } }然后在自定义容器里注册它:
public class CompanyApplicationContext extends AbstractRefreshableApplicationContext { @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { // 加载 XML 或注解配置 } @Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new ServiceLogPostProcessor()); } }这里的关键是:用addBeanPostProcessor直接注册,而不是把这个处理器本身注册成一个 BeanDefinition。
两种方式的区别我在前面提过。直接addBeanPostProcessor是在容器启动的极早期插入,能够保证作用于所有后续创建的 Bean。而注册成 BeanDefinition 的话,Spring 在registerBeanPostProcessors()阶段才去实例化它,这时候有些非懒加载的普通 Bean 可能还没创建,但顺序上仍然是先注册好处理器再创建单例 Bean,所以也不会漏。真正的问题在于:如果你用@Bean方式声明一个实现了BeanPostProcessor的类,Spring 会因为检测到它是 BeanPostProcessor 类型,在registerBeanPostProcessors()阶段提前实例化它,这可能导致它依赖的其他 Bean 还没准备好,出现各种诡异的初始化顺序问题。在postProcessBeanFactory里用编程方式注册,不经过 BeanDefinition 解析,反而更可控。
3.2 自定义作用域与解析依赖
另一个高频场景是往容器里注册自定义作用域。比如我们团队做过一个“任务级作用域”,每个任务请求会创建一个独立的JobContext实例,任务执行期间,所有 Bean 拿到的都是当前任务上下文里的那一份。
实现的关键就是两个:注册Scope、注册可解析依赖。
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.registerScope("job", new JobScope()); beanFactory.registerResolvableDependency(JobContext.class, new JobContextHolder()); }registerScope("job", ...)之后,你就可以在 Bean 定义上用:
<bean id="jobExecutor" class="com.company.job.JobExecutor" scope="job"/>或者配合自定义注解使用。而registerResolvableDependency(JobContext.class, ...)则允许你在任意 Bean 的构造函数或@Autowired字段里直接注入JobContext,即使容器里根本没有名为jobContext的 BeanDefinition:
@Component public class JobRunner { private final JobContext jobContext; public JobRunner(JobContext jobContext) { this.jobContext = jobContext; } }registerResolvableDependency不是postProcessBeanFactory特有的,prepareBeanFactory里就注册过BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext等一堆内置可解析依赖。但在postProcessBeanFactory里做这件事,好处是时机同样够早,而且能让自定义容器子类按需添加。
3.3 在加载完 BeanDefinition 后做体检和审计
这个方法既然在loadBeanDefinitions()之后触发,那它天然适合做一件事:BeanDefinition 加载结果的检查与审计。
我有一次排查线上问题,发现某个环境里因为 XML 配置错误导致容器加载了大量重复的 BeanDefinition,但因为项目启动时没有做任何校验,问题到运行一段时间后才暴露。后来我在自定义容器里加了个审计逻辑:
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { Map<String, BeanDefinition> duplicates = new HashMap<>(); StringBuilder summary = new StringBuilder(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition bd = beanFactory.getBeanDefinition(beanName); String className = bd.getBeanClassName(); if (className == null) { className = bd.getFactoryBeanName() + "#" + bd.getFactoryMethodName(); } if (duplicates.containsKey(className)) { duplicates.get(className).concat("," + beanName); } else { duplicates.put(className, beanName); } summary.append(beanName).append(" -> ").append(className).append("; "); } // 检查同一 Class 是否被注册了多个 BeanDefinition List<String> conflictClasses = duplicates.entrySet().stream() .filter(e -> e.getValue().contains(",")) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (!conflictClasses.isEmpty()) { throw new IllegalStateException("检测到重复的 Bean 实现类: " + conflictClasses); } if (auditLogEnabled) { log.info("容器加载完成,共 {} 个 BeanDefinition:\n{}", beanFactory.getBeanDefinitionCount(), summary); } }这段代码的效果是:容器启动时,如果同一个实现类被多个 BeanDefinition 引用,直接启动失败,而不是上线后踩到依赖注入错乱。postProcessBeanFactory里抛异常,会直接中断refresh(),Spring 容器启动失败,错误信息清晰可控。这个玩法比每次部署后人工检查配置靠谱得多。
3.4 手动调用:非标准上下文也能享受这个扩展点
如果你的项目用的是AnnotationConfigApplicationContext,但你又确实想在 BeanDefinition 加载后、其他处理开始前做点类似的事,怎么办?
最直接的办法是摆脱对postProcessBeanFactory的依赖,改用BeanFactoryPostProcessor。但如果你的场景就适合这个方法,也可以自己手动调。GenericApplicationContext子类里,你可以在refresh()之前拿到getBeanFactory()然后手动执行逻辑:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class); ConfigurableListableBeanFactory factory = ctx.getBeanFactory(); // 手动模拟 postProcessBeanFactory 的时机 factory.addBeanPostProcessor(new ServiceLogPostProcessor()); factory.registerScope("job", new JobScope()); ctx.refresh();这种方式尽管没有真正调用postProcessBeanFactory方法,但达到了同样的时机和目标:在refresh()之前,把基础设施处理器和自定义作用域注册进去。对于使用AnnotationConfigApplicationContext的场景,这也是一种解套手段。
不过我个人建议,如果只是为了一次性扩展,用BeanFactoryPostProcessor更符合 Spring 的设计习惯,毕竟AnnotationConfigApplicationContext的设计者就没打算让你走继承覆写的路。但如果你写的是一个完整的自定义容器子类,且希望复用AbstractRefreshable的 BeanFactory 重建机制,那覆写postProcessBeanFactory依然是更正统的选择。
4. 它与 BeanFactoryPostProcessor 的协作与边界
4.1 两兄弟的调用顺序
postProcessBeanFactory和BeanFactoryPostProcessor经常被放在一起讨论,因为名字像、定位也像,但它们的调用顺序差别很大。
对于AbstractRefreshableApplicationContext体系,调用顺序是:
postProcessBeanFactory(beanFactory) -> invokeBeanFactoryPostProcessors(beanFactory)也就是说,容器先让子类通过postProcessBeanFactory做最后的基础设施定制,再调用所有显式声明的BeanFactoryPostProcessor。
这个顺序有讲究。BeanFactoryPostProcessor里做的是修改 BeanDefinition 的工作,而postProcessBeanFactory做的是修改 BeanFactory 基础设施的工作。先基础、后业务,层级分明。如果你在postProcessBeanFactory里注册了一个BeanFactoryPostProcessor类型的 BeanDefinition,那么在接下来的invokeBeanFactoryPostProcessors阶段是能拿到的。但如果你在BeanFactoryPostProcessor里想调用postProcessBeanFactory,不好意思,时机已经过了,这个阶段容器早已过了那个钩子。
4.2 动 BeanDefinition 该找谁
前面我强调过多次,postProcessBeanFactory主要是给子类扩展 BeanFactory 能力用的,不是改 BeanDefinition 的推荐位置。真正修改 BeanDefinition 的官方扩展点是BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。
有一个典型需求:动态往容器里注册一个新的 BeanDefinition。很多人会想当然地写在postProcessBeanFactory里:
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setBeanClassName(MyService.class.getName()); if (beanFactory instanceof BeanDefinitionRegistry) { ((BeanDefinitionRegistry) beanFactory).registerBeanDefinition("myService", bd); } }这个写法能不能跑通?能。但你要意识到,BeanFactoryPostProcessor在之后执行时,会看到这个动态注册的 BeanDefinition,一切正常。问题在于:如果你动态注册的是一个被增强器、代理处理器所关注的 Bean,那么它的 BeanPostProcessor 链不受影响,因为它同样经历后续的实例化流程。但如果你的目的是希望在ConfigurationClassPostProcessor解析完@Configuration之前做一些“覆盖式”的 Bean 定义变更,那postProcessBeanFactory就不合适了,因为对于注解配置类,这个时机还没执行ConfigurationClassPostProcessor。想要修改@Bean方法生成的 BeanDefinition,必须实现BeanDefinitionRegistryPostProcessor并且postProcessBeanDefinitionRegistry阶段要在ConfigurationClassPostProcessor之后执行的话,得控制优先级。这块一旦搞混,写出来的代码就是薛定谔的生效与报错。
4.3 优先级问题的坑
既然BeanFactoryPostProcessor也有优先级(通过PriorityOrdered、Ordered接口控制),那么在postProcessBeanFactory里注册的BeanPostProcessor优先级如何?直接addBeanPostProcessor注册的处理器,它是被动追加在处理器列表末尾的,换句话说它排在所有已注册的 BeanPostProcessor 之后执行。
这个问题很隐蔽。如果你在postProcessBeanFactory里添加了一个处理器,希望它对所有 Bean 的初始化过程生效,不好意思,它确实对所有 Bean 都生效,但执行顺序靠后。对于大多数只做“观察”的BeanPostProcessor,这个顺序不影响结果。可如果你的处理器需要在某个现有处理器之前执行(比如想在ApplicationContextAwareProcessor注入上下文之前先做点别的),那就不可能用这种方式实现优先级控制。
这时候更合适的做法是:把这个BeanPostProcessor声明成一个PriorityOrdered的 Bean,让 Spring 在registerBeanPostProcessors阶段按照排序规则注册,这样优先级才是可控的。像ApplicationListenerDetector这类后期才需要执行的处理器,才适合在postProcessBeanFactory里直接追加。先想清楚你的处理器执行时机,再决定注册方式。
5. 常见问题与排查技巧
5.1 覆写方法却没执行:上下文类型选错了
这是我见过最多的问题。有人自定义了上下文类,继承的是GenericApplicationContext或AnnotationConfigApplicationContext,覆写了postProcessBeanFactory,结果这个方法压根没被调用。代码没错,错在选错了父类。
postProcessBeanFactory只在两条路径上会被自动触发:
- 一是
AbstractRefreshableApplicationContext.refreshBeanFactory()内部; - 二是
AbstractApplicationContext.refresh()主流程里prepareBeanFactory()之后的那次调用,但默认是空实现。
GenericApplicationContext体系不走refreshBeanFactory()这条路,所以它的子类如果不手动调用,覆写的postProcessBeanFactory在refresh()主流程里根本不会被触碰。解决办法上面已经给过:要么换父类,要么在refresh()前手动调,要么改用BeanFactoryPostProcessor。
验证方法很简单,在覆写的方法体第一行加一句System.out.println("postProcessBeanFactory invoked"),集成测试时观察控制台。
5.2 “not eligible for auto-proxying”警告
在postProcessBeanFactory里注册 BeanPostProcessor,还有一个经典警告:
Bean 'xxx' of type [com.example.xxx] is not eligible for getting processed by all BeanPostProcessors这通常是因为你注册的 BeanPostProcessor 本身在创建过程中依赖了某些未被增强的 Bean,或者它被提前实例化,导致部分 BeanPostProcessor 还没注册完,就创建了一批 Bean。
我之前踩过的具体场景:在postProcessBeanFactory里往容器注册了一个 BeanPostProcessor,而这个处理器的构造方法里注入了一个DataSource。结果 Spring 为了实例化这个处理器,提前去创建DataSource,而DataSource本身应该被事务增强的 post-processor 处理,但那时还没注册,于是出现“not eligible”警告,且DataSource上的事务增强失效。排查了一天,最终方案是让 BeanPostProcessor 延迟获取DataSource,不通过构造函数注入,改为在postProcessAfterInitialization时才从容器获取,彻底绕开提前初始化问题。
这类问题的本质是:早期注册的处理器不能有过重的依赖需求。要么让它无依赖,要么让它延迟依赖。
5.3 注册的 Scope 不生效
另一个常见现象是:在postProcessBeanFactory里通过registerScope注册了自定义作用域,但业务 Bean 引用该作用域时依然报错,或者拿到的不是预期实例。
排查思路分三步:
- 看
registerScope是否真的在refresh()之前执行了。可以加日志,或者在postProcessBeanFactory里打印beanFactory.getRegisteredScope("job")是否为空。 - 看作用域实现类是否正确。
Scope接口有get、remove、registerDestructionCallback、resolveContextualObject、getConversationId五个方法,很多人会漏实现getConversationId,虽然不常用,但某些框架在序列化场景会调用。 - 看作用域是否是
SimpleThreadScope这种内置实现。Spring 自带的SimpleThreadScope只对单个线程有效,每次请求若是在不同线程里执行,取到的就不是同一个实例。如果业务是异步场景,就得自己实现一个基于ThreadLocal+ 请求上下文的 Scope。
postProcessBeanFactory只是给了你注册入口,作用域行为对不对,还得看你的Scope实现类够不够完善。
5.4 调试定位的常用手段
如果postProcessBeanFactory里的代码没有生效或者执行顺序不对,快速定位的办法:
第一,在方法里直接抛一个异常测试,看容器启动时是否报错。如果报错,说明执行了;如果启动正常,说明压根没进这个方法。这个办法简单粗暴但有效。
第二,在AbstractApplicationContext.refresh()方法上打条件断点,条件写成beanFactory.getClass().getName().contains("YourContext"),观察调用栈,能清晰看到obtainFreshBeanFactory到postProcessBeanFactory的完整链路。
第三,打印beanFactory.getBeanDefinitionCount()和所有 BeanDefinition 名称。这能看到postProcessBeanFactory执行时的容器状态,判断 BeanDefinition 加载是否完成。如果数量为 0,说明你的loadBeanDefinitions还没生效,问题在加载配置阶段,不在postProcessBeanFactory。
6. 个人经验总结与适用场景建议
我实际用postProcessBeanFactory比较少,因为它确实是个很底层的扩展点。但凡是用到的地方,基本都是框架级场景,比如前面提到的 Web 环境注册作用域和ServletContextAwareProcessor,再比如 Spring 自带的ApplicationListenerDetector自动识别监听器。这些都属于你平时感知不到、但它不工作整个容器就转不起来的核心能力。
最后分享一个小技巧:如果你想观察自定义容器里postProcessBeanFactory的触发时机,可以在自定义上下文类里覆写这个方法,同时别调用super,然后打印调用栈——你会看到refreshBeanFactory -> obtainFreshBeanFactory -> refresh这条经典链路。看到那条链路后,你再回看refresh()源码,整个启动过程就会非常清晰。
另外,做组件封装时,如果发现自己的 Bean 总是比预期早初始化,或者某些后置处理器没有覆盖到所有 Bean,优先排查一下是不是应该在postProcessBeanFactory阶段用addBeanPostProcessor直接注册,而不是把处理器声明成普通 Bean。很多时候,Bean 初始化顺序的诡异问题,根源就在这个容易被忽略的早期钩子上。