AOP

AOP基础

AOP: Aspect Object Programming(面向切面编程),其实就是面向特定方法编程

动态代理是面向切面编程最主流的实现。而SpringAOP 是Spring 框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特性的方法进行编程

需求:统计service 层各个业务方法的执行时间

引入SpringBoot AOP依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

实现一个切面类:

package com.chuangzhou.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TimeAspact {
    
    @Around("execution(* com.chuangzhou.serivce.impl.*.*(..))")
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        //1. 记录当前系统毫秒值
        long start = System.currentTimeMillis();

        //2.执行拦截方法
        Object result = proceedingJoinPoint.proceed();  // result 为被执行方法的返回值

        //3.记录当前系统毫秒值
        long end = System.currentTimeMillis();
        log.info(proceedingJoinPoint.getSignature() + "耗时:{}", end - start);

        return result;
    }

}

拦截到方法的耗时时间:

使用场景和优势:

AOP 核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知 + 切入点)
  • 目标对象:Target,通知所应用的对象

debug aop 执行流程:

通知类型

新知识点:@Pointcut: 可以抽取切入点表达式,别的切面类也可以使用,前提是不是private修饰

package com.chuangzhou.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect1 {

    @Pointcut("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void pt(){}

    @Before("pt()")   //前置通知
    public void Before(){
        log.info("Before ...");
    }


    @Around("pt()")  //环绕通知
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before...");

        Object proceed = proceedingJoinPoint.proceed();

        log.info("around after...");
        return proceed;
    }

    @After("pt()")  // 后置通知,无论是否有异常
    public void after(){
        log.info("after ...");
    }

    @AfterReturning("pt()")  // 后置通知,有异常不会执行
    public void afterReturn(){
        log.info("afterReturn ...");
    }

    @AfterThrowing("pt()")  // 后置通知,有异常才会执行
    public void afterThrow(){
        log.info("afterThrow ...");
    }

}

通知顺序

切面类2:

package com.chuangzhou.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect2 {

    @Before("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void before2(){
        log.info("before2 ..");
    }

    @After("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void after2(){
        log.info("after2 ..");
    }
}

切面类3:

package com.chuangzhou.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect3 {

    @Before("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void before3(){
        log.info("before3 ..");
    }

    @After("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void after3(){
        log.info("after3 ..");
    }
}

切面类4:

package com.chuangzhou.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect4 {

    @Before("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void before4(){
        log.info("before4 ..");
    }

    @After("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    public void after4(){
        log.info("after4 ..");
    }
}

执行结果:

因此不同切面类中,默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

还有一种配置方式就是使用Spring 提供的 @Order(数字) 注解可以定制化顺序:

结果:

  • 目标方法前的通知方法:数字排名靠前的先执行
  • 目标方法后的通知方法:数字排名靠前的后执行

切入点表达式 - execution

切入点表达式 - @annotation

基于execution 如何只匹配两个单独的切入点?,可以采用如下方式:

    
    @Before("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.list()) || " +
            "execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.delete(java.lang.Integer)) ")
    public void before(){
        log.info("before5 ...");
    }

但是有点繁琐,下面介绍基于注解来进行匹配切入点:

1.自定义注解:

package com.chuangzhou.aop;


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

@Retention(RetentionPolicy.RUNTIME) //什么时候生效
@Target(ElementType.METHOD) //标记的位置
public @interface MyLog {
}

2.在切入点方法上标记自定义注解

    @MyLog
    @Override
    public List<Dept> list() {
        List<Dept> depts = deptMapper.list();
        return depts;
    }


    @MyLog
    @Transactional
    @Override
    public void delete(Integer id) throws Exception {

        deptMapper.deleteById(id); // 删除部门

        empMapper.deleteByDeptId(id);

    }

3.切面类:

package com.chuangzhou.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
public class MyAspect5 {


    @Pointcut("@annotation(com.chuangzhou.aop.MyLog)")
    private void pt(){}

    @Before("pt()")
    public void before(){
        log.info("before5 ...");
    }
}

总结:@annotation 切入点表达式,用于匹配标识有特定注解的方法

连接点

在spring 中用 JoinPoint 抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名,方法名,方法参数等

  • 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
  • 对于 其他四种通知,获取连接点信息只能使用JoinPoint ,它是 ProceedingJoinPoint 的父类
package com.chuangzhou.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Slf4j
@Component
@Aspect
public class MyAspect6 {

    @Pointcut("execution(* com.chuangzhou.serivce.impl.DeptServiceImpl.*(..))")
    private void pt(){}


    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("MyAspect6   around before ...");
        //1. 获取目标对象的类名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        log.info("获取目标对象的类名:{}",className);
        //2.获取目标方法的签名
        String methodSignature = proceedingJoinPoint.getSignature().getName();
        log.info("获取目标方法的签名:{}", methodSignature);
        //3.获取目标方法运行时传入的参数
        Object[] args = proceedingJoinPoint.getArgs();
        log.info("获取目标方法运行时传入的参数:{}", Arrays.toString(args));
        //4.放行目标方法执行
        Object proceed = proceedingJoinPoint.proceed();
        log.info("获取目标方的返回值:{}",proceed);
        log.info("MyAspect6   around after ...");
        return proceed;
    }
}

结果:

posted @ 2023-08-27 16:36  chuangzhou  阅读(12)  评论(0编辑  收藏  举报