跳转至

可扩展 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 收集所有插件实现

定义一个接口:

public interface Plugin {
    String type();
    void execute(PluginContext ctx);
}

业务方实现:

@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 接口。
  • 每个渠道一个实现 jarwechat-pay-pluginalipay-plugin
  • 自动装配:starter 用 @ConditionalOnClass 检测渠道类是否存在。
  • 配置中心动态开关:Nacos/Apollo 配置 plugin.wechat.enabled=false,配合 @RefreshScope 或自定义 listener 动态注册/卸载插件。

四、高级扩展点

扩展点 作用
BeanFactoryPostProcessor 修改 BeanDefinition
BeanPostProcessor 包装 Bean 实例(AOP 就是它实现的)
ImportBeanDefinitionRegistrar 编程式注册 Bean
ApplicationContextInitializer 容器刷新前做初始化
ApplicationListener / @EventListener 监听事件
EnvironmentPostProcessor 修改环境属性

五、注意事项

  1. 避免循环依赖:插件接口要稳定,实现不要反向依赖主应用内部类。
  2. 配置隔离:每个插件用独立的 prefix,避免配置冲突。
  3. 健康检查:提供 HealthIndicator,插件加载失败不影响主应用启动。
  4. 版本兼容:插件接口要向后兼容,废弃方法用 @Deprecated 而非直接删。

面试加分

  • 答出 Spring Boot Starter 是"约定大于配置"的自动装配,本质是 spring.factories(Boot 2.7+ 改为 AutoConfiguration.imports)。
  • 结合 SPI(ServiceLoader)与 Spring 的 @Conditional,可以做出"零配置插拔"效果。