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;
}
}
}