How to dynamically refresh the configuration of SpringBoot (custom)

The custom part of this article includes

1. Custom refresh event (with RefreshScope to achieve automatic refresh)

2. Force refresh the context (restart the entire ApplicationContext)

3. Implement Http interface loading of configuration files (similar to Nacos)

Custom refresh event (with RefreshScope to achieve automatic refresh)

Manual refresh of configuration based on org.springframework.cloud.context.config.annotation.RefreshScope

Spring uses org.springframework.cloud.context.refresh.ContextRefresher to refresh the context

Can be used in the following ways

@RestController
@RequestMapping("/lab")
public class LabRefreshController implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Autowired
    private ContextRefresher contextRefresher;

    @Autowired

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        LabRunController. applicationContext = applicationContext;
    }

    @GetMapping("/refresh0")
    public BaseResponse<Object> refresh0() {
        new Thread() {

            @Override
            public void run() {
                applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Defs config"));
            }

        }.start();
        return BaseResponse.success("refresh0 done " + LocalDateTime.now());
    }

    @GetMapping("/refresh1")
    public BaseResponse<Object> refresh1() {
        new Thread() {

            @Override
            public void run() {
                contextRefresher. refresh();
            }

        }.start();
        return BaseResponse.success("refresh1 done " + LocalDateTime.now());
    }

}



## refresh0 Realize refresh by publishing refresh event
## refresh1 directly calls ContextRefresher to perform refresh

These two types of refresh are for the org.springframework.cloud.context.config.annotation.RefreshScope annotation
The bean is valid, if not, it will not be refreshed

Forcibly refresh the context (restart the entire ApplicationContext)

Manual refresh of configuration based on org.springframework.cloud.context.config.annotation.RefreshScope

For beans that do not have this annotation, they cannot take effect. You can restart the entire ApplicationContext to reload the entire container. This operation will interrupt the service, so use it with caution

## Bootstrap startup class


@SpringBootApplication(scanBasePackages = {"com.app"})
@Slf4j
public class AppApiApplication {
    public static void main(String[] args) {
        CLASSLOADER = Thread.currentThread().getContextClassLoader();
        ARGS = args;

        AppApiApplication.CURRENT = SpringApplication.run(AppApiApplication.class, args);
        log.info("Application start up...");
    }

    public static void remain() {
        CURRENT = SpringApplication.run(AppApiApplication.class, ARGS);
        log.info("Application restart up...");
    }

    public static ConfigurableApplicationContext CURRENT;

    public static String[] ARGS;

    public static ClassLoader CLASSLOADER;
}

## LabRefreshController refresh method


@Slf4j
@RestController
@RequestMapping("/lab")
public class LabRefreshController {

    @GetMapping("/restart")
    private BaseResponse<Object> labapp() {
        Map<String, Object> map = new HashMap<>();
        map.put("before-processId", ProcessIdUtil.getProcessId());
        map.put("before-current", Objects.toString(AppApiApplication.CURRENT));
        map.put("before-classLoader", Objects.toString(AppApiApplication.CLASSLOADER));
        map.put("before-args", Objects.toString(AppApiApplication.ARGS));

        new Thread(() -> {
            try {
                if (AppApiApplication.CURRENT == null || !AppApiApplication.CURRENT.isActive()) {
                    log.info("labapp skip, null ctx");
                    return;
                }
                Thread.currentThread().setContextClassLoader(AppApiApplication.CLASSLOADER);
                AppApiApplication. CURRENT. close();

                AppApiApplication.remain();

                map.put("after-processId", ProcessIdUtil.getProcessId());
                map.put("after-current", Objects.toString(AppApiApplication.CURRENT));
                map.put("after-classLoader", Objects.toString(AppApiApplication.CLASSLOADER));
                map.put("after-args", Objects.toString(AppApiApplication.ARGS));

            } catch (Exception e) {
                log. warn("labapp, Exception: {}", e);
            } finally {
                log.info("labapp, map: {}", JacksonUtil.writeSimpleValue(map));
            }
        }).start();

        return BaseResponse.success("labapp done " + LocalDateTime.now());
    }

}

Implement Http interface loading of configuration files (similar to Nacos)

Locally implement local loading or loading remote configuration, put data into Environment and participate in the generation and use of Bean and Properties, similar to Nacos

1. Define the basic information needed to obtain the configuration

Similar to the address and name of Nacos (com.alibaba.cloud.nacos.NacosConfigProperties)

/**
 *
 * @author yonghua.cao
 * @date 2023/05/22
 */
@Data
@ConfigurationProperties(DefsConfigProperties. PREFIX)
public class DefsConfigProperties {

    public static final String PREFIX = "spring.cloud.defs.config";

    String url;

}

2. Implement PropertySourceLocator and load the required configuration

Similar to Nacos (com.alibaba.cloud.nacos.client.NacosPropertySourceLocator)

The smaller the @Order value, the higher the priority

@Order(-100) and @Order(-99) define a configuration at the same time, and the configuration in @Order(-100) takes effect

/**
 *
 * @author yonghua.cao
 * @date 2023/05/22
 */
@Slf4j
@Order(-100)
public class DefsPropertySourceLocator implements PropertySourceLocator {

    private DefsConfigProperties defsConfigProperties;

    private static final String PROPERTY_SOURCE_NAME = "DefsRemote";

    public DefsPropertySourceLocator(DefsConfigProperties defsConfigProperties) {
        super();
        this.defsConfigProperties = defsConfigProperties;
    }

    @Override
    public PropertySource<?> locate(Environment env) {
        if (env == null) {
            log.warn("DefsPropertySourceLocator not working, env is null");
            return null;
        }
        if (defsConfigProperties == null || defsConfigProperties. getUrl() == null) {
            log.warn("DefsPropertySourceLocator not working, defsConfigProperties is null");
            return null;
        }
        String url = defsConfigProperties. getUrl();
        String httpStr = HttpUtil. send(url, HttpMethod. GET, null, null);
        BaseResponse<List<Map<String, Object>>> httpObj =
            JacksonUtil.readValue(httpStr, new TypeReference<BaseResponse<List<Map<String, Object>>>>() {});
        List<Map<String, Object>> httpData = httpObj != null ? httpObj.getData() : null;
        if (CollectionUtils. isEmpty(httpData)) {
            log.warn("DefsPropertySourceLocator not work, not load from " + url);
            return null;
        }
        Map<String, Object> source = new HashMap<>();
        for (Map<String, Object> item : httpObj. getData()) {
            source.put(MapUtils.getString(item, "queueName"), MapUtils.getString(item, "queueDesc"));
        }

        CompositePropertySource composite = new CompositePropertySource(PROPERTY_SOURCE_NAME);
        composite.addFirstPropertySource(new MapPropertySource(url, source));
        return composite;
    }

}

3. Implement org.springframework.cloud.bootstrap.BootstrapConfiguration to implement Bean initialization

Similar to Nacos (com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration)

/**
 *
 * @author yonghua.cao
 * @date 2023/05/21
 */
@Configuration
public class DefsConfigBootstrapConfiguration {

    public DefsConfigBootstrapConfiguration() {
        super();
    }

    @Bean
    @ConditionalOnMissingBean
    public DefsConfigProperties defsConfigProperties() {
        return new DefsConfigProperties();
    }

    @Bean
    public DefsPropertySourceLocator defsPropertySourceLocator(DefsConfigProperties defsConfigProperties) {
        return new DefsPropertySourceLocator(defsConfigProperties);
    }

}

4. Configure META-INF/spring.factories to implement springboot startup loading

 Create /src/main/resources/META-INF/spring.factories

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.app.cloud.defs.DefsConfigBootstrapConfiguration

5. Configure bootstrap.properties

 New configuration in the configuration file

spring.cloud.defs.config.url=http://www.node.com/config/api/list

6. Whether the startup verification is loaded successfully, it can cooperate with the online refresh to realize dynamic refresh verification