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();
        }
    }
}
相关推荐
孫治AllenSun1 小时前
【LangChain4J-03】Springboot 项目搭建框架
java·spring boot·后端
骇客野人2 小时前
SpringBoot + RocketMQ 异步解耦与最终一致性解决方案
spring boot·rocketmq·java-rocketmq
东小西2 小时前
【SAA实战】第 2 篇:模型与消息——ReactAgent 怎么挑模型、怎么传消息
java·后端·spring
就叫飞六吧4 小时前
Spring 动态注册与移除 Bean 科普
java·后端·spring
晓晓_za8986685 小时前
多租户 GEO 优化系统源码架构:权限隔离与数据分离实现
java·tcp/ip·spring·缓存·微服务·架构
choumou_M5 小时前
MySQL_1:数据库悲观锁
后端·spring·spring cloud
南城以南溫暖如初1475 小时前
树洞交友系统架构设计与匿名聊天实战指南
java·spring boot·mysql·系统架构·vue·mybatis·交友
会编程的吕洞宾6 小时前
Spring AI 2.0 接 Milvus 做混合检索:RAG 召回率翻倍的实战方案
人工智能·spring·milvus
智码看视界6 小时前
Day60-Serverless:函数计算改写传统Spring Boot
spring boot·云原生·serverless·函数计算·冷启动