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;
}
}
结果:
本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/17660459.html