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.
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!
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.