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();
        }
    }
}
相关推荐
雷一言说7 小时前
Spring AI 2.0 Tool Search:工具多了,只把相关的发给模型
spring
郭小李8 小时前
spring mvc 工作流程
java·spring·mvc
乌暮9 小时前
综合案例:宠物电商系统
java·开发语言·spring·mvc·restful·1024程序员节
VimorphTech9 小时前
一段字幕太长,怎样拆句并安排显示时间?
java·spring boot
万联WANFLOW9 小时前
Kimi K3.1 Pro 接口接入指南:模型选型、费用计算与零数据保留
java·spring boot·python·安全·spring·大模型
aiks_yuan9 小时前
Spring AI 2.1 ChatMemory 已过时用什么替代:迁移到 Session Memory 实战(JDBC 存储与会话压缩)
spring boot
学到老活到老9 小时前
Spring 事务怎样生效?为什么加了 @Transactional 仍不回滚?
java·spring
学到老活到老9 小时前
Spring 怎样创建 Bean?@Autowired 在哪一步生效?
java·spring
木头科技10 小时前
【AI 工程化第十八篇】Spring AI 语音 Agent 实战:实时转写、WebSocket、流式 TTS 与打断机制
java·人工智能·安全·spring·性能优化
步行cgn10 小时前
Spring MVC 核心配置文件详解:springmvc.xml 每一行配置的意义
xml·spring·mvc