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();
        }
    }
}
相关推荐
user_admin_god7 小时前
第 01 篇:OpenAI 兼容 API 初探 —— 用 curl 跑通第一次对话
java·人工智能·spring boot·语言模型·devops
步行cgn8 小时前
Spring 底层如何创建对象:反射机制与实例化策略
java·后端·spring
小辰爱喝汤9 小时前
基于智能定价的 校园二手交易平台-计算机毕业设计
spring boot·毕业设计·课程设计
步行cgn10 小时前
Spring 注入 null 和空字符串详解
数据库·spring·oracle
RuoyiOffice10 小时前
SpringBoot3+Vue3 库存盘点:账面和实盘对不上,差异单怎么走审批再调账
spring boot·vue3·erp·spring boot 3·库存盘点·盘盈盘亏
Wang's Blog11 小时前
Java框架快速入门: Spring Security+OAuth2之单点登录(SSO)实战
服务器·spring·状态模式
譕痕11 小时前
Idea 集成 智能体开发工具“千问(qwen)”
java·ide·spring·语言模型
行者-全栈开发12 小时前
Spring Boot 接入 MaxKey 单点登录:6 个内部系统,一次登录全通行
spring boot·iam·oauth2·统一认证·oidc·sso 单点登录·maxkey
user_admin_god12 小时前
第 09 篇:实践一 —— 文本摘要(Map-Reduce 分块)
java·人工智能·spring boot·语言模型
vx_BS8133012 小时前
【项目编号:project49759】Spring Boot 宠物综合服务平台:商城、预约、咨询、活动与健康档案的一体化实现
java·spring boot·eclipse·tomcat·intellij-idea