该文章是介绍《Spring Boot中AOP与SpEL的利用》,依据我以前的学习笔记以及工作中长时间积攒所整顿的一份笔记,纯干货,心愿对大家有帮忙。

Spring Boot中AOP与SpEL的利用

定义

我不想在这里去摘抄百度百科的内容, 以下内容纯属集体了解:

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的利用》的分享。
  • 也欢送大家交换探讨,该文章若有不正确的中央,心愿大家多多包涵。
  • 你们的反对就是我最大的能源,如果对大家有帮忙给个赞哦~~~