loveqq-bootstrap 和 springcloud-bootstrap 有什么区别

前言

最开始为 loveqq 框架添加 nacos 配置中心启动器的时候,一个问题一直困扰着我。

那就是框架的启动需要配置文件,而配置文件在配置中心。

当应用启动后再取读取配置文件也是可以的,但问题是有些条件 bean,需要依赖配置项才能决定是否应该实例化。 所以当应用启动完成后再拉取配置文件,虽然配置读到了,但是 bean 的依赖关系却不是我所需要的。

于是,我就陷入了一个死循环:要启动就需要读取配置,而要配读取置就需要先启动!到底如何才能提前读取到配置文件呢?

最开始,我想搞一个 nacos 的 BeanPostProcessor,当监控到 PropertyContext 实例化的时候直接去拉取配置。 但是这样是不行的,因为 BeanPostProcessor 注册的时候,所有的 bean 定义已经加载完毕,条件注解已经判断,此时再读取配置已经晚了。

此时,我突然想到了 spring 的 @FeignClient 客户端的一些初始化源码逻辑,依稀记得每次创建 Feign 客户端,spring 都要创建一个 SpringApplication run 一下。

于是,我就想到了是否可以额外启动一个应用,仅用于读取配置文件,读取完毕后,再走主流程?

于是,一个构思慢慢的出现了,我创建了一个新模块:loveqq-boot-cloud-bootstrap,并新增了一个 @BootstrapConfiguration 注解。 该模块专门用于加载 @BootstrapConfiguration 注解的配置,加载后将 @BootstrapConfiguration 注解的 bean 注册到主流程的 BeanFactory。

引导上下文

引导上下文(BootstrapApplicationContext),继承了 DefaultConfigurableApplicationContext,目的是防止重入,导致重复引导。

Bootstrap 接口

专门抽象出了 Bootstrap 接口,方便第三方实现自己的引导逻辑,具体实现如下:

java 复制代码
package com.kfyty.loveqq.framework.cloud.bootstrap;

/**
 * 描述: {@link BeanFactory} 引导
 * 加载 {@link BootstrapConfiguration} 标记的配置类,并复制到给定的应用上下文
 *
 * @author kfyty725
 * @date 2023/9/10 22:00
 * @email kfyty725@hotmail.com
 */
@Slf4j
public class BeanFactoryBootstrap implements Bootstrap {
    /**
     * 空 bean
     */
    private static final Object EMPTY_BEAN = new Object();

    @Override
    public void bootstrap(ConfigurableApplicationContext applicationContext) throws Exception {
        log.info("Bootstrap starting...");
        try (ApplicationContext bootstrapContext = new BootstrapApplicationContextFactory().create(applicationContext.getCommandLineArgs(), BeanFactoryBootstrapApplication.class)) {
            bootstrapContext.refresh();
            this.mergeBootstrapConfiguration(bootstrapContext, applicationContext);
        }
        log.info("Bootstrap succeed completed.");
    }

    /**
     * 合并 {@link BootstrapConfiguration} 标注的 bean
     * 将引导上下文中的 {@link BootstrapConfiguration} 标注的 bean 合并到主应用上下文
     *
     * @param bootstrapContext   引导 BeanFactory
     * @param applicationContext 主应用 BeanFactory
     */
    protected void mergeBootstrapConfiguration(ApplicationContext bootstrapContext, ConfigurableApplicationContext applicationContext) {
        for (Map.Entry<String, BeanDefinition> entry : bootstrapContext.getBeanDefinitionWithAnnotation(BootstrapConfiguration.class).entrySet()) {
            this.registryBootstrapBean(entry.getKey(), entry.getValue(), bootstrapContext, applicationContext);
        }
    }

    /**
     * 注册引导配置类
     * 被注册的引导配置类,在引导上下文中将被替换为 {@link this#EMPTY_BEAN},避免销毁引导上下文时被销毁
     *
     * @param beanName           引导配置 bean
     * @param beanDefinition     引导配置 bean definition
     * @param bootstrapContext   引导上下文
     * @param applicationContext 应用上下文
     */
    protected void registryBootstrapBean(String beanName, BeanDefinition beanDefinition, ApplicationContext bootstrapContext, ConfigurableApplicationContext applicationContext) {
        // 注册到应用上下文
        Object bean = bootstrapContext.getBean(beanName);
        applicationContext.registerBeanDefinition(beanName, beanDefinition);
        applicationContext.replaceBean(beanName, bean);
        if (bean instanceof BeanPostProcessor) {
            applicationContext.registerBeanPostProcessors(beanName, (BeanPostProcessor) bean);
        }

        // 替换引导上下文的中 bean,避免被销毁
        bootstrapContext.replaceBean(beanName, EMPTY_BEAN);

        // 使用应用上下文重新回调 aware 接口
        this.invokeAware(beanName, bean, applicationContext);

        // 注册嵌套的引导配置 bean
        for (Method method : ReflectUtil.getMethods(beanDefinition.getBeanType())) {
            Bean beanAnnotation = AnnotationUtil.findAnnotation(method, Bean.class);
            if (beanAnnotation != null) {
                String nestedBeanName = BeanUtil.getBeanName(method, beanAnnotation);
                if (bootstrapContext.containsBeanDefinition(nestedBeanName)) {
                    this.registryBootstrapBean(nestedBeanName, bootstrapContext.getBeanDefinition(nestedBeanName), bootstrapContext, applicationContext);
                }
            }
        }
    }

    /**
     * 执行相关能力接口,注入新的上下文
     *
     * @param bean               引导 bean
     * @param applicationContext 应用上下文
     */
    protected void invokeAware(String beanName, Object bean, ConfigurableApplicationContext applicationContext) {
        if (bean instanceof BeanFactoryAware aware) {
            aware.setBeanFactory(applicationContext);
        }

        if (bean instanceof ApplicationContextAware aware) {
            aware.setApplicationContext(applicationContext);
        }

        if (bean instanceof ConfigurableApplicationContextAware aware) {
            aware.setConfigurableApplicationContext(applicationContext);
        }

        // 主应用还未启动,配置上下文还不存在,注册一个回调 bean 定义
        if (bean instanceof PropertyContextAware) {
            BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(PropertyContextAwareCallback.class)
                    .setBeanName(beanName.concat("_PropertyContextAwareCallback"))
                    .addConstructorArgs(PropertyContextAware.class, bean)
                    .getBeanDefinition();
            applicationContext.registerBeanDefinition(beanDefinition.getBeanName(), beanDefinition, false);
        }
    }

    @RequiredArgsConstructor
    static class PropertyContextAwareCallback implements PropertyContextAware, Ordered {
        /**
         * bean
         */
        private final PropertyContextAware bean;

        @Override
        public void setPropertyContext(GenericPropertiesContext propertiesContext) {
            this.bean.setPropertyContext(propertiesContext);
        }

        @Override
        public int getOrder() {
            return Integer.MIN_VALUE;
        }
    }
}

这样一来,就实现了通用的引导逻辑。

nacos 配置中心引导

于是 nacos 的配置中心启动器就有了解决方案 创建一个 NacosPropertyLoaderBeanPostProcessor 处理器,并将其标记为 BootstrapConfiguration。 这样在引导阶段,该 BeanPostProcessor 就会被注册到主应用里,将 PropertyContext 实例化的时候,就可以第一时间感知到,并拉取配置。

springcloud 实现

今天我突然想到 springcloud 应该也会遇到我这个问题,那么 springcloud 是如何实现的呢? 一同搜索,大家可以看这个:zhuanlan.zhihu.com/p/456499577

loveqq 在没有参考,并且不知道 springcloud 如何实现的情况下,和 springcloud 的实现可以说是不谋而合。

不过区别还是有的: springcloud 是基于自己的事件监听机制引导,loveqq 则是专门的引导接口。

springcloud 专门新增了 bootstrap.yaml 配置文件,loveqq 则不需要。

springcloud 做了不少默认配置及判断操作,loveqq 则不需要,因为 loveqq 在设计之初,就具有一定的自我配置,自我启动能力。比如 ApplicationContext 的创建也依赖自身的依赖注入功能。

这次就到这里了,感兴趣的可以 gitee/gitcode/github 看看。

相关推荐
RoyLin1 小时前
TypeScript设计模式:桥接模式
前端·后端·typescript
CryptoRzz2 小时前
印度尼西亚股票数据API对接实现
javascript·后端
brzhang2 小时前
干翻 Docker?WebAssembly 3.0 的野心,远不止浏览器,来一起看看吧
前端·后端·架构
追逐时光者2 小时前
一个基于 .NET 开源、简易、轻量级的进销存管理系统
后端·.net
bobz9652 小时前
OAI Triton 是 OpenAI 开发的一种类似 Python 的开源编程语言
后端
IT_陈寒3 小时前
React 18实战:7个被低估的Hooks技巧让你的开发效率提升50%
前端·人工智能·后端
星星电灯猴3 小时前
Thor 抓包工具详解 iOS 抓包方法、HTTPS 抓包难点与常见网络调试工具对比
后端
姓王者3 小时前
可能解决Tauri多窗口应用阻塞问题
后端
RoyLin3 小时前
TypeScript设计模式:抽象工厂模式
前端·后端·typescript