Spring Boot中AOP与SpEL的运用

定义

我不想在这里去摘抄百度百科的内容, 以下内容纯属个人理解:

jspel与jsp有什么关系硬核资本清华博士的Spring Boot中AOP与SpEL笔记码农跪拜 Webpack

AOP: 面向切面编程?NO, 我们低端点, 它便是一个非常厉害的装饰器, 可以和业务逻辑平走运行, 适宜处理一些日志记录/权限校验等操作

SpEL: 全称SpEL表达式, 可以理解为JSP的超级加强版, 利用得当可以为我们节省代码(此话为抄袭), 大家利用它最多的地方实在是引入配置, 例如:

// 你看我熟习不?@Value("#{file.root}")

那么什么时候会一起利用它们呢?

实在很多履历丰富的大佬们下意识就能回答, 记录系统日志

没错, AOP是与业务逻辑平行, SpEL是与业务数据平行, 把它们结合起来, 就能让我们在传统的面向工具/过程编程的套路中更上一层楼

接下来我就用一个实际的记录业务日志功能的实现来记录如何在Spring Boot中利用AOP与SpEL

理解它们

想要利用它们, 作为先行者列出个中的重点是个人责任, 我们先来看看个中须要特殊在意的几点观点:

AOP

明确观点:

@Aspect: 切面

@Poincut: 切点

JoinPoint: 普通连接点

ProceedingJoinPoint: 环抱连接点

切入机遇:

before: 目标方法开始实行之前

after: 目标方法开始实行之后

afterReturning: 目标方法返回之后

afterThrowing: 目标方法抛出非常之后

around: 环抱目标方法, 最为分外

我们用代码来展示下各个切入机遇的位置:

try{ try{ @around @before method.invoke(); @around }catch(){ throw new Exception(); }finally{ @after } @afterReturning}catch(){ @afterThrowing}

个中的around是最为分外的切入机遇, 它的切入点也必须为ProceedingJoinPoint, 其它均为JoinPoint

我们须要手动调用ProceedingJoinPoint的proceed方法, 它会去实行目标方法的业务逻辑

around最麻烦, 却也是最强的

SpEL

要利用SpEL, 肯定难不住每一位小伙伴, 但它到底是如何从一个大略的笔墨表达式转换为运行中的数据内容呢?

实在Spring和Java已经供应了大部分功能, 我们只须要手动处理如下部分:

TemplateParserContext: 表达式解析模板, 即如何提取SpEL表达式

RootObject: 业务数据内容, SpEL表达式解析过程中须要的数据都从这个中获取

ParameterNameDiscoverer: 参数解析器, 在SpEL解析的过程中, 考试测验从rootObject中直接获取数据

EvaluationContext: 解析高下文, 包含RootObject, ParameterNameDiscoverer等数据, 是全体SpEL解析的环境

那么SpEL的过程我们可以粗略概括为:

设计RootObject->设计SpEL表达式的运行高下文->设计SpEL解析器(包括表达式解析模板和参数解析器)

实践出真知

业务场景

实现一个业务日志记录的功能, 须要记录如下内容:

功能模块

业务描述, 包含业务数据id

目标方法详情

目标类详情

入参

返回值

用户信息, 如用户id/ip等

一览无余, 这些数据都须要在运行的过程中进行获取, 还记得吗?在刚才的先容里, 运行这个状态词是AOP和SpEL的特点

以是, 我们将利用AOP和SpEL, 来完成这个需求

业务剖析

仔细不雅观察须要记录的数据内容, 我们可以剖析它们从哪里得到:

功能模块: 通过AOP中切入点的表明得到

业务描述: 将SpEL表达式写入AOP切入点的表明, 在AOP运行过程中翻译表达式得到

目标方法详情: 通过AOP切入点得到

目标类详情: 通过AOP切入点得到

入口: 通过AOP切入点得到

返回值: 通过AOP切入点得到

用户信息: 在AOP运行的过程中通过代码得到

在明确了数据来源后, 我们前辈行AOP干系的设计

AOP 表明设计

AOP表明的目的是在代码层面记录数据, 并供应切入点, 上面提及的功能模块和业务描述须要在这里写入, 开始写代码:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface KoalaLog { / 功能模块 @return 功能模块 / String type() default ""; / 业务描述 @return 业务描述 / String content() default "";}

AOP 切面设计

切面设计实在就两个内容:

切入点

切入机遇

有没有创造这和刚才的先容是差不多的呢?

回归正题, 我们的切入点便是刚才设计的表明, 切入机遇是目标方法实行之后, 即afterReturning

细心的小伙伴肯定知道, 方法的实行是可能会出错的, 以是除了afterReturning之外, 我们还须要再加一个关于非常的切入机遇, 即afterThrowing

@Aspect@Component@Slf4jpublic class KoalaLogAspect { @Pointcut(value = "@annotation(cn.houtaroy.springboot.koala.starter.log.annotations.KoalaLog)") public void logPointCut() {} @AfterReturning(value = "logPointCut()", returning = "returnValue") public void log(JoinPoint joinPoint, Object returnValue) { // 记录正平日记 } @AfterThrowing(pointcut = "logPointCut()", throwing = "error") public void errorLog(JoinPoint joinPoint, Exception error) { // 记录非常日志 } }

以上, AOP的全部设计都结束了, 至于如何实现记录日志的逻辑, 我们要等SpEL设计结束后再进行, 暂且搁置

SpEL 模型设计

为了实现SpEL, 我们须要如下几个模型:

LogRootObject: 运行数据来源

LogEvaluationContext: 解析高下文, 用于全体解析环境

LogEvaluator: 解析器, 解析SpEL, 获取数据

LogRootObject

LogRootObject是SpEL表达式的数据来源, 即业务描述

上文提及在业务描述中须要记录业务数据的id, 它可以通过方法参数得到, 那么:

@Getter@AllArgsConstructorpublic class LogRootObject { / 方法参数 / private final Object[] args;}

但须要把稳的是, 它的构造直接决定了业务描述SpEL表达式翻译完成的结果, 以是务必提前和需求沟通好业务描述最全面的数据范围

比如, 我们还须要记录目标方法/目标类的信息, 那这个设计是不知足, 该当是:

@Getter@AllArgsConstructorpublic class LogRootObject { / 目标方法 / private final Method method; / 方法参数 / private final Object[] args; / 目标类的类型信息 / private final Class<?> targetClass;}

LogEvaluationContext

Spring供应了MethodBasedEvaluationContext, 我们只须要继续它, 并实现对应的布局方法:

public class LogEvaluationContext extends MethodBasedEvaluationContext { / 布局方法 @param rootObject 数据来源工具 @param discoverer 参数解析器 / public LogEvaluationContext(LogRootObject rootObject, ParameterNameDiscoverer discoverer) { super(rootObject, rootObject.getMethod(), rootObject.getArgs(), discoverer); }}

LogEvaluator

这是我们最核心的解析器, 用于解析SpEL, 返回真正期望的数据内容

我们须要初始化表达式编译器/表达式编译模板/参数解析器

@Getterpublic class LogEvaluator { / SpEL解析器 / private final SpelExpressionParser parser = new SpelExpressionParser(); / 参数解析器 / private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); / 表达式模板 / private final ParserContext template = new TemplateParserContext("${", "}"); / 解析 @param expression 表达式 @param context 日志表达式高下文 @return 表达式结果 / public Object parse(String expression, LogEvaluationContext context) { return getExpression(expression).getValue(context); } / 获取翻译后表达式 @param expression 字符串表达式 @return 翻译后表达式 / private Expression getExpression(String expression) { return getParser().parseExpression(expression, template); } }

到此为止, 全体SpEL表达式的全部内容搞定, 再次强调下它的逻辑:

设计RootObject->设计SpEL表达式的运行高下文->设计SpEL解析器(包括表达式解析模板和参数解析器)

AOP业务逻辑

完成了SpEL的设计, 我们可以把目光回归到刚才AOP中没有实现的业务代码, 这里的流程非常大略:

解析SpEL->天生日志实体->保存日志

这里的内容不再赘述, 小伙伴们只须要负责看一遍代码就全部明白了

@Aspect@Slf4jpublic class KoalaLogAspect { / 日志SpEL解析器 / private final LogEvaluator evaluator = new LogEvaluator(); / jackson / @Autowired private ObjectMapper objectMapper; / 日志切入点 / @Pointcut(value = "@annotation(cn.houtaroy.springboot.koala.starter.log.annotations.KoalaLog)") public void logPointCut() { } / 方法返回后切入点 @param joinPoint 切入点 @param returnValue 返回值 / @AfterReturning(value = "logPointCut()", returning = "returnValue") public void log(JoinPoint joinPoint, Object returnValue) { // 记录正平日记 Log koalaLog = generateLog(joinPoint); try { koalaLog.setReturnValue(objectMapper.writeValueAsString(returnValue)); // 记录日志代码... } catch (JsonProcessingException e) { log.error("KOALA-LOG: 序列化返回值失落败", e); } } / 方法抛出非常后切入点 @param joinPoint 切入点 @param error 非常 / @AfterThrowing(pointcut = "logPointCut()", throwing = "error") public void errorLog(JoinPoint joinPoint, Exception error) { // 记录非常日志 Log koalaLog = generateLog(joinPoint); koalaLog.setReturnValue(error.getMessage()); // 记录日志代码... } / 天生日志实体 @param joinPoint 切入点 @return 日志实体 / private Log generateLog(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); Object[] args = joinPoint.getArgs(); Class<?> targetClass = AopProxyUtils.ultimateTargetClass(joinPoint.getTarget()); KoalaLog annotation = method.getAnnotation(KoalaLog.class); LogRootObject rootObject = new LogRootObject(method, args, targetClass); LogEvaluationContext context = new LogEvaluationContext(rootObject, evaluator.getDiscoverer()); Object content = evaluator.parse(annotation.content(), context); Log koalaLog = Log.builder().type(annotation.type()).content(content.toString()).createTime(new Date()).build(); try { koalaLog.setArguments(objectMapper.writeValueAsString(args)); } catch (JsonProcessingException e) { log.error("KOALA-LOG: 序列化方法参数失落败", e); } return koalaLog; }}

上面的内容短缺记录日志的详细代码, 各位根据实际情形进行补充(我不会承认是自己拖延症还没有将ORM封装写完)

总结

AOP和SpEL是专注于运行状态下的数据处理, 在大略的业务中, 完备没有必须利用的必要

代码是我们的工具, 如何精确和便捷地利用它们也是一种能力

以上便是《Spring Boot中AOP与SpEL的运用》的分享。
也欢迎大家互换磋商,该文章若有禁绝确的地方,希望大家多多包涵。
你们的支持便是我最大的动力,如果对大家有帮忙给个赞哦~~~