可扩展 Spring 插件开发思路(携程)¶
一、目标¶
做一个"插件化"框架,业务方只需引入 jar 包、写个配置或实现接口,主应用就能自动加载其功能,不需要修改主应用代码。常见场景:多租户、多支付渠道、多消息渠道、多推送渠道。
二、核心思路¶
Spring 扩展点 + SPI + 自动装配。
1. Spring Boot Starter 模式¶
把每个插件做成一个 xxx-spring-boot-starter,主应用引入依赖即生效。
my-plugin-spring-boot-starter/
├── src/main/java/.../MyPluginAutoConfiguration.java
└── src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
AutoConfiguration:
@AutoConfiguration
@ConditionalOnClass(Plugin.class)
@EnableConfigurationProperties(PluginProperties.class)
public class MyPluginAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public PluginService pluginService(PluginProperties props) {
return new DefaultPluginService(props);
}
@Bean
public PluginScanner pluginScanner(PluginService service) {
return new PluginScanner(service);
}
}
2. 用 SPI 收集所有插件实现¶
定义一个接口:
业务方实现:
@Component
public class WechatPayPlugin implements Plugin {
public String type() { return "WECHAT"; }
public void execute(PluginContext ctx) { ... }
}
主应用收集所有 Plugin Bean:
@Component
public class PluginManager implements ApplicationContextAware {
private Map<String, Plugin> plugins = new HashMap<>();
public void setApplicationContext(ApplicationContext ctx) {
ctx.getBeansOfType(Plugin.class).values()
.forEach(p -> plugins.put(p.type(), p));
}
public Plugin get(String type) { return plugins.get(type); }
}
3. 配合 @Conditional 按需启用¶
@Bean
@ConditionalOnProperty(prefix = "plugin.wechat", name = "enabled", havingValue = "true")
public WechatPayPlugin wechatPayPlugin() { ... }
4. 用 BeanFactoryPostProcessor 做扩展¶
在 BeanDefinition 加载后、实例化前,扫描指定包下所有类,按条件注册 BeanDefinition。适合"包路径约定即插件"的场景。
三、携程的典型做法¶
- SPI 接口定义:在核心包定义
PaymentChannel接口。 - 每个渠道一个实现 jar:
wechat-pay-plugin、alipay-plugin。 - 自动装配:starter 用
@ConditionalOnClass检测渠道类是否存在。 - 配置中心动态开关:Nacos/Apollo 配置
plugin.wechat.enabled=false,配合@RefreshScope或自定义 listener 动态注册/卸载插件。
四、高级扩展点¶
| 扩展点 | 作用 |
|---|---|
BeanFactoryPostProcessor |
修改 BeanDefinition |
BeanPostProcessor |
包装 Bean 实例(AOP 就是它实现的) |
ImportBeanDefinitionRegistrar |
编程式注册 Bean |
ApplicationContextInitializer |
容器刷新前做初始化 |
ApplicationListener / @EventListener |
监听事件 |
EnvironmentPostProcessor |
修改环境属性 |
五、注意事项¶
- 避免循环依赖:插件接口要稳定,实现不要反向依赖主应用内部类。
- 配置隔离:每个插件用独立的 prefix,避免配置冲突。
- 健康检查:提供
HealthIndicator,插件加载失败不影响主应用启动。 - 版本兼容:插件接口要向后兼容,废弃方法用
@Deprecated而非直接删。
面试加分
- 答出 Spring Boot Starter 是"约定大于配置"的自动装配,本质是
spring.factories(Boot 2.7+ 改为AutoConfiguration.imports)。 - 结合 SPI(
ServiceLoader)与 Spring 的@Conditional,可以做出"零配置插拔"效果。