From 74ab6625acad5901da90d39e5de0ea36f29ab77c Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:31:44 +0100 Subject: [PATCH] Document registration recommendations for BeanPostProcessor and BeanFactoryPostProcessor Closes gh-34964 (cherry picked from commit 3b8efbe5a63143c81745fa6dba69a8039d235b16) --- .../pages/core/beans/factory-extension.adoc | 93 ++++++++++++++++--- .../config/BeanFactoryPostProcessor.java | 7 ++ .../factory/config/BeanPostProcessor.java | 8 ++ .../context/annotation/Bean.java | 25 +++++ 4 files changed, 122 insertions(+), 11 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc index dfc69d5cee0..4a89e94245d 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc @@ -67,6 +67,13 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise, Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the initialization of other beans in the context, this early type detection is critical. +Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method, +declare the method as `static` and ideally with no dependencies. Doing so avoids eager +initialization of the configuration class and other beans, which would make them +ineligible for full post-processing (such as auto-proxying). See the +"BeanPostProcessor-returning `@Bean` methods" section in the +{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details. + [[beans-factory-programmatically-registering-beanpostprocessors]] .Programmatically registering `BeanPostProcessor` instances NOTE: While the recommended approach for `BeanPostProcessor` registration is through @@ -80,7 +87,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat are always processed before those registered through auto-detection, regardless of any explicit ordering. -.`BeanPostProcessor` instances and AOP auto-proxying +.`BeanPostProcessor` instances and early initialization [NOTE] ==== Classes that implement the `BeanPostProcessor` interface are special and are treated @@ -90,17 +97,23 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor` instances nor the beans they directly reference are eligible for auto-proxying and, -thus, do not have aspects woven into them. +thus, do not have aspects woven into them. More generally, any bean that is instantiated +during this early phase is not eligible for full post-processing by all +`BeanPostProcessor` instances. -For any such bean, you should see an informational log message: `Bean someBean is not -eligible for getting processed by all BeanPostProcessor interfaces (for example: not -eligible for auto-proxying)`. +For any such bean, you should see a WARN-level log message similar to the following. -If you have beans wired into your `BeanPostProcessor` by using autowiring or -`@Resource` (which may fall back to autowiring), Spring might access unexpected beans -when searching for type-matching dependency candidates and, therefore, make them -ineligible for auto-proxying or other kinds of bean post-processing. For example, if you -have a dependency annotated with `@Resource` where the field or setter name does not +[quote] +Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by +all BeanPostProcessors (for example: not eligible for auto-proxying). + +To minimize the number of beans affected, register a `BeanPostProcessor` with a +`static` `@Bean` method that has no dependencies (see the note above). If you have +beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which +may fall back to autowiring), Spring might access unexpected beans when searching +for type-matching dependency candidates and, therefore, make them ineligible for +auto-proxying or other kinds of bean post-processing. For example, if you have a +dependency annotated with `@Resource` where the field or setter name does not directly correspond to the declared name of a bean and no name attribute is used, Spring accesses other beans for matching them by type. ==== @@ -164,7 +177,48 @@ Kotlin:: ---- ====== -The following `beans` element uses the `InstantiationTracingBeanPostProcessor`: +You can register the `InstantiationTracingBeanPostProcessor` with Java configuration +by using a `static` `@Bean` method (recommended to avoid eager initialization of the +configuration class and other beans): + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"] +---- + @Configuration + public class AppConfig { + + @Bean + public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() { + return new InstantiationTracingBeanPostProcessor(); + } + + // ... other bean definitions + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"] +---- + @Configuration + class AppConfig { + + @Bean + companion object { + @JvmStatic + fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor() + } + + // ... other bean definitions + } +---- +====== + +Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the +`bean` element with XML configuration: [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -301,6 +355,23 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean post-processors, at the appropriate time. You can deploy these post-processor beans as you would any other bean. +When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a +`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts +with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the +configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods" +section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc +for details and an example. + +For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type, +you should see an INFO-level log message similar to the following. + +[quote] +@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's +BeanFactoryPostProcessor interface. This will result in a failure to process annotations +such as @Autowired, @Resource, and @PostConstruct within the method's declaring +@Configuration class. Add the 'static' modifier to this method to avoid these container +lifecycle issues; see @Bean javadoc for complete details. + NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure ``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a `Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java index 8baf87b85f1..aba123e59d8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java @@ -40,6 +40,13 @@ import org.springframework.beans.BeansException; * A {@code BeanFactoryPostProcessor} may also be registered programmatically * with a {@code ConfigurableApplicationContext}. * + *

When registering a {@code BeanFactoryPostProcessor} via an {@code @Bean} method + * in a {@code @Configuration} class, use a {@code static} method to avoid eager + * initialization of other beans in the configuration class. See the + * "BeanFactoryPostProcessor-returning {@code @Bean} methods" section in + * {@link org.springframework.context.annotation.Bean @Bean}'s javadoc for details + * and an example. + * *

Ordering

*

{@code BeanFactoryPostProcessor} beans that are autodetected in an * {@code ApplicationContext} will be ordered according to diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java index 40d50ce127e..26c990abd73 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java @@ -34,6 +34,14 @@ import org.springframework.lang.Nullable; * created. A plain {@code BeanFactory} allows for programmatic registration of * post-processors, applying them to all beans created through the bean factory. * + *

When registering a {@code BeanPostProcessor} via an {@code @Bean} method in + * a {@code @Configuration} class, use a {@code static} method with ideally no + * dependencies in order to avoid eager initialization that can make other beans + * ineligible for full post-processing. See the "BeanPostProcessor-returning + * {@code @Bean} methods" section in + * {@link org.springframework.context.annotation.Bean @Bean}'s javadoc for details + * and an example. + * *

Ordering

*

{@code BeanPostProcessor} beans that are autodetected in an * {@code ApplicationContext} will be ordered according to diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java index 37d56f742e3..e5d1d058096 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java @@ -194,6 +194,31 @@ import org.springframework.core.annotation.AliasFor; * issued for any non-static {@code @Bean} methods having a return type assignable to * {@code BeanFactoryPostProcessor}. * + *

{@code BeanPostProcessor}-returning {@code @Bean} methods

+ * + *

Similarly, special consideration must be taken for {@code @Bean} methods that return Spring + * {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor} + * ({@code BPP}) types. Because {@code BPP} objects must be instantiated early in the container + * lifecycle, a non-static {@code @Bean} method that returns a {@code BPP} will cause eager + * initialization of its declaring {@code @Configuration} class, which can make other beans in the + * {@code @Configuration} class (as well as depencencies of those beans) ineligible for full + * post-processing. To avoid these lifecycle issues, mark {@code BPP}-returning {@code @Bean} + * methods as {@code static}. For example: + * + *

+ * @Bean
+ * public static MyBeanPostProcessor myBeanPostProcessor() {
+ *     return new MyBeanPostProcessor();
+ * }
+ * + * By marking this method as {@code static}, it can be invoked without causing instantiation of its + * declaring {@code @Configuration} class. Furthermore, the method should ideally not declare any + * dependencies so that the container does not need to instantiate other beans to create the + * post-processor, which would make those beans ineligible for post-processing as well. For any such + * bean, you should see a WARN-level log message similar to the following: "Bean 'someBean' of type + * [org.example.SomeType] is not eligible for getting processed by all BeanPostProcessors (for example: + * not eligible for auto-proxying)". + * * @author Rod Johnson * @author Costin Leau * @author Chris Beams