不重启服务,实现 Spring Bean 注册和销毁

20 阅读2分钟

背景

Spring Boot Starters 的自动装配机制给我们提供了非常丰富的扩展点,但有时候需要动态开启或者关闭某些组件,只能修改配置,重启服务才能生效。例如,我们接入了 Arthas 工具,有时候需要关闭,有时候需要开启,但又不能随意重启服务。

目标

监听 Spring Boot Starters 组件的配置变化,动态注册销毁 Bean。

实现

以 Arthas Spring Boot Starter 组件为例。我们发现 Spring Cloud 组件提供了 EnvironmentChangeEvent 作为配置变更事件,传入 ApplicationListener 监听器可以实现配置变化的监听,代码片段如下:

@RequiredArgsConstructor
@Slf4j
public class ArthasEnvironmentChangeListener implements ApplicationListener<EnvironmentChangeEvent> {

    public static final String ARTHAS_AGENT = "arthasAgent";

    private final ApplicationContext applicationContext;

    private final Environment environment;

    private final ArthasProperties arthasProperties;

    private final Map<String, String> arthasConfigMap;

    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        Set<String> keys = event.getKeys();
        for (String key : keys) {
            if ("spring.arthas.enabled".equals(key)) {
                if (Boolean.parseBoolean(environment.getProperty(key))) {
                    registerBean(); // 注册 Bean
                } else {
                    destroyBean(); // 销毁 Bean
                }
                break;
            }
        }
    }

    private void registerBean() {
        DefaultListableBeanFactory defaultListableBeanFactory = getDefaultListableBeanFactory();
        if (defaultListableBeanFactory.containsBean(ARTHAS_AGENT)) {  // 检查 Bean 是否存在 Spring 缓存
            defaultListableBeanFactory.getBean(ARTHAS_AGENT)).init(); 
        } else {
            defaultListableBeanFactory.registerSingleton(ARTHAS_AGENT, new ArthasAgent(arthasConfigMap, arthasProperties));
        }
    }

    private void destroyBean() {
        DefaultListableBeanFactory defaultListableBeanFactory = getDefaultListableBeanFactory();
        if (defaultListableBeanFactory.containsBean(ARTHAS_AGENT)) {
            defaultListableBeanFactory.getBean(ARTHAS_AGENT)).destroy();
            defaultListableBeanFactory.destroySingleton(ARTHAS_AGENT); // 从 Spring 缓存移除 Bean
        }
    }

    @NotNull
    private DefaultListableBeanFactory getDefaultListableBeanFactory() {
        return (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
    }
}

ArthasEnvironmentChangeListener 复制到 ArthasAutoConfiguration 类注册 Bean。

@AutoConfigureBefore(ArthasConfiguration.class)
@EnableConfigurationProperties({ SpringArthasProperties.class, ArthasProperties.class })
@RequiredArgsConstructor
@Slf4j
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Configuration(proxyBeanMethods = false)
public class ArthasAutoConfiguration {

    // 注册监听器
    @Bean
    public ArthasEnvironmentChangeListener arthasEnvironmentChangeListener(
    @Autowired @Qualifier(ARTHAS_CONFIG_MAP) Map<String, String> arthasConfigMap) {
        return new ArthasEnvironmentChangeListener(applicationContext, environment, arthasProperties, arthasConfigMap);
    }

    // 启动时判断是否开启,如果开启则注册 Bean
    @ConditionalOnProperty(
        name = {"spring.arthas.enabled"},
        matchIfMissing = true
    )
    @Primary
    @Bean
    public ArthasAgent arthasAgent(@Autowired @Qualifier(ARTHAS_CONFIG_MAP) Map<String, String> arthasConfigMap) {
        arthasConfigMap = StringUtils.removeDashKey(arthasConfigMap);
        return init(arthasConfigMap, environment, arthasProperties); // 初始化 Bean,细节省略
    }
}

ArthasAutoConfiguration 类添加到 spring.factories 文件即可。

在 Nacos 设置 spring.arthas.enabled=false,期望关闭 Arthas。

从控制台可以看到 Destroy arthas from ws:... 字眼。

在 Nacos 设置 spring.arthas.enabled=true,期望开启 Arthas。

从控制台可以看到 Register arthas to ws:... 字眼。

产出

研发团队在生产环境的配置中心发布配置更新,可以实现应用零停机动态注册和销毁 Bean,解决了特定场景的需求。

本文涉及的代码完全开源,感兴趣的伙伴可以查阅 eden-arthas-spring-boot-starter