Java SpringAOP简介

简介

官方介绍:
SpringAOP的全称是(Aspect Oriented Programming)中文翻译过来是面向切面编程,AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

个人理解:
java开发过程中,若多个模块方法执行前后需要统一功能,例如日志记录,权限校验,事务,效率(程序执行时间)等。若一一修改代码会比较麻烦且代码冗余,我们可以通过切面+注解的方式,给各模块方法增加简便的统一的功能。

SpringAOP的应用场景

日志记录
权限验证(SpringSecurity有使用)
事务控制(调用方法前开启事务, 调用方法后提交关闭事务 )
效率检查(检测方法运行时间)
数据源代理(seata里面,获取到数据源连接执行的sql)
缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )

核心概念

切面(Aspect):切面是一个概念,是一个模块化的单元,它包含了与特定关注点相关的通知和切点的定义。通知指的是在执行某个切点时要执行的代码逻辑,例如前置通知、后置通知、环绕通知等。切点指的是定义了真正需要被增强的连接点,例如方法调用或者方法执行等。

连接点(Join Point):连接点指代是需要被增强的地方,程序执行过程中的一个特定点,例如方法调用、方法执行、构造函数调用等。切点实际上就是连接点的选择,用来指定需要被切入的具体方法。

通知(Advice):通知是切面中的连接点执行前,执行后需要增加的具体代码逻辑,它定义了在特定切点执行前、执行后或执行中进行的操作。常见的通知类型有前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。

切点表达式(Pointcut Expression):切点表达式用于指定需要被增强的具体连接点,可以通过AspectJ注解或者Spring AOP XML配置来定义。

实现方式

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
</dependency>

Springboot 项目无需引入上面jar包。

定义切点

指定需要增强的方法,切点表达式写法参考:
https://blog.51cto.com/u_16099229/9422977

// 定义切点,匹配某个特定的方法或包下的所有方法
    @Pointcut(value = "execution(* * ..StudentServiceImpl.createStudent(..))")
    public void serviceMethods() {
    }

通知类型

@Before:前置通知,若通知程序异常不执行连接点;
@After:后置通知,连接点是否异常,都会执行;
@AfterReturning:后置通知,连接点正常才会执行;
@AfterThrowing:后置通知,连接点异常才会执行;
@Around:环绕通知,可以作用于连接点前或连接点后(joinPoint.proceed()前的是连接点前,后连接点后),可以修改连接点返回值。

执行循序:

@Around > @Before > 连接点中的程序 > @AfterReturning/@AfterThrowing > @After > @Around

通知调用示例

@Slf4j
@Aspect
@Component
public class LoggingAspect {

    // 定义切点,匹配某个特定的方法或包下的所有方法
    @Pointcut(value = "execution(* * ..StudentServiceImpl.createStudent(..))")
    public void serviceMethods() {
    }

    /**
     * Before:前置通知,带方法参数的切面
     * 切面方法有参数时要求参数是JoinPoint类型,参数名自定义,该参数就代表了连接点方法,即createStudent方法
     * 使用该参数可以获取切入点表达式、切入点方法签名、目标对象等
     * <p>
     * (1)访问修饰符权限是public
     * (2)方法的返回值是void
     * (3)方法名称是自定义
     * (4)可以没有方法形式参数,如果有,必为JoinPoint类型
     * (5)必须使用@Before注解来声明切入的时机是前切功能和切入点
     */
    @Before(value = "serviceMethods()")
    public void beforeAddStudent(JoinPoint joinPoint) {
        log.info("前置通知(Before):若异常不执行目标程序");
        log.info("前置通知(Before):方法签名:{}", joinPoint.getSignature());
        log.info("前置通知(Before):方法名称:{}", joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        log.info("前置通知(Before):方法参数:{}", Arrays.toString(args));
    }

    @After(value = "serviceMethods()")
    public void afterAddStudent() {
        log.info("后置通知(After):目标程序异常继续执行");
    }

    @AfterReturning(value = "serviceMethods()")
    public void afterReturningAddStudent() {
        log.info("后置通知(AfterReturning):目标程序正常才会继续执行");
    }


    @AfterThrowing(value = "serviceMethods()")
    public void afterThrowingAddStudent() {
        log.info("后置通知(AfterThrowing):目标异常正常才会继续执行");
    }

    /**
     * 环绕通知,环绕通知可以改变方法返回值
     * <p>
     * 能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码
     */
      /*
    环绕通知:@Around(切入点表达式)
    1、环绕通知是最重要的一个通知,他表示在连接点方法的前或者后都可以执行,它的本质就是jdk动态代理的invoke
       方法的method参数
    2、定义格式
        a、public
        b、必须有返回值,类型为Object
    2、连接点出现异常时,可以增加try catch处理,否则异常向上一级抛出。
     */
    @Around(value = "serviceMethods()")
    public Object aroundAddStudent(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("环绕通知(Around):连接点前");

        try {
            Object retVal = joinPoint.proceed();
            log.info("retVal : {}", retVal);
            log.info("环绕通知(Around):连接点后");
            return retVal;
        } catch (Exception e) {
            log.info("环绕通知(Around):连接点出现异常后");
            return new StudentResult();
        }
    }
}

AOP中使用自定义注解

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MetricTime {

    String value();
}

注解+环绕切面

@Slf4j
@Aspect
@Component
public class LoggingAspect {

    // 定义切点,匹配某个特定的方法或包下的所有方法
    @Pointcut(value = "execution(* * ..StudentServiceImpl.createStudent(..))")
    public void serviceMethods() {
    }
 
    @Around("@annotation(metricTime)")
    public Object around(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
        String name = metricTime.value();
        long start = System.currentTimeMillis();
        System.out.println("[Metrics] " + name);

        log.info("环绕通知(Around):连接点前:{}", metricTime.value());
        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            log.error("环绕通知(Around):连接点后异常:{}", e.getMessage());
            return null;
        } finally {
            long t = System.currentTimeMillis() - start;
            log.info("[Metrics] {}: {}ms", name, t);
        }
    }
}

在连接点上增加自定义注解
在这里插入图片描述
测试调用结果
在这里插入图片描述

文档参考网址:

https://blog.csdn.net/xuewenyu_/article/details/134246558
https://blog.51cto.com/u_16099229/9422977

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值