Transactional钩子函数实现 [ 自定义注解 + 多线程 ]

217 阅读5分钟

Transactional钩子函数实现 [ 自定义注解 + 多线程 ]:

  • 适用对象:在有事务的代码块中,想在事务提交之后执行操作的业务环境中,一般用户redis缓存,发送mq消息等

  • 优点:傻瓜式操作,注解式操作,两种形式集成

  • 缺点:剥离主线程,执行方法无事务

代码结构:

  • annotation
    • AfterCommit [事务提交后置执行注解]
    • BeforeCommit [事务提交前置执行注解]
  • aspect
    • TransactionalAop [事务提交AOP]
  • thread
    • ThreadUtil [线程池配置类]
    • TransactionalExecutor [事务执行器]

AfterCommit:

import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

/**
 * 事务提交后置执行
 *
 * @author a-xin
 * @date 2019/3/12 23:50
 */
@Order(2)
@Inherited
@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AfterCommit {

    /**
     * 事务提交后执行方法名称
     */
    String method();

}

BeforeCommit:

import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

/**
 * 事务提交前置执行
 *
 * @author a-xin
 * @date 2019/3/12 23:50
 */
@Order(1)
@Inherited
@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface BeforeCommit {

    /**
     * 事务提交前执行方法名称
     */
    String method();

}

TransactionalAop:

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.axin229913.thread.annotation.AfterCommit;
import com.axin229913.thread.annotation.BeforeCommit;
import com.axin229913.thread.util.TransactionalExecutor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
@Aspect
@EnableAspectJAutoProxy
public class TransactionalAop {

    @Resource
    private TransactionalExecutor transactionalExecutor;

    /**
     * 当前线程子线程
     */
    private static final ThreadLocal<Map<String, Boolean>> THREAD_LOCAL = new ThreadLocal<>();

    private static final Map<String, Boolean> COMMIT_MAP = new HashMap<>();

    @PostConstruct
    private void init() {
        COMMIT_MAP.put(TransactionalExecutor.BEFORE_COMMIT, Boolean.TRUE);
        COMMIT_MAP.put(TransactionalExecutor.AFTER_COMMIT, Boolean.TRUE);
    }


    @Pointcut("@annotation(com.axin229913.thread.annotation.AfterCommit)")
    public void pointCutAfter() {
    }

    @Pointcut("@annotation(com.axin229913.thread.annotation.BeforeCommit)")
    public void pointCutBefore() {
    }


    @Before(value = "pointCutAfter()")
    public void afterPointCut() {
        if (CollUtil.isEmpty(THREAD_LOCAL.get())) {
            THREAD_LOCAL.set(COMMIT_MAP);
        }
    }

    @AfterThrowing(value = "pointCutAfter()")
    public void afterThrowing1() {
        THREAD_LOCAL.get().put(TransactionalExecutor.AFTER_COMMIT, Boolean.FALSE);
    }


    @Before(value = "pointCutBefore()")
    public void beforePointCut() {
        if (CollUtil.isEmpty(THREAD_LOCAL.get())) {
            THREAD_LOCAL.set(COMMIT_MAP);
        }
    }

    @AfterThrowing(value = "pointCutBefore()")
    public void afterThrowing2() {
        THREAD_LOCAL.get().put(TransactionalExecutor.BEFORE_COMMIT, Boolean.FALSE);
    }


    @After("pointCutAfter()")
    public void afterPointCut(JoinPoint joinPoint) {
        if (THREAD_LOCAL.get().get(TransactionalExecutor.AFTER_COMMIT)) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            if (!method.isAnnotationPresent(AfterCommit.class)) {
                return;
            }
            AfterCommit afterCommit = method.getAnnotation(AfterCommit.class);
            String methodName = afterCommit.method();
            if (CharSequenceUtil.isBlank(methodName)) {
                return;
            }

            Class<?> declaringClass = method.getDeclaringClass();

            transactionalExecutor.afterRun(() -> {
                try {
                    Method declaredMethod = declaringClass.getDeclaredMethod(methodName);
                    declaredMethod.invoke(joinPoint.getTarget());
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            });

        }
    }

    @After("pointCutBefore()")
    public void beforePointCut(JoinPoint joinPoint) {
        if (THREAD_LOCAL.get().get(TransactionalExecutor.BEFORE_COMMIT)) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            if (!method.isAnnotationPresent(BeforeCommit.class)) {
                return;
            }
            BeforeCommit beforeCommit = method.getAnnotation(BeforeCommit.class);
            String methodName = beforeCommit.method();
            if (CharSequenceUtil.isBlank(methodName)) {
                return;
            }

            Class<?> declaringClass = method.getDeclaringClass();

            transactionalExecutor.beforeRun(() -> {
                try {
                    Method declaredMethod = declaringClass.getDeclaredMethod(methodName);
                    declaredMethod.invoke(joinPoint.getTarget());
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            });

        }
    }

}

ThreadUtil:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 类名:动态线程池工具类,按照服务器资源cpu核数定义
 *
 * @author a-xin
 * @date 15:17
 */
@Slf4j
@Configuration
public class ThreadUtil {

    @Value("${spring.application.name}")
    private String SERVICE_NAME;

    /**
     * CPU核心数
     */
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    /**
     * 核心线程数
     */
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;

    /**
     * 最大线程数
     */
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

    /**
     * 空闲线程存活时间
     */
    private static final int KEEP_ALIVE = 60;

    /**
     * 任务队列
     */
    private static final int WORK_QUEUE = MAXIMUM_POOL_SIZE * 2;

    /**
     * 自定义线程池<br/>
     * corePoolSize - 即使空闲时仍保留在池中的线程数,除非设置allowCoreThreadTimeOut<br/>
     * maximumPoolSize - 池中允许的最大线程数<br/>
     * keepAliveTime - 当线程数大于内核时,这是多余的空闲线程在终止前等待新任务的最大时间<br/>
     * unit - keepAliveTime参数的时间单位<br/>
     * workQueue - 用于在执行任务之前使用的队列,这个队列将仅保存execute方法提交的Runnable任务<br/>
     * threadFactory - 执行程序创建新线程时使用的工厂<br/>
     * handler - 执行被阻止时使用的处理程序,因为达到线程限制和队列容量<br/>
     * <p>
     * RejectedExecutionHandler rejected = null;<br/>
     *  - rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务,抛出异常<br/>
     *  - rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务,不抛出异常【如果允许任务丢失这是最好的】<br/>
     *  - rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列<br/>
     *  - rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务,回退
     */
    @Bean(value = "thread_pool")
    public ThreadPoolExecutor THREAD_POOL() {
        log.info("===>>> Start initializing the thread pool configuration:serviceName:{}, corePoolSize:{}, maximumPoolSize:{}, keepAlive:{} second",
                SERVICE_NAME,
                CORE_POOL_SIZE,
                MAXIMUM_POOL_SIZE,
                KEEP_ALIVE);
        return new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAXIMUM_POOL_SIZE,
                KEEP_ALIVE,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(WORK_QUEUE),
                r -> new Thread(r, "[" + SERVICE_NAME + "]-threadPool-" + r.hashCode()),
                new ThreadPoolExecutor.DiscardOldestPolicy());
    }

}

TransactionalExecutor:

import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 类名:事务处理线程类
 *
 * @author a-xin
 * @date 2024/4/29 13:57
 */
@Slf4j
@Component
public class TransactionalExecutor implements TransactionSynchronization {

    public static final String BEFORE_COMMIT = "BEFORE_COMMIT";

    public static final String AFTER_COMMIT = "AFTER_COMMIT";

    private static final ThreadLocal<Map<String, Runnable>> RUNNABLE = new ThreadLocal<>();

    @Resource
    private ThreadUtil threadUtil;

    private ThreadPoolExecutor THREAD_POOL;

    @PostConstruct
    public void init() {
        log.info("===>>> Start initializing the transactional thread pool...");
        THREAD_POOL = threadUtil.THREAD_POOL();
    }

    @PreDestroy
    public void destroy() {
        log.error("Start destroy the transactional thread pool...");
        if (null != THREAD_POOL && !THREAD_POOL.isShutdown()) {
            THREAD_POOL.shutdown();
        }
    }

    /**
     * 事务提交执行,如果当前执行方法存在事务,则会通过{@link TransactionalExecutor}的afterCommit()方法进行执行,执行条件为当前事务正常提交<br/>
     * 如果当前执行方法不存在事务,则直接执行runnable方法<br/>
     * 请注意:此方法为自定义线程池异步执行,执行过程中产生的异常,在子线程中是不能返回主线程的!!!
     *
     * @param runnable the runnable task
     */
    public void beforeRun(@NonNull Runnable runnable) {
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            log.warn("There are no transactions for the current method...");
            runnable.run();
            return;
        }
        log.info("There is a transaction for the current method...");
        Map<String, Runnable> runnableMap = RUNNABLE.get();
        if (CollUtil.isEmpty(runnableMap)) {
            runnableMap = new HashMap<>();
            RUNNABLE.set(runnableMap);
            TransactionSynchronizationManager.registerSynchronization(this);
        }
        runnableMap.put(BEFORE_COMMIT, runnable);
    }

    /**
     * 事务提交执行,如果当前执行方法存在事务,则会通过{@link TransactionalExecutor}的afterCommit()方法进行执行,执行条件为当前事务正常提交<br/>
     * 如果当前执行方法不存在事务,则直接执行runnable方法<br/>
     * 请注意:此方法为自定义线程池异步执行,执行过程中产生的异常,在子线程中是不能返回主线程的!!!
     *
     * @param runnable the runnable task
     */
    public void afterRun(@NonNull Runnable runnable) {
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            log.warn("There are no transactions for the current method...");
            runnable.run();
            return;
        }
        log.info("There is a transaction for the current method...");
        Map<String, Runnable> runnableMap = RUNNABLE.get();
        if (CollUtil.isEmpty(runnableMap)) {
            runnableMap = new HashMap<>();
            RUNNABLE.set(runnableMap);
            TransactionSynchronizationManager.registerSynchronization(this);
        }
        runnableMap.put(AFTER_COMMIT, runnable);
    }

    @Override
    public void beforeCommit(boolean readOnly) {
        log.debug("The transaction commit is processed... ");
        Map<String, Runnable> runnableMap = RUNNABLE.get();
        THREAD_POOL.execute(runnableMap.get(BEFORE_COMMIT));
    }

    @Override
    public void afterCommit() {
        log.debug("The transaction commit is processed... ");
        Map<String, Runnable> runnableMap = RUNNABLE.get();
        THREAD_POOL.execute(runnableMap.get(AFTER_COMMIT));
    }

    @Override
    public void afterCompletion(int status) {
        log.info("The current transaction has been processed: {} ... ", status);
        RUNNABLE.remove();
    }

}

使用实例:

/**  
* 测试API接口  
*/  
@Override  
@Transactional(rollbackFor = Exception.class)  
@AfterCommit(method = "after")  
@BeforeCommit(method = "before")  
public Result<String> testApi(SysForm form) {  
  
    SysUserInfo loginUser = UserUtil.getLoginUser();  
    loginUser.setEmail("909090@qq.com");  
    sysUserInfoMapper.updateById(loginUser);  
    System.out.println(1 / 0);  
  
    transactionalExecutor.afterRun(() -> {  
        System.out.println("after: " + loginUser);  
    });  
    transactionalExecutor.beforeRun(() -> {  
        System.out.println("before: " + loginUser);  
    });  
  
    return Result.success();  
}  
  
public void after() {  
    log.error("后...");  
    log.error("hou...");  
}  
  
public void before() {  
    log.error("前...");  
    log.error("qian...");  
}

第一种类型:使用注解式:

image.png

第二种类型:使用线程式:

image.png

image.png

运行结果:

image.png

如有问题,可联系:QQxin7045