SpringBoot整合AOP-最佳实践

号外:今天(2019-08-22)写这篇博客的时候,正好是带着耳机听歌,突然有一个想法,在以后的博客里都将会把我写博客时听的觉得好的歌分享给大家,希望与大家不只是技术的交流!学习之前,先听一首歌。

今日分享:『歌名:水星记    歌手:郭顶』

前言

       最近在做一个后管系统,涉及到一个需求想要记录“特殊”方法的调用日志以及方法调用前后数据状态变化。我立马想到用AOP实现该功能,但是在这之前对AOP也只是概念上的了解,在项目中并没有使用(自己没有使用,但是看过别人写的),借此“机会”可以全面的学习一下AOP,在此记录我学习的过程以及使用,可能工作中最幸福的事,莫过于边学习边完成KPI,哈哈!

AOP介绍

       AOP 全称是 Aspect Oriented Programming,即面向切面的编程,AOP 是一种开发理念。AOP通过提供另一种思考程序结构的方式来补充OOP。OOP中模块化的关键单元是类,而在AOP中,模块化单元是切面。 切面实现了跨越多种类型和对象的关注点(例如事务管理)的模块化。在Spring-AOP中,可以通过基于XML配置和@Aspect注释两种方式实现自定义切面。

AOP原理

       关于AOP原理,想必大家都知道。即通过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的前后。当然在实际使用中并非说的这么简单,也会遇到各种问题,在此就不进行详细介绍。感兴趣的同学可以去学习一下Spring-AOP源码。

AOP相关概念

连接点-Join point

       连接点是指程序执行过程中的一些点,比如方法调用,异常处理等,即能够被拦截的地方。在Spring AOP中,仅支持方法级别的连接点(Spring AOP是基于动态代理的,所以是方法拦截的),所以每一个方法都可以是一个连接点。

切点-Pointcut

        切点是与连接点匹配的谓词。通知与切点的表达式相关联,并在切点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。前面是官方文档的解释,基本上看了还是不知道切点是什么,简单的说切点就是具体定位的连接点,上面已经说过每个方法都可以是一个连接点,但实际上我们不会对项目所有的方法都要进行“特殊”处理,只有我们具体定位到的某一个方法就成为切点。

通知/增强 - Advice

        某个方面在特定连接点处采取的操作,也就是具体横切需要执行的逻辑。如果说切点解决的是where的问题,那通知解决的就是when和how的问题。Spring-AOP中定义了以下几种通知类型:

  • 前置通知(Before advice)- 在目标方法调用前执行通知
  • 后置通知(After advice)- 在目标方法完成后执行通知
  • 返回通知(After returning advice)- 在目标方法执行成功后,调用通知
  • 异常通知(After throwing advice)- 在目标方法抛出异常后,执行通知
  • 环绕通知(Around advice)- 在目标方法调用前后均可执行自定义逻辑

切面-Aspect

       切面由切点通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。也就是说切面就可以解决对什么方法(where)在何时(when - 前置、后置或者环绕)执行什么样的横切逻辑(how)的三连发问题。

织入-Weaving

       将切面与其他类型的应用程序或对象链接以创建增强型的对象,所谓织入就是在切点的引导下,将通知逻辑插入到方法调用上,使得我们的通知逻辑在方法调用时得以执行。

引入/引介-Introduction

       引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!

 

AOP应用

      上面对AOP原理及相关概念进行了简单的介绍,接下来将介绍如何在SpringBoot项目中使用AOP实现文章开始说到的功能。首先看一下项目的整体框架,如下图:

SpringBoot整合AOP-最佳实践_第1张图片

虽然只是一个小小功能的实现但是项目中每个模块及框架还是定义的清晰明了,要从小的细节养成好的习惯,这样你不仅收获了知识,Fighting。

引入相关的依赖

       首先引入搭建SpringBoot项目及AOP相关依赖包,对应的版本号使用的是spring-boot-dependencies-2.1.0.RELEASE中定义的版本。


        
        
            org.springframework.boot
            spring-boot-starter
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.aspectj
            aspectjweaver
        

创建相关类

自定义注解类: 

       自定义一个注解类,用于定位具体的方法需要进行增强处理。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author zj
 * @since 2019-08-21
 */
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Operate {
    /**
     * 操作描述
     */
    String desc() default "";

    /**
     * 操作类型
     */
    String type() default "";
}

启动对@AspectJ注解的支持

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author zj
 * @since 2019-08-21
 */
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
    // 支持AOP配置
}

定义切面

       这里需要说明一下,在定义切面选择通知类型时,需结合自己的切面逻辑来确定, 我这里选择环绕类型是因为需要根据方法执行后的结果来决定要不要保存操作记录。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author zj
 * @since 2019-08-21
 */
@Aspect
@Component
public class OperationInterceptor {

    @Around("@annotation(com.zhouj.endless.aop.annotation.Operate)")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Operate operate = null;
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName) && method.isAnnotationPresent(Operate.class)) {
                operate = method.getAnnotation(Operate.class);
            }
        }
        //获取方法参数值数组
        Object[] args = joinPoint.getArgs();
        Object object = joinPoint.proceed(args);
        System.out.println("保存操作记录:"+operate.type()+"--"+operate.desc());
        return object;
    }
}

添加测试类

import com.zhouj.endless.aop.annotation.Operate;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author zj
 * @since 2019-08-21
 */
@RestController
public class OperationController {

    @ResponseBody
    @Operate(desc = "操作日志", type = "查询")
    @RequestMapping(value = "/operation", method = RequestMethod.GET)
    public Object operation(@RequestParam("key") String key) {
        return key;
    }
}

 

测试结果

 

SpringBoot整合AOP-最佳实践_第2张图片

 

Spring-AOP整合及使用介绍完成

 

 

语雀同步更新链接:https://www.yuque.com/apocalypse/wfaxag

[ZhoujEndless]至此!感觉您的阅读,如有任何问题,欢迎打扰!如有表述错误,劳烦指出!

 

你可能感兴趣的:(Spring,spring-aop,注解,java,springboot)