《框架封装者 · 自定义初始化事件》

发布于:2024-07-06 ⋅ 阅读:(41) ⋅ 点赞:(0)

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍


写在前面的话

上篇博文介绍了《监听器 Listener》,主要介绍了 Spring 监听器的用法,用的最多的还是初始化监听器,可以程序启动之前执行一些诸如,数据加载到缓存等动作。
显然,这个用法很简单,关于 ContextRefreshedEvent 的初始化监听,作为具体某个模块的开发人员完全可以写一个 Spring 监听器类,完成自己想要的初始化动作,So easy~
但该用法存在一些局限性:
首先,该操作是同步的,若初始化执行的程序逻辑耗时较多,会影响整个服务的启动时长,进而引发一系列问题,例如KS8误判启动失败等;
其次,该操作中,只要业务逻辑存在未把控到位的情况,意外抛出了异常,那么将直接导致程序启动失败,这可能是违背初衷的;
总之,这两点因素带来的影响都很大,那么作为一个框架搭建人员,如何应对这些现象,如何给开发人员更灵活的编码体验,这个是需要我们思考的。


构思阶段

情况大致了解了,开始构思如何处理这个需求呢?
上面提到的两个问题,肯定有人要问了,业务开发人员直接可以通过编码的方式避免。例如添加异步多线程操作,或者将异常catch住,有什么需要框架封装人员好考虑的?
其实不然,并不是所有开发人员都有良好的编程习惯,可以对自己的代码负责。框架封装人员就是要为大部分的开发人员做兜底操作,替他们考虑好可能的问题,让他们更明确自己的代码走向。
那说了怎么多,怎么解决呢?答案就是包装一层,对异步和异常进行可配置,具体往下看实现。


实现阶段

1、框架层面,定义一个初始化类,ApplicationInitializerInvoker,其也是实现 ApplicationListener 接口;
2、定义一个操作接口 ApplicationInitializer,包含是否异步、是否抛出异常等属性;
3、定义一个抽象类 AbstractApplicationInitializer,实现 ApplicationInitializer 接口的若干方法;
4、在第一步的ApplicationInitializerInvoker#onApplicationEvent方法中,获取项目的所有ApplicationInitializer接口的Bean,包含框架内置的和用户自定义的,执行初始化逻辑;
5、至此,流程结束,详见下方代码示例(部分)。

public class ApplicationInitializerInvoker 
    implements ApplicationListener<ApplicationStartedEvent> {

    private final List<ApplicationInitializer> initializers;
    
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        // 是否已经处理过(多个容器加载的情况下事件会被多次刷新)
        if (this.processed.getAndSet(true)) {
            return;
        }
        if (initializers == null || initializers.isEmpty()) {
            return;
        }
        initializers.sort(Comparator.comparingInt(ApplicationInitializer::getOrderIndex));

        log.info("[应用初始化事件] 共找到{}个初始化事件,开始初始化...", initializers.size());
        long start = System.currentTimeMillis();
        OnelinkInitResult onelinkInitResult = new OnelinkInitResult();
        for (ApplicationInitializer initializer : initializers) {
            // 异步初始化
            if (initializer.isAsync()) {
                    this.initWithAsync(initializer, event, onelinkInitResult);
            } else {
                this.init(initializer, event, onelinkInitResult);
            }
        }
        long end = System.currentTimeMillis();
        log.info("[应用初始化事件] 初始化完成,总耗时:{}ms", end - start);
    }

    /**
     * 同步方式初始化
     */
    private void init(ApplicationInitializer initializer, ApplicationStartedEvent event, OnelinkInitResult initResult) throws Exception {
        if (!initializer.preInit(event)) {
            return;
        }
        try {
            initializer.init(event);
            this.finish(initializer, initResult);
        } catch (Exception e) {
            initResult.addFailureEx(e);
            if (initializer.throwable()) {
                throw e;
            } else {
                log.error("应用程序初始化失败:[{}]}", initializer.getClass().getName(), e);
            }
        }
    }

    /**
     * 异步方式初始化
     */
    private void initWithAsync(ApplicationInitializer initializer, ApplicationStartedEvent event, OnelinkInitResult initResult) {
        ThreadUtil.execute(() -> {
            try {
                this.init(initializer, event, initResult);
                this.finish(initializer, initResult);
            } catch (Exception e) {
                initResult.addFailureEx(e);
                log.error("应用程序初始化失败:[{}] - 原因:{}", initializer.getClass().getName(), e.getMessage(), e);
            }
        });
    }
}

public interface ApplicationInitializer {


    /**
     * 是否异步
     */
    default boolean isAsync() {
        return false;
    }

    /**
     * 要执行初始化之前的判断逻辑
     *
     * @param event 容器事件
     * @return , true为执行 , false 不执行
     */
    default boolean preInit(ApplicationStartedEvent event) {
        return true;
    }

    /**
     * 执行顺序(值越小优先级越高)
     *
     * @return 默认优先级为0
     */
    default int getOrderIndex() {
        return 0;
    }

    /**
     * 事件名,用于描述当前初始化的动作,可以为空
     */
    default String eventName() {
        return null;
    }

    /**
     * 是否可以抛出异常
     *
     * @return true = 初始化发生异常会中断程序的启动 , false = 初始化发生异常不会中断程序的启动
     */
    default boolean throwable() {
        return true;
    }

    /**
     * 初始化逻辑
     *
     * @param event 容器事件
     * @throws Exception 初始化时发生的异常
     */
    void init(ApplicationStartedEvent event) throws Exception;

}

public abstract class AbstractApplicationInitializer implements ApplicationInitializer {

    @Override
    public void init(ApplicationStartedEvent event) throws Exception {
        long start = System.currentTimeMillis();
        String eventName = this.eventName() == null ? this.getClass().getName() : this.eventName();
        String asyncType = this.isAsync() ? "异步方式" : "同步方式";
        if (preInit(event)) {
            this.doInit(event);
        }
        long end = System.currentTimeMillis();
        log.info("[应用初始化事件] [{}] [{}] 初始化完成,耗时:{}ms", asyncType, eventName, end - start);
    }

    /**
     * 执行初始化
     *
     * @param event 容器事件
     * @throws Exception .
     */
    protected abstract void doInit(ApplicationStartedEvent event) throws Exception;
}

应用阶段

开发人员怎么使用呢?
如下所示,只需要继承框架封装的抽象类AbstractApplicationInitializer即可,实现doInit方法即可。
可以按需配置是否异步、是否可以抛出异常等等。

@Slf4j
@Component
public class Portalnitializer extends AbstractApplicationInitializer {

    @Override
    protected void doInit(ApplicationStartedEvent event) {
        log.info("自定义初始化事件");
    }

    /**
     * 是否异步
     */
    @Override
    public boolean isAsync() {
        return false;
    }

    /**
     * 是否可以抛出异常
     * @return true = 初始化发生异常会中断程序的启动 , false = 初始化发生异常不会中断程序的启动
     */
    @Override
    public boolean throwable() {
        return false;
    }
}

总结陈词

上文介绍了框架封装人员,如何提供一个相对灵活的初始化自定义事件,让业务开发人员可以用的更顺手。
本系列博文也以此示例开篇,介绍框架搭建人员如何以恰当的方式应对各式各样的情况,这也是此专栏的主题。
💗 后续将持续更新,请多多支持!


网站公告

今日签到

点亮在社区的每一天
去签到