前言
在前面的文章中,我们拆解了Spring的IoC、AOP、事务管理和设计模式。这些是Spring的核心机制。但Spring还有一个强大的能力:扩展点------它提供了多个钩子,让你可以在Bean创建的各个阶段插入自定义逻辑。
面试中,扩展点是区分"会用Spring"和"能定制Spring"的分水岭:
"BeanPostProcessor和BeanFactoryPostProcessor有什么区别?"
"InitializingBean和@PostConstruct哪个先执行?"
"Aware接口是干什么用的?"
"如何优雅地批量注册Bean定义?"
本文拆解Spring中最重要的四个扩展点,从执行时机到适用场景,让你掌握随时介入Bean创建流程的能力。
本文核心问题:
- BeanPostProcessor和BeanFactoryPostProcessor有什么区别?各自的执行时机是什么?
- InitializingBean和@PostConstruct有什么区别?执行顺序是怎样的?
- Aware接口家族分别可以在什么时机获取容器的哪些核心组件?
- ImportBeanDefinitionRegistrar和ImportSelector各在什么场景下使用?
- 这些扩展点是如何协同工作的?Bean从创建到初始化的完整扩展链是什么?
读完本文,你将对Spring的扩展点拥有从原理到实战的完整理解。
一、BeanFactoryPostProcessor------修改Bean的定义
疑问:BeanFactoryPostProcessor是什么?它和BeanPostProcessor有什么区别?
回答:BeanFactoryPostProcessor用于修改Bean的定义(元数据),在所有BeanDefinition加载完成之后、Bean实例化之前执行。BeanPostProcessor作用于Bean实例化之后,用于修改实例本身。
1.1 执行时机
Spring容器启动流程:
1. 扫描所有配置类(@Configuration、@ComponentScan)
2. 解析所有@Bean、@Component等 → 生成BeanDefinition
3. BeanFactoryPostProcessor执行 → 修改BeanDefinition ← 我们在这里!
4. 遍历所有BeanDefinition → 实例化Bean
5. BeanPostProcessor执行 → 修改Bean实例
6. 容器启动完成
BeanFactoryPostProcessor在"定义已加载但实例还未创建"的窗口期执行,它可以修改BeanDefinition中的属性值、调整作用域、或者新增/删除Bean定义。
1.2 典型应用:覆盖属性占位符
PropertySourcesPlaceholderConfigurer是BeanFactoryPostProcessor的一个实现,它负责解析${}占位符。容器启动时所有BeanDefinition已经生成,但@Value注解的值还是${server.port}这种未解析的字符串。这个PostProcessor在此时扫描所有BeanDefinition,将占位符替换为application.yml中的实际值,然后Bean才开始实例化。
1.3 典型应用:强制指定数据源
java
// 一个自定义的BeanFactoryPostProcessor:强制将DataSource的实现类替换为Druid
@Component
public class DataSourceOverridePostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 获取DataSource的BeanDefinition
BeanDefinition definition = beanFactory.getBeanDefinition("dataSource");
// 覆盖为Druid连接池(不管原始定义是什么实现类)
definition.setBeanClassName("com.alibaba.druid.pool.DruidDataSource");
definition.getPropertyValues().add("initialSize", "5");
definition.getPropertyValues().add("maxActive", "20");
}
}
这个PostProcessor在Bean实例化之前,强制修改了DataSource的BeanClassName和属性值。 不管你原始配置里写的是什么数据源,到这里都会被统一替换为Druid连接池。
二、BeanPostProcessor------修改Bean的实例
疑问:BeanPostProcessor又是什么?和BeanFactoryPostProcessor有什么区别?
回答:BeanFactoryPostProcessor操作的是BeanDefinition(元数据),在实例化之前执行。BeanPostProcessor操作的是Bean实例,在实例化之后、初始化前后执行。
2.1 两个核心方法
java
public interface BeanPostProcessor {
// 在初始化方法(@PostConstruct / afterPropertiesSet)之前执行
default Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean; // 可以返回原Bean或代理对象
}
// 在初始化方法之后执行
default Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean; // AOP代理对象就是在这里生成的!
}
}
2.2 AOP代理的生成时机
@Transactional、@Cacheable、自定义AOP切面的代理对象,都是在postProcessAfterInitialization中生成的。 Spring遍历所有BeanPostProcessor,如果发现某个Bean需要被代理,就返回代理对象替代原Bean,后续容器中持有的就是这个代理对象。这也是事务失效的底层原因------自调用绕过的就是这个代理。
2.3 自定义BeanPostProcessor:注入追踪日志
java
// 给所有Controller的方法调用自动加日志
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 只处理有@RestController注解的Bean
if (bean.getClass().isAnnotationPresent(RestController.class)) {
// 为这个Bean创建代理,插入日志逻辑
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
log.info("调用 {} 的 {} 方法", beanName, method.getName());
Object result = method.invoke(bean, args);
log.info("{} 的 {} 方法执行完成", beanName, method.getName());
return result;
}
);
}
return bean;
}
}
这个PostProcessor在初始化完成后,检查每个Bean是否有@RestController注解,有就创建动态代理包装它。 代理在每个方法调用前后打印追踪日志,Controller本身的业务代码一行业务逻辑都没受到影响。
三、InitializingBean和@PostConstruct------初始化扩展
疑问:@PostConstruct和InitializingBean.afterPropertiesSet()有什么区别?执行顺序是怎样的?
回答:两者都是在Bean属性赋值完成后执行的初始化回调,但执行顺序不同:@PostConstruct先执行,afterPropertiesSet()后执行,最后是自定义的init-method。
3.1 执行顺序
Bean实例化(调用构造器)
↓
属性赋值(@Autowired注入依赖)
↓
BeanPostProcessor.postProcessBeforeInitialization
↓
@PostConstruct 方法执行 ← 第一个初始化钩子
↓
InitializingBean.afterPropertiesSet() ← 第二个初始化钩子
↓
@Bean(initMethod = "customInit") ← 第三个初始化钩子
↓
BeanPostProcessor.postProcessAfterInitialization(AOP在这里增强)
↓
Bean就绪
3.2 一个实例:多种初始化方式并存
java
@Component
public class OrderService implements InitializingBean {
private OrderMapper orderMapper;
@Autowired // 构造后注入
private void setOrderMapper(OrderMapper orderMapper) {
this.orderMapper = orderMapper;
}
@PostConstruct // 最先执行
public void init() {
log.info("1. @PostConstruct:orderMapper是否已注入?{}", orderMapper != null);
// 此时依赖已注入,可以校验非空
Assert.notNull(orderMapper, "orderMapper不能为空");
}
@Override // 其次执行
public void afterPropertiesSet() {
log.info("2. afterPropertiesSet:执行预热加载,把常用数据放入本地缓存");
preloadCache();
}
}
@PostConstruct适合做依赖校验 ------此时@Autowired已经完成,可以断言关键依赖不为空。afterPropertiesSet()适合做预热加载------比如从数据库加载常用数据到本地缓存,此时所有依赖已就绪,Bean即将投入使用。
四、Aware接口------向容器"要东西"
疑问:Aware接口是做什么的?有哪些常用子接口?
回答:Aware接口让一个Bean能感知到Spring容器中的核心组件------通过回调,容器把自身的核心能力传递给你的Bean。
4.1 常用Aware接口
java
// 如果你想让Bean感知到自己在容器中的名字
public class MyBean implements BeanNameAware {
@Override
public void setBeanName(String name) {
log.info("我在容器中的名字是: {}", name);
}
}
// 如果你想让Bean获取到BeanFactory(容器)
public class MyBean implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
BeanFactory bf = this.beanFactory; // 容器本身
}
}
// 如果你想让Bean直接获取到ApplicationContext
public class MyBean implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContext ctx = this.applicationContext; // 应用上下文
ctx.getBean(...); // 可以主动查找其他Bean
ctx.publishEvent(...); // 可以发布事件
}
}
4.2 Aware接口汇总
| Aware接口 | 获取的对象 | 应用场景 |
|---|---|---|
| BeanNameAware | Bean自身在容器中的名字 | 需要在运行时知道自己的beanName |
| BeanFactoryAware | 当前BeanFactory | 需要动态获取其他Bean |
| ApplicationContextAware | 完整的应用上下文 | 需主动获取Bean、发布事件、访问容器能力 |
| EnvironmentAware | 配置环境对象 | 获取配置项或动态切换Profile |
| ResourceLoaderAware | 资源加载器 | 需加载classpath下的文件 |
| ApplicationEventPublisherAware | 事件发布器 | 需要在非Spring管理的类中发布事件 |
五、ImportSelector和ImportBeanDefinitionRegistrar------批量注册组件
疑问:SpringBoot的@EnableAutoConfiguration就是通过@Import导入选择器的,具体是怎么做的?
回答:@Import可以导入普通类、ImportSelector或ImportBeanDefinitionRegistrar。ImportSelector根据条件选择要导入的类,ImportBeanDefinitionRegistrar直接向容器动态注册新的BeanDefinition。
5.1 ImportSelector------根据条件选择配置类
java
// AsyncConfigurationSelector根据@EnableAsync的adviceMode选择具体的配置类
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] { ProxyAsyncConfiguration.class.getName() };
case ASPECTJ:
return new String[] { AspectJAsyncConfiguration.class.getName() };
default:
return null;
}
}
}
5.2 ImportBeanDefinitionRegistrar------直接注册Bean
java
// 在权限系统中,用于扫描所有@RequirePermission注解的方法并批量注册拦截器
public class PermissionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 1. 扫描所有标注了@RequirePermission的类
Set<MethodMetadata> methods = findAnnotatedMethods("@RequirePermission");
// 2. 为每个权限码动态注册一个权限校验Bean
for (MethodMetadata method : methods) {
String permCode = method.getAnnotationAttributes()
.get("value").toString();
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(PermissionValidator.class)
.addConstructorArgValue(permCode);
registry.registerBeanDefinition("permissionValidator_" + permCode,
builder.getBeanDefinition());
}
}
}
ImportBeanDefinitionRegistrar可以和自定义注解配合 ,写一个@EnableXXX注解,用户在配置类上标注后自动注册一组相关的Bean定义。这是SpringBoot自动配置starter常用的扩展手段。
六、扩展点执行顺序全景图
BeanDefinition加载完成
↓
BeanFactoryPostProcessor.postProcessBeanFactory()
↓ 修改BeanDefinition的元数据
↓
---------------------------------------------------------
实例化阶段
---------------------------------------------------------
↓
遍历BeanDefinition → 调用构造器实例化Bean
↓
BeanPostProcessor.postProcessBeforeInstantiation(可返回代理替代原Bean)
↓
---------------------------------------------------------
属性赋值阶段
---------------------------------------------------------
↓
@Autowired / @Value 注解的字段被注入
↓
Aware接口回调:
BeanNameAware.setBeanName()
BeanFactoryAware.setBeanFactory()
ApplicationContextAware.setApplicationContext()
↓
---------------------------------------------------------
初始化阶段
---------------------------------------------------------
↓
BeanPostProcessor.postProcessBeforeInitialization
↓
@PostConstruct 方法
↓
InitializingBean.afterPropertiesSet()
↓
@Bean(initMethod) 或其他自定义初始化方法
↓
BeanPostProcessor.postProcessAfterInitialization ← AOP在这里生成代理!
↓
---------------------------------------------------------
Bean就绪,放入一级缓存
↓
容器启动完成
↓
---------------------------------------------------------
销毁阶段
---------------------------------------------------------
↓
@PreDestroy 方法
↓
DisposableBean.destroy()
↓
@Bean(destroyMethod)
七、面试中这样回答
面试官:"BeanPostProcessor和BeanFactoryPostProcessor有什么区别?"
回答框架:
"两者名字相似,但执行时机和操作对象完全不同。BeanFactoryPostProcessor在所有BeanDefinition加载完成之后、Bean实例化之前执行------它操作的是Bean的元数据,可以修改BeanDefinition中的属性值、作用域等,但触碰不到Bean实例。BeanPostProcessor在Bean实例化之后、初始化前后执行------它操作的是Bean实例本身。postProcessBeforeInitialization在@PostConstruct之前执行,postProcessAfterInitialization在@PostConstruct之后执行。AOP代理就是在postProcessAfterInitialization中生成的------这就是为什么自调用事务会失效,因为this调用绕过了这里返回的代理对象。"
总结
- BeanFactoryPostProcessor在实例化前修改BeanDefinition (元数据),PropertySourcesPlaceholderConfigurer解析
${}占位符是最经典的应用 - BeanPostProcessor在实例化后修改Bean实例:postProcessBeforeInitialization在@PostConstruct之前,postProcessAfterInitialization在之后------AOP在这里生成代理
- InitializingBean和@PostConstruct的执行顺序:@PostConstruct先于afterPropertiesSet,前者适合依赖校验,后者适合预热加载
- Aware接口让Bean向容器"要东西":ApplicationContextAware获取上下文、BeanNameAware获取自己的容器名
- ImportSelector和ImportBeanDefinitionRegistrar实现批量注册:ImportSelector根据条件选择返回类名列表,ImportBeanDefinitionRegistrar直接向容器注册新的BeanDefinition
下一篇预告:Spring原理(八)------Spring与微服务:从SpringBoot到SpringCloud的演进。拆解微服务架构的核心组件------Nacos注册中心与配置中心、Sentinel熔断限流、Gateway网关------在Spring Cloud生态中各自解决什么问题,以及秒杀系统的微服务实际架构。