SpringAOP

发布于:2024-11-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

快速入门

引入AOP依赖:

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

编写简单的代码:实现一个统计方法执行的时间

@Slf4j
@Aspect
@Component
public class TimeRecordAspect {
    @Around("execution(* com.wpre.springaop.controller.*.*(..))")
    public Object timeRecord(ProceedingJoinPoint pjt) throws Throwable {
        //记录开始时间
        long start = System.currentTimeMillis();

        //执行目标方法
        Object ret = pjt.proceed();

        //记录结束时间
        long end = System.currentTimeMillis();

        log.info("{}耗时:{}ms", pjt.getSignature(), end - start);

        return ret;
    }
}

切点

切点(PointCut),也称切入点,是一个表达式,用于匹配哪些类、方法(或字段)是通知(Advice)应该被应用的地方

切面

切面是实现横切关注点的模块化单位。它由两个主要部分组成:切点和通知。切面将切点和通知结合起来,定义了在哪些连接点上应用哪些通知。切面通常用类和注解来表示。

通知

通知是切面的一部分,它定义了在切点匹配到的连接点上应该执行的逻辑。通知可以在方法执行之前、之后或抛出异常时被触发

连接点

连接点是程序执行过程中的特定点,这些点可以被Spring AOP框架所拦截。在Spring AOP中,连接点通常是方法的执行,但也包括异常抛出等其他类型的连接点

通知类型

  • 这是通知的具体分类,每种类型的通知定义了在程序执行的不同阶段执行的逻辑。具体包括:
    • Before Advice:在目标方法执行之前执行的通知。
    • After Returning Advice:在目标方法成功执行后执行的通知。
    • After Throwing Advice:在目标方法抛出异常后执行的通知。
    • After (Finally) Advice:无论目标方法是否抛出异常,都会在方法执行后执行的通知。
    • Around Advice:在目标方法执行前后都能执行的通知,可以控制方法的执行流程。

在这里插入图片描述

代码案例:

@Slf4j
@Aspect
@Component
public class AspectDemo {
    @Before("execution(* com.wpre.springaop.controller.*.*(..))")
    public void doBefore() {
        log.info("AspectDemo 执行 doBefore");
    }

    @After("execution(* com.wpre.springaop.controller.*.*(..))")
    public void doAfter() {
        log.info("AspectDemo 执行 doAfter");
    }

    @AfterThrowing("execution(* com.wpre.springaop.controller.*.*(..))")
    public void doAfterThrowing() {
        log.info("AspectDemo 执行 doAfterThrowing");
    }

    @AfterReturning("execution(* com.wpre.springaop.controller.*.*(..))")
    public void doAfterReturning() {
        log.info("AspectDemo 执行 doAfterReturning");
    }

    @Around("execution(* com.wpre.springaop.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint pjt) throws Throwable {
        log.info("AspectDemo 执行 doAround前");
        Object proceed = pjt.proceed();
        log.info("AspectDemo 执行 doAround后");
        return proceed;
    }

}

切点表达式

常见的切点表达式有两种:

1、execution(…):根据方法签名匹配

2、@annotation(…):根据注解匹配

execution表达式

语法:

execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)

在这里插入图片描述

例如

1、TestController下的public修饰,返回类型为String,方法名为t1的无参数方法

execution(public String com.example.demo.controller.TestController.t1())

2、省略访问修饰符(private不生效)

execution(public String com.example.demo.controller.TestController.t1())

3、匹配所有的返回类型

execution(* com.example.demo.controller.TestController.t1())

4、匹配TestController下的所有的无参方法

execution(* com.example.demo.controller.TestController.*())

5、匹配TestController下的所有方法

execution(* com.example.demo.controller.TestController.*(..))

6、匹配controller下的所有的类的所有方法

execution(* String com.example.demo.controller.*.*(..))

7、匹配所有包下的TestController

execution(* com..TestController.*(..))

8、匹配com.example.demo包下,所有类的所有方法

execution(* com.example.demo..*(..))

@annotation

实现:自定义一个注解,只要加了这个注解,就能统计方法执行的时间

1、定义一个注解

在这里插入图片描述

2、自定义注解

@Retention(RetentionPolicy.RUNTIME)//表示注解的生命周期
@Target({ElementType.METHOD})//TYPE表示类注解,能加在类上,也能加在方法上;METHOD表示方法注解,只能加在方法上
public @interface TimeRecord {

}

TimeRecordAspect:统计方法执行的时间

@Slf4j
@Aspect
@Component
//统计方法执行的时间
public class TimeRecordAspect {
    @Around("@annotation(com.wpre.springaop.aspect.TimeRecord)")
    public Object timeRecord(ProceedingJoinPoint pjt) throws Throwable {
        //记录开始时间
        long start = System.currentTimeMillis();

        //执行目标方法
        Object ret = pjt.proceed();

        //记录结束时间
        long end = System.currentTimeMillis();

        log.info("{}耗时:{}ms", pjt.getSignature(), end - start);

        return ret;
    }
}

如果想让所有加了RequestMapping的都实现统计时间的功能,只需要把@Around注解改成如下的内容:

@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")

PointCut的使用

定义:

@Pointcut("execution(* com.wpre.springaop.controller.*.*(..))")
public void pt() {
}

使用:

@Before("pt()")
public void doBefore() {
    log.info("AspectDemo 执行 doBefore");
}

在其他类中使用,需要加上路径,如果切点是private则其他类无法使用

@Before("com.wpre.springaop.aspect.AspectDemo.pt()")
public void doBefore() {
    log.info("AspectDemo 执行 doBefore");
}

切点类优先级:按照类名的首字母顺序

通过@Order注解可以修改优先级,数字越大优先级越低

@Order(1)
public class AspectDemo {
	//.................
}

面试题

1、什么是Spring AOP

2、AOP实现方式

基于代理实现、基于注解实现(自定义注解)、基于配置(< aop:config >)

3、AOP原理

JDK动态代理:Spring AOP可以使用JDK动态代理来创建代理对象,这要求目标对象实现一个接口。

CGLIB代理:当目标对象没有实现接口时,Spring AOP可以使用CGLIB库来创建代理对象。

4、Spring用的哪种代理方式?

Spring的代理方式:

ProxyTargetClass 目标对象 代理方式
false 实现了接口 jdk代理
false 未实现接口 cglib代理
true 实现了接口 cglib代理
true 未实现接口 cglib代理

SpringBoot的代理方式:

从2.x版本开始,默认使用cglib