Backend Development 13 min read

Master Spring’s Extension Points: BeanPostProcessor, BeanFactoryPostProcessor, and More

This article explains Spring and SpringBoot’s key extension points—including BeanPostProcessor, BeanFactoryPostProcessor, ApplicationContextInitializer, EnvironmentPostProcessor, and PropertySourceLocator—provides detailed code examples, registration steps, and trigger timing to help developers customize and extend Spring applications effectively.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring’s Extension Points: BeanPostProcessor, BeanFactoryPostProcessor, and More

1. Introduction

Spring and SpringBoot provide several extension points that let developers customize the framework at different stages of the application lifecycle. The five main extension points covered are:

BeanPostProcessor (Spring) – Executes custom logic before or after bean initialization.

BeanFactoryPostProcessor (Spring) – Modifies bean definitions before any beans are instantiated.

ApplicationContextInitializer (Spring) – Allows early initialization of the ApplicationContext before it is refreshed.

EnvironmentPostProcessor (SpringBoot) – Handles and modifies environment properties during application startup.

PropertySourceLocator (SpringCloud) – Extends property file loading, often used to pull configuration from remote sources such as Nacos.

The following sections demonstrate how to use each extension point and when they are triggered.

2. Practical Cases

2.1 BeanPostProcessor

Custom implementation:

<code>class PackBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Person) {
            Person p = (Person) bean;
            p.setAge(33);
            System.out.println(beanName + ", before");
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Person) {
            System.out.println(beanName + ", after");
        }
        return bean;
    }
}
</code>

Registration and test:

<code>try (GenericApplicationContext context = new GenericApplicationContext()) {
    context.registerBean(PackBeanPostProcessor.class);
    context.registerBean("person", Person.class);
    context.refresh();
    System.out.println(context.getBean(Person.class));
}
</code>

Console output:

<code>person, before
person, after
Person [age=33]
</code>

Spring’s built‑in BeanPostProcessors include:

CommonAnnotationBeanPostProcessor – processes @Resource and similar annotations.

AutowiredAnnotationBeanPostProcessor – processes @Autowired.

AnnotationAwareAspectJAutoProxyCreator – creates AOP proxies.

Trigger timing (simplified):

<code>public abstract class AbstractAutowireCapableBeanFactory {
    protected Object doCreateBean() {
        Object exposedObject = bean;
        try {
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    }
    protected Object initializeBean() {
        // before
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        // after
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
}
</code>

2.2 BeanFactoryPostProcessor

Custom implementation that changes a bean’s scope before instantiation:

<code>class PackBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition definition = beanFactory.getBeanDefinition("person");
        definition.setScope("prototype");
    }
}
</code>

Test code:

<code>try (GenericApplicationContext context = new GenericApplicationContext()) {
    context.registerBean(PackBeanFactoryPostProcessor.class);
    context.registerBean("person", Person.class);
    context.refresh();
    System.out.println(context.getBean(Person.class).hashCode());
    System.out.println(context.getBean(Person.class).hashCode());
}
</code>

Console output shows different instances, confirming prototype scope:

<code>891095110
2011482127
</code>

A special BeanFactoryPostProcessor – BeanDefinitionRegistryPostProcessor – can register beans dynamically:

<code>class PackBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        registry.registerBeanDefinition("person", BeanDefinitionBuilder.genericBeanDefinition(Person.class).getBeanDefinition());
    }
}
</code>

Trigger timing occurs during AbstractApplicationContext.refresh() where all BeanFactoryPostProcessors are invoked before singleton beans are instantiated.

2.3 ApplicationContextInitializer

Typical use in web applications; example for SpringBoot:

<code>public class PackApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment env = context.getEnvironment();
        Map<String, Object> source = new HashMap<>();
        source.put("pack.age", 20);
        MapPropertySource ps = new MapPropertySource("pack", source);
        env.getPropertySources().addLast(ps);
    }
}
</code>

Registration (in spring.factories ):

<code>org.springframework.context.ApplicationContextInitializer=\
com.pack.initializer.PackApplicationContextInitializer
</code>

Test class prints the custom property:

<code>public class SpringbootComprehensiveApplication implements CommandLineRunner {
    @Value("${pack.name}")
    private String name;
    @Override
    public void run(String... args) throws Exception {
        System.out.println("pack.name = " + name);
    }
}
</code>

Console output:

<code>pack.name = 张三
</code>

Trigger timing: SpringApplication.run() calls prepareContext() , which applies all registered ApplicationContextInitializer instances before the context is refreshed.

2.4 EnvironmentPostProcessor

Used to modify the environment before the Spring context is refreshed. Typical tasks include reading environment variables, loading custom *.properties or *.yml files, and extending the container configuration.

<code>public class PackEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        PropertySource<?> ps = environment.getPropertySources().get("pack");
        if (ps == null) {
            Map<String, Object> source = new HashMap<>();
            source.put("pack.age", "66");
            ps = new MapPropertySource("pack", source);
            environment.getPropertySources().addLast(ps);
        } else {
            MapPropertySource mps = (MapPropertySource) ps;
            mps.getSource().put("pack.age", "88");
        }
    }
}
</code>

Registration (in spring.factories ):

<code>org.springframework.boot.env.EnvironmentPostProcessor=\
com.pack.environment.PackEnvironmentPostProcessor
</code>

Trigger timing occurs during SpringApplication.prepareEnvironment() , which fires the ApplicationEnvironmentPreparedEvent and iterates over all registered EnvironmentPostProcessor implementations.

2.5 PropertySourceLocator

Primarily used in SpringCloud to load property sources from remote systems such as Nacos.

<code>@Configuration
public class CustomPropertySourceLocator implements PropertySourceLocator {
    @Override
    public PropertySource<?> locate(Environment environment) {
        Map<String, Object> values = new HashMap<>();
        values.put("net.timeout", 10000);
        return new MapPropertySource("net", values);
    }
}
</code>

Registration (in spring.factories for SpringCloud bootstrap):

<code>org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.pack.propertysource.CustomPropertySourceLocator
</code>

Trigger timing is the same as the EnvironmentPostProcessor: it runs when the ApplicationEnvironmentPreparedEvent is published.

2.6 Nacos Configuration Example

Sample bootstrap.yml for Nacos (version 2.1, SpringCloud 2021.0.6):

<code>spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: xxoo
      config:
        enabled: true
        file-extension: yaml
        group: CAPP
        name: composite
        extension-configs:
          - dataId: other.yaml
            group: CAPP
            refresh: true
        shared-configs:
          - dataId: redis.yaml
            group: CAPP
            refresh: true
</code>

Relevant source code snippet from Nacos’ PropertySourceLocator implementation:

<code>public class NacosPropertySourceLocator implements PropertySourceLocator {
    public PropertySource<?> locate(Environment env) {
        nacosConfigProperties.setEnvironment(env);
        ConfigService configService = nacosConfigManager.getConfigService();
        long timeout = nacosConfigProperties.getTimeout();
        nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
        String name = nacosConfigProperties.getName();
        String dataIdPrefix = nacosConfigProperties.getPrefix();
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = name;
        }
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = env.getProperty("spring.application.name");
        }
        CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
        loadSharedConfiguration(composite);
        loadExtConfiguration(composite);
        loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
        return composite;
    }
}
</code>

By leveraging these extension points, developers can tailor Spring’s behavior to meet specific business requirements and extend the framework’s functionality.

Finished!

NacosSpringBootBeanPostProcessorSpringCloudExtensionPointsEnvironmentPostProcessor
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.