常規(guī)情況下豪娜,我們可以通過業(yè)務定制化的注解
,借助AOP
機制來實現(xiàn)某些通用的處理策略哟楷。比如定義個@Permission
注解侵歇,可以用于標識在具體的方法上,然后用來指定某個方法必須要指定角色的人才能夠訪問調用吓蘑。
// 標識只有管理員角色才能調用此接口
@Permission(role = UserRole.ADMIN)
public void deleteResource(DeleteResourceReqBody reqBody) {
// do something here...
}
這里惕虑,注解里面?zhèn)魅氲膮?shù)始終是編碼的時候就可以確定下來的固定值(role = UserRole.ADMIN
)。
在業(yè)務開發(fā)中磨镶,也許你會遇到另一種場景:
比如有個文檔資源控制接口溃蔫,你需要判斷出當前用戶操作的目標文檔ID,然后去判斷這個用戶是否有此文檔的操作權限琳猫。
我們希望能夠使用注解的方式來實現(xiàn)伟叛,需要能夠將動態(tài)的文檔ID通過注解傳遞,然后在Aspect
處理類中獲取到文檔ID然后進行對應的權限控制脐嫂。但是按照常規(guī)方式去寫代碼的時候统刮,會發(fā)現(xiàn)并不支持直接傳遞一個請求對象到注解中。
這個時候账千,就輪到我們的主角“SpEL表達式
”上場了侥蒙,借助EL表達式,可以讓我們將上面的想法變?yōu)楝F(xiàn)實匀奏。
下面講一下具體的做法鞭衩。
- 先定義一個業(yè)務注解,其中參數(shù)支持傳入
EL表達式
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ResourceAccessPermission {
/**
* 操作的目標資源的唯一ID娃善, 支持EL表達式
*
* @return ID
*/
String objectId();
}
- 編寫EL表達式的
解析器
论衍,如下所示:
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
@Getter
@ToString
@AllArgsConstructor
public class ExpressionRootObject {
private final Object object;
private final Object[] args;
}
- 編寫對應的Aspect切換處理類,借助上面的EL解析器進行獲取注解中的傳入的EL表達式聚磺,然后獲取方法的入?yún)⑴魈ǎx取EL表達式代表的真實的參數(shù)值,進而按照業(yè)務需要的邏輯進行處理瘫寝。
@Component
@Aspect
@Slf4j
public class ResourceAccessPermissionAspect {
private ExpressionEvaluator<String> evaluator = new ExpressionEvaluator<>();
@Pointcut("@annotation(com.vzn.demo.ResourceAccessPermission)")
private void pointCut() {
}
@Before("pointCut()")
public void doPermission(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
ResourceAccessPermission permission = method.getAnnotation(ResourceAccessPermission.class);
if (joinPoint.getArgs() == null) {
return;
}
// [重點]EL表達式的方式讀取對應參數(shù)值
EvaluationContext evaluationContext = evaluator.createEvaluationContext(joinPoint.getTarget(),
joinPoint.getTarget().getClass(), ((MethodSignature) joinPoint.getSignature()).getMethod(),
joinPoint.getArgs());
AnnotatedElementKey methodKey =
new AnnotatedElementKey(((MethodSignature) joinPoint.getSignature()).getMethod(),
joinPoint.getTarget().getClass());
// 讀取objectID蜒蕾,如果以#開頭則按照EL處理,否則按照普通字符串處理
String objectId;
if (StringUtils.startsWith(permission.objectId(), "#")) {
objectId = evaluator.condition(permission.objectId(), methodKey, evaluationContext, String.class);
} else {
objectId = permission.objectId();
}
// TODO 對objectID進行業(yè)務自定義邏輯處理
}
}
至此矢沿,通過EL表達式動態(tài)注解參數(shù)傳遞與解析處理的邏輯就都構建完成了滥搭。
- 具體業(yè)務使用的時候,直接通過EL表達式從請求體中動態(tài)的獲取到對應的參數(shù)值然后傳入到注解
aspect
切面處理邏輯中捣鲸,按照定制的業(yè)務邏輯進行統(tǒng)一處理瑟匆。
@ResourceAccessPermission(objectId = "#reqBody.docUniqueId")
public void deleteResource(DeleteResourceReqBody reqBody) {
// do something here...
}
借助JAVA注解 + AOP + SpEL
的組合,會讓我們在很多實際問題的處理上變得游刃有余栽惶,可以抽象出很多公共通用的處理邏輯愁溜,實現(xiàn)通用邏輯與業(yè)務邏輯的解耦疾嗅,便于業(yè)務層代碼的開發(fā)。
我是悟道君冕象,聊技術代承、又不僅僅聊技術~
期待與你一起探討,一起成長為更好的自己渐扮。