快速入门
引入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