简介
官方介绍:
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