当前位置: 首页 > article >正文

SpringBoot自定义实现触发器模型的starter

文章目录

    • 前言
    • 正文
      • 一、项目环境
      • 二、核心类的类图关系
        • 2.1 触发器核心接口&类
        • 2.2 触发器工厂&注册&管理使用
      • 三、项目代码
        • 3.1 pom.xml
        • 3.2 org.springframework.boot.autoconfigure.AutoConfiguration.imports
        • 3.3 ITriggerActionEnum
        • 3.4 Trigger
        • 3.5 TriggerContext
        • 3.6 AbstractTriggerTemplate
        • 3.7 TriggerException
        • 3.8 TriggerFactory
        • 3.9 ApplicationBeanProvider
        • 3.10 TriggerApplicationRunner
        • 3.11 TriggerAutoConfiguration
        • 3.12 LockTemplateFactory
        • 3.13 TriggerActionManage
      • 四、测试调用
        • 4.1 引入坐标依赖&配置redis
        • 4.2 新增触发器操作枚举
        • 4.3 新增触发器
        • 4.4 调用触发器
      • 五、总结

前言

前不久,写了个管理系统的后端,其中涉及到一个“触发器模型”的部分,可以对业务进行解耦,复用。具体内容见下边这个链接:

https://blog.csdn.net/FBB360JAVA/article/details/143182736

本文的重点是,对它的一些优化,和更加规范化的调整。

正文

一、项目环境

  • Java 23
  • 整体编码使用 UTF-8
  • SpringBoot 3.3.1
  • redission 3.30.0
  • lock4j-redission 整合包 2.2.7
  • lombok 1.18.32

项目结构如下:
在这里插入图片描述

二、核心类的类图关系

2.1 触发器核心接口&类

在这里插入图片描述

2.2 触发器工厂&注册&管理使用

在这里插入图片描述

三、项目代码

3.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.pine.trigger</groupId>
    <artifactId>pine-trigger-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>pine-pine-starter</name>
    <description>pine-pine-starter</description>

    <properties>
        <java.version>23</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>3.3.1</spring-boot.version>

        <redission.version>3.30.0</redission.version>
        <lock4j-redisson.version>2.2.7</lock4j-redisson.version>
        <lombok.version>1.18.32</lombok.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>${redission.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
            <version>${lock4j-redisson.version}</version>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>23</source>
                    <target>23</target>
                    <encoding>UTF-8</encoding>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.2 org.springframework.boot.autoconfigure.AutoConfiguration.imports

这个地方是用于自动装配,也就是自定义starter。

com.pine.trigger.config.TriggerAutoConfiguration
3.3 ITriggerActionEnum
package com.pine.trigger.constant;

/**
 * 触发器操作接口-其实现类应该设计为枚举
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 11:39
 */
public interface ITriggerActionEnum {

    /**
     * 获取触发器操作代码
     *
     * @return 操作代码
     */
    String getCode();

    /**
     * 获取触发器操作描述
     *
     * @return 操作描述
     */
    String getDesc();
}

3.4 Trigger
package com.pine.trigger.core;

import com.pine.trigger.constant.ITriggerActionEnum;

/**
 * 触发器-顶级接口
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 13:59
 */
public interface Trigger<T, R> {

    /**
     * 触发
     *
     * @param context 上下文
     * @return 返回结果
     */
    R trigger(TriggerContext<T> context);

    ITriggerActionEnum getAction();

    default void init(TriggerContext<T> context) {
    }

    default R after(TriggerContext<T> context, R result) {
        return result;
    }
}

3.5 TriggerContext

这里定义为一个记录类(record)。你自己实现的时候,也可以换成一个简单的实体类。

package com.pine.trigger.core;

import com.pine.trigger.constant.ITriggerActionEnum;

/**
 * 触发器上下文
 *
 * @param action 触发器action
 * @param <T> 触发器数据
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 14:02
 */
public record TriggerContext<T>(T data, ITriggerActionEnum action) {
}

3.6 AbstractTriggerTemplate

采用模版方法设计模式实现业务流程的组装,分为初始化(init)、执行(trigger),执行后(after)。另外这里处理的时候,选择先从spring容器中获取对应的触发器对象,然后调用的方式,也是为了后续更方便在触发器方法上增加切面、事务等操作。

package com.pine.trigger.core;

import com.pine.trigger.config.ApplicationBeanProvider;
import com.pine.trigger.constant.ITriggerActionEnum;

/**
 * 抽象触发器模板
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 14:07
 */
public abstract class AbstractTriggerTemplate<T, R> implements Trigger<T, R> {

    private final ITriggerActionEnum action;

    public AbstractTriggerTemplate(ITriggerActionEnum action) {
        this.action = action;
    }

    @SuppressWarnings("unchecked")
    public final R start(TriggerContext<T> context) {
        // 从容器中获取触发器实例
        AbstractTriggerTemplate<T, R> triggerTemplate = ApplicationBeanProvider.getBean(this.getClass());

        // 初始化
        triggerTemplate.init(context);

        // 执行触发器主体
        R result = triggerTemplate.trigger(context);

        // 触发后置操作
        triggerTemplate.after(context, result);
        return result;
    }


    @Override
    public ITriggerActionEnum getAction() {
        return action;
    }
}

3.7 TriggerException

自定义异常类。

package com.pine.trigger.core;

import java.util.Objects;

/**
 * 触发器异常
 *
 * @author 01434188
 * @version 1.0
 * @since 2025-01-22 14:56
 */
public class TriggerException extends RuntimeException {

    public TriggerException(String message) {
        super(message);
    }

    public static TriggerException error(String message) {
        return new TriggerException(message);
    }

    public static void requiredNonNull(Object param, String message) {
        if (Objects.isNull(param)) {
            throw new TriggerException(message);
        }
    }
}

3.8 TriggerFactory

这个类是触发器工厂,设计为一个单例。提供的工厂方法,也使用了非静态的方式定义。主要提供注册触发器、获取触发器的方法。

package com.pine.trigger.core;

import com.pine.trigger.constant.ITriggerActionEnum;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
 * 触发器工厂(单例工厂)
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 14:21
 */
@Slf4j
public class TriggerFactory {

    /**
     * 饿汉式单例:触发器工厂实例
     */
    @SuppressWarnings("all")
    private static final TriggerFactory TRIGGER_FACTORY = new TriggerFactory();

    /**
     * 触发器池
     */
    private static final Map<ITriggerActionEnum, AbstractTriggerTemplate<?, ?>> TRIGGER_POOL_MAP = new HashMap<>(16);


    /**
     * 注册触发器
     *
     * @param trigger 触发器
     */
    @SuppressWarnings("all")
    public void register(AbstractTriggerTemplate trigger) {
        TriggerException.requiredNonNull(trigger.getAction(), "触发器action不能为空!");

        // 尝试注册触发器
        if (TRIGGER_POOL_MAP.putIfAbsent(trigger.getAction(), trigger) != null) {
            log.warn("触发器[" + trigger.getAction() + "]已经存在,请勿重复注册!");
        }
    }


    /**
     * 获取触发器
     *
     * @param action 触发器action
     * @param <T>    触发器入参类型
     * @param <R>    触发器返回类型
     * @return 触发器
     */
    @SuppressWarnings("all")
    public AbstractTriggerTemplate getTrigger(ITriggerActionEnum action) {
        TriggerException.requiredNonNull(action, "触发器action不能为空!");
        AbstractTriggerTemplate<?, ?> triggerTemplate = TRIGGER_POOL_MAP.get(action);
        TriggerException.requiredNonNull(triggerTemplate,"触发器[" + action + "]不存在,请先注册!");
        return triggerTemplate;
    }


    /**
     * 获取触发器工厂实例
     *
     * @return 触发器工厂实例
     */
    public static TriggerFactory getInstance() {
        return TRIGGER_FACTORY;
    }

    private TriggerFactory() {
    }
}

3.9 ApplicationBeanProvider

相当于一个spring bean 的工具类,主要是实现了ApplicationContextAware 接口,并提供获取bean的方法。

package com.pine.trigger.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;

import java.util.ArrayList;
import java.util.List;

/**
 * 应用bean提供类
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 14:12
 */
@Configuration
public class ApplicationBeanProvider implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

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

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    public static <T> List<T> getBeans(Class<T> clazz) {
        return new ArrayList<>(applicationContext.getBeansOfType(clazz).values());
    }
}

3.10 TriggerApplicationRunner

用于程序启动后,注册全部的触发器。

package com.pine.trigger.config;

import com.pine.trigger.core.AbstractTriggerTemplate;
import com.pine.trigger.core.TriggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * 触发器的应用启动后置操作执行器
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 14:35
 */
@Configuration
public class TriggerApplicationRunner implements ApplicationRunner {
    @SuppressWarnings("all")
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 从容器中获取所有的触发器,并注册到触发器工厂中
        List<AbstractTriggerTemplate> triggers = ApplicationBeanProvider.getBeans(AbstractTriggerTemplate.class);
        triggers.forEach(trigger -> TriggerFactory.getInstance().register(trigger));
    }
}

3.11 TriggerAutoConfiguration

用于自动装配这个自定义的starter,并指定需要扫描的包路径。

package com.pine.trigger.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 触发器自动配置类
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 11:37
 */
@ComponentScan(basePackages = "com.pine.trigger")
@Configuration
public class TriggerAutoConfiguration {
}

3.12 LockTemplateFactory

在整合 Lock4j 和 redission 之后,获取 LockTemplate 实例。


package com.pine.trigger.manage;

import com.baomidou.lock.LockTemplate;
import com.pine.trigger.config.ApplicationBeanProvider;
import com.pine.trigger.core.TriggerException;

import java.util.Objects;

/**
 * LockTemplate工厂
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 15:17
 */
public class LockTemplateFactory {

    private static LockTemplate lockTemplate;

    public static LockTemplate lockTemplate() {
        if (Objects.nonNull(LockTemplateFactory.lockTemplate)) {
            return LockTemplateFactory.lockTemplate;
        }

        LockTemplateFactory.lockTemplate = ApplicationBeanProvider.getBean(LockTemplate.class);
        TriggerException.requiredNonNull(lockTemplate, "lockTemplate 不能为空");
        return LockTemplateFactory.lockTemplate;
    }
}

3.13 TriggerActionManage

触发器管理类,最终在使用触发的时候,也是要通过调用它的方法。
这里提供了幂等和非幂等两种实现方式。

package com.pine.trigger.manage;

import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.pine.trigger.core.AbstractTriggerTemplate;
import com.pine.trigger.core.TriggerContext;
import com.pine.trigger.core.TriggerException;
import com.pine.trigger.core.TriggerFactory;
import lombok.SneakyThrows;

import java.util.Objects;

/**
 * 触发器操作管理类
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 14:49
 */
public class TriggerActionManage {


    /**
     * 触发器操作处理(无幂等)
     *
     * @param context 上下文信息
     * @param <T>     触发入参类型
     * @param <R>     触发响应类型
     * @return 处理响应结果
     */
    @SuppressWarnings("all")
    public static <T, R> R handle(TriggerContext<T> context) {
        // 从工厂中获取触发器
        AbstractTriggerTemplate trigger = TriggerFactory.getInstance().getTrigger(context.action());

        // 启动触发器执行
        return (R) trigger.start(context);
    }

    /**
     * 触发器操作处理(幂等,采取redis加锁3s来限制)<br>
     * <b>要求:需要对于入参进行 toString的重写</b>
     *
     * @param context 上下文信息
     * @param <T>     触发入参类型
     * @param <R>     触发响应类型
     * @return 处理响应结果
     */
    @SuppressWarnings("all")
    public static <T, R> R handleWithIdempotent(TriggerContext<T> context) {
        String key = context.toString();
        // 尝试对key加锁,失效时间为3s
        LockTemplate lockTemplate = LockTemplateFactory.lockTemplate();
        LockInfo locked = lockTemplate.lock(key, 3000L, 0L);
        // 如果加锁成功
        if (Objects.nonNull(locked)) {
            return handle(context);
        }

        throw new TriggerException("触发器执行失败");
    }
}

四、测试调用

4.1 引入坐标依赖&配置redis

另外新增一个普通的springboot 项目,在其中引入上边实现的starter。
比如,在 新项目的pom.xml中增加:

        <dependency>
            <groupId>com.pine.trigger</groupId>
            <artifactId>pine-trigger-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

在你的配置中增加redis的配置信息,比如在 application.yaml中配置:

spring:
  data:
    redis:
      database: 1
      host: 127.0.0.1
      port: 6379
      password: 2131313da


4.2 新增触发器操作枚举
package com.example.bootdemo17.trigger;

import com.pine.trigger.constant.ITriggerActionEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 触发器action
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 15:58
 */
@Getter
@AllArgsConstructor
public enum MyTriggerActionEnum implements ITriggerActionEnum {

    DEMO("demo", "demo");


    private final String code;
    private final String desc;
}

4.3 新增触发器

注意:这里需要重写无参数构造器,并注入一个枚举类型,一个枚举对应一个触发器。

package com.example.bootdemo17.trigger;

import com.pine.trigger.core.AbstractTriggerTemplate;
import com.pine.trigger.core.TriggerContext;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * dd
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 16:00
 */
@Component
public class MyTrigger extends AbstractTriggerTemplate<List<Long>, String> {

    public MyTrigger() {
        super(MyTriggerActionEnum.DEMO);
    }


    /**
     * 触发
     *
     * @param context 上下文
     * @return 返回结果
     */
    @Override
    public String trigger(TriggerContext<List<Long>> context) {
        return "d3weqedasd";
    }
}

4.4 调用触发器
package com.example.bootdemo17;

import com.example.bootdemo17.trigger.MyTriggerActionEnum;
import com.pine.trigger.core.TriggerContext;
import com.pine.trigger.manage.TriggerActionManage;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * dd
 *
 * @author pine
 * @version 1.0
 * @since 2025-01-22 16:09
 */
@RestController
@RequestMapping("/test")
public class DemoController {

    @GetMapping("/demo")
    public String demo() {
        // 组装上下文
        TriggerContext<List<Long>> context = new TriggerContext<>(List.of(1L, 2L, 3L, 4L),MyTriggerActionEnum.DEMO);

        // 调用触发器
        String result = TriggerActionManage.handle(context);
        return result;
    }

}

五、总结

触发器模型与spring框架中的事件触发机制的作用类似,但是更加强大。

触发器模型可以有返回值。并用于组装你自己的业务链。
更适合实现复杂的业务场景。
不太适用在查询数据的场景,更多的是一些操作,比如修改,新增等。


http://www.kler.cn/a/517072.html

相关文章:

  • pandas基础:基本数据结构
  • 把网站程序数据上传到服务器的方法和注意事项
  • Linux cron 定时任务查看执行情况
  • 微服务学习-Nacos 注册中心实战
  • C#集合操作优化:高效实现批量添加与删除
  • sql主从同步
  • 【期末速成】软件设计模式与体系结构
  • 把网站程序数据上传到服务器的方法和注意事项
  • 针对业务系统的开发,如何做需求分析和设计?
  • 【数据结构】_基于顺序表实现通讯录
  • 在Docker 容器中安装 Oracle 19c
  • 编译Android平台使用的FFmpeg库
  • 【玩转全栈】----YOLO8训练自己的模型并应用
  • 6. 马科维茨资产组合模型+政策意图AI金融智能体(DeepSeek-V3)增强方案(理论+Python实战)
  • (详细)Springboot 整合动态多数据源 这里有mysql(分为master 和 slave) 和oracle,根据不同路径适配不同数据源
  • Redis线上阻塞要如何排查
  • Java面向对象专题
  • 【leetcode100】二叉搜索树中第k小的元素
  • python远程获取数据库中的相关数据并存储至json文件
  • MySQL中的关联查询:方式、区别及示例
  • Python 爬虫——爬取Web页面图片
  • 03垃圾回收篇(D3_垃圾收集器的选择及相关参数)
  • 2K高刷电竞显示器怎么选?
  • 记忆层增强的 Transformer 架构:通过可训练键值存储提升 LLM 性能的创新方法
  • Django 静态文件配置实战指南
  • <keep-alive> <component ></component> </keep-alive>缓存的组件实现组件,实现组件切换时每次都执行指定方法