JAVA注解与AOP、SpEL结合,能否激发更多实现潜能?

2026-04-28 09:282阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1154个文字,预计阅读时间需要5分钟。

JAVA注解与AOP、SpEL结合,能否激发更多实现潜能?

借助JAVA注解、AOP和SpEL的组合,我们可以在处理众多实际问题时变得游刃有余,可抽象出许多通用处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。

借助`JAVA注解 + AOP + SpEL`的组合,会让我们在很多实际问题的处理上变得游刃有余,可以抽象出很多公共通用的处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。

常规情况下,我们可以通过业务定制化的注解,借助AOP机制来实现某些通用的处理策略。比如定义个@Permission注解,可以用于标识在具体的方法上,然后用来指定某个方法必须要指定角色的人才能够访问调用。

// 标识只有管理员角色才能调用此接口 @Permission(role = UserRole.ADMIN) public void deleteResource(DeleteResourceReqBody reqBody) { // do something here... }

这里,注解里面传入的参数始终是编码的时候就可以确定下来的固定值(role = UserRole.ADMIN)。

在业务开发中,也许你会遇到另一种场景:

JAVA注解与AOP、SpEL结合,能否激发更多实现潜能?

比如有个文档资源控制接口,你需要判断出当前用户操作的目标文档ID,然后去判断这个用户是否有此文档的操作权限。

我们希望能够使用注解的方式来实现,需要能够将动态的文档ID通过注解传递,然后在Aspect处理类中获取到文档ID然后进行对应的权限控制。但是按照常规方式去写代码的时候,会发现并不支持直接传递一个请求对象到注解中。

这个时候,就轮到我们的主角“SpEL表达式”上场了,借助EL表达式,可以让我们将上面的想法变为现实。

下面讲一下具体的做法。

  1. 先定义一个业务注解,其中参数支持传入EL表达式

@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ResourceAccessPermission { /** * 操作的目标资源的唯一ID, 支持EL表达式 * * @return ID */ String objectId(); }

  1. 编写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; }

  1. 编写对应的Aspect切换处理类,借助上面的EL解析器进行获取注解中的传入的EL表达式,然后获取方法的入参,读取EL表达式代表的真实的参数值,进而按照业务需要的逻辑进行处理。

@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表达式的方式读取对应参数值 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进行业务自定义逻辑处理 } }

至此,通过EL表达式动态注解参数传递与解析处理的逻辑就都构建完成了。

  1. 具体业务使用的时候,直接通过EL表达式从请求体中动态的获取到对应的参数值然后传入到注解aspect切面处理逻辑中,按照定制的业务逻辑进行统一处理。

@ResourceAccessPermission(objectId = "#reqBody.docUniqueId") public void deleteResource(DeleteResourceReqBody reqBody) { // do something here... }

借助JAVA注解 + AOP + SpEL的组合,会让我们在很多实际问题的处理上变得游刃有余,可以抽象出很多公共通用的处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。


我是悟道君,聊技术、又不仅仅聊技术~
期待与你一起探讨,一起成长为更好的自己。

本文共计1154个文字,预计阅读时间需要5分钟。

JAVA注解与AOP、SpEL结合,能否激发更多实现潜能?

借助JAVA注解、AOP和SpEL的组合,我们可以在处理众多实际问题时变得游刃有余,可抽象出许多通用处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。

借助`JAVA注解 + AOP + SpEL`的组合,会让我们在很多实际问题的处理上变得游刃有余,可以抽象出很多公共通用的处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。

常规情况下,我们可以通过业务定制化的注解,借助AOP机制来实现某些通用的处理策略。比如定义个@Permission注解,可以用于标识在具体的方法上,然后用来指定某个方法必须要指定角色的人才能够访问调用。

// 标识只有管理员角色才能调用此接口 @Permission(role = UserRole.ADMIN) public void deleteResource(DeleteResourceReqBody reqBody) { // do something here... }

这里,注解里面传入的参数始终是编码的时候就可以确定下来的固定值(role = UserRole.ADMIN)。

在业务开发中,也许你会遇到另一种场景:

JAVA注解与AOP、SpEL结合,能否激发更多实现潜能?

比如有个文档资源控制接口,你需要判断出当前用户操作的目标文档ID,然后去判断这个用户是否有此文档的操作权限。

我们希望能够使用注解的方式来实现,需要能够将动态的文档ID通过注解传递,然后在Aspect处理类中获取到文档ID然后进行对应的权限控制。但是按照常规方式去写代码的时候,会发现并不支持直接传递一个请求对象到注解中。

这个时候,就轮到我们的主角“SpEL表达式”上场了,借助EL表达式,可以让我们将上面的想法变为现实。

下面讲一下具体的做法。

  1. 先定义一个业务注解,其中参数支持传入EL表达式

@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ResourceAccessPermission { /** * 操作的目标资源的唯一ID, 支持EL表达式 * * @return ID */ String objectId(); }

  1. 编写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; }

  1. 编写对应的Aspect切换处理类,借助上面的EL解析器进行获取注解中的传入的EL表达式,然后获取方法的入参,读取EL表达式代表的真实的参数值,进而按照业务需要的逻辑进行处理。

@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表达式的方式读取对应参数值 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进行业务自定义逻辑处理 } }

至此,通过EL表达式动态注解参数传递与解析处理的逻辑就都构建完成了。

  1. 具体业务使用的时候,直接通过EL表达式从请求体中动态的获取到对应的参数值然后传入到注解aspect切面处理逻辑中,按照定制的业务逻辑进行统一处理。

@ResourceAccessPermission(objectId = "#reqBody.docUniqueId") public void deleteResource(DeleteResourceReqBody reqBody) { // do something here... }

借助JAVA注解 + AOP + SpEL的组合,会让我们在很多实际问题的处理上变得游刃有余,可以抽象出很多公共通用的处理逻辑,实现通用逻辑与业务逻辑的解耦,便于业务层代码的开发。


我是悟道君,聊技术、又不仅仅聊技术~
期待与你一起探讨,一起成长为更好的自己。