SpringAop通过el表达式动态获取方法入参的值

1.新增工具类

复制代码
import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SpElUtils的增强工具类,相比于SpelUtils,增加了切面对象的上下文。
 * MethodAspectUtils提供的对象,请参考:https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html#cache-spel-context
 * 注意:该方法不支持#beanName语法。
 *
 * 应用场景:
 * <pre>
 *     @Before(value = "@annotation(resource)")
 *     public void checkOperationRight(JoinPoint jp, ProtectResource resource) {
 *         if (!MethodAspectUtils.evaluateWithJoinPointContext(resource.condition(), jp)) {
 *             throw new NoPermissionException("没有权限");
 *         }
 *     }
 *
 *     @ProtectResource(condtion = "#scope.fullDataAccess")
 *     public Department saveOrUpdate(DeptSaveDTO dto, UserDataScope scope) {
 *
 *     }
 * </pre>
 *
 * @see org.springframework.cache.annotation.Cacheable#key()
 * @author zhanghuangbin
 * @date 2024/11/15
 */
public class MethodAspectUtils {


    private static final Map<String, Expression> KEY_CACHE = new ConcurrentHashMap<>(64);

    /**
     *
     * @param el spel表达式,<a href="https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html#cache-spel-context">语法</a>
     *           不支持#beanName语法
     * @param jp
     * @return
     * @param <T>
     */
    public static <T> T evaluateWithJoinPointContext(String el, JoinPoint jp) {
       return evaluateWithJoinPointContext(el, jp, null);
    }

    /**
     *
     * @param el
     * @param jp
     * @param result ProceedingJoinPoint.proceed()的结果
     * @return
     * @param <T>
     */
    public static <T> T evaluateWithJoinPointContext(String el, JoinPoint jp, Object result) {
        Expression expression = KEY_CACHE.computeIfAbsent(el, (key) -> {
            SpelExpressionParser parser = new SpelExpressionParser();
            return parser.parseExpression(key);
        });


        MethodContext context = new MethodContext(jp, result);
        MethodBasedEvaluationContext ctx = new MethodBasedEvaluationContext(context, context.getMethod(), context.getArgs(), new DefaultParameterNameDiscoverer());
        ctx.setVariables(context.getAsVariables());
        return (T) expression.getValue(ctx);
    }


    @Data
    static class MethodContext implements Serializable {

        private final Object[] args;

        private final Object target;

        private final Method method;

        private final String methodName;

        private final Class<?> targetClass;

        private Object result;

        public MethodContext(JoinPoint jp) {
            this(jp, null);
        }

        public MethodContext(JoinPoint jp, Object result) {
            args = jp.getArgs();
            target = jp.getTarget();
            method = ((MethodSignature) jp.getSignature()).getMethod();
            methodName = method.getName();
            targetClass = target.getClass();
            this.result = result;
        }

        public Map<String, Object> getAsVariables() {
            Map<String, Object> map = new HashMap<>();
            map.put("args", args);
            map.put("target", target);
            map.put("method", method);
            map.put("methodName", methodName);
            map.put("targetClass", targetClass);
            map.put("result", result);
            return map;
        }
    }
}

2. 在切面中使用

复制代码
@Aspect
@Slf4j
@Component
public class ToggleDatasourceAspect {

    @Around("@annotation(datasource)")
    public Object selectTargetDatasource(ProceedingJoinPoint joinPoint, ToggleDatasource datasource) throws Throwable {
        String el = datasource.databaseId();
        log.info("ToggleDatasource selectTargetDatasource el={}", el);
        String databaseId = MethodAspectUtils.evaluateWithJoinPointContext(el, joinPoint);
        try {
            DynamicDataSourceContextHolder.push(databaseId);
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }
}
相关推荐
萧瑟余晖3 小时前
Spring Boot 核心理念与快速上手
spring boot
省钱兄--zs3 小时前
北京24小时自助健身房系统软件开发实战:从架构设计到部署指南
java·数据仓库·spring boot·小程序·需求分析
Terra.K10 小时前
Spring AI day1(SSE+AI)
java·人工智能·spring·springai
小蒜学长12 小时前
在线保险服务与管理平台的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·服务平台·在线保险
Wang's Blog14 小时前
Java框架 SpringCloud 快速入门: 路由断言工厂
java·spring·spring cloud
ym hyd 11114 小时前
门诊挂号系统源码 Java+SpringBoot+Vue3 前后分离
java·vue.js·spring boot·毕设
萧瑟余晖15 小时前
Spring Boot 自动配置原理详解
spring boot
知守观15 小时前
Spring Boot 2.1 → 3.5 迁移推演:这个 2018 年的项目会炸在哪
java·spring boot
LiuYanG15 小时前
MQTT 客户端反复重连怎么办?Spring Boot 接 EMQX 断线重连踩坑实录
spring boot·物联网
Q264336502315 小时前
【有源码】基于SpringBoot+Vue的博物馆展览与服务一体化平台-java博物馆数字化展品展示与交互平台设计
java·vue.js·spring boot·spring·毕业设计·javaweb·课程设计