AOP(Aspect Oriented Programming)面向切面编程通过提供另一种程序结构思维对OOP(Object Oriented Programming)面向对象编程进行了补充。OOP模块关键点在于类(Class),而AOP模块的关键点在切面(Aspect)。
Spring AOP 主要应用于:
- 提供声明性企业服务,最重要的这类服务是声明式事务管理。
- 让用户实现自定义切面,用AOP补充对OOP的使用。如对一些公共逻辑进行抽离复用:日志记录、监控方法运行时间(性能监控)、异常处理、缓存优化、权限控制等。
POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP相关概念
- 切面(Aspect): 跨多个类的关注点的模块化。使用
@Aspect
标注的类,或基于Schema-Based模式的常规类。 - 连接点(Join point):程序执行执行的一个点。
JoinPoint
相关接口,有@Around
标注的方法才可以使用ProceedingJoinPoint
类 - 通知(Advice):切面在特定连接点执行的操作。相关注解
@Around
、@Before
、@AfterReturning
、@AfterThrowing
、@After
。 - 切入点(Pointcut):匹配连接点的谓词。
@Pointcut
与Advice相关注解可搭配使用 - 编织(Weaving):将切面与其他其他应用程序类型或者对象进行链接,以此来创建通知对象。可以在编译时(例如使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP在运行时执行编织。
AOP Advice通知
定义一个切面类,并声明一个切入点
@Aspect
@Component
@Order(1)
public class StudyAspect {
/**
* com.silvergravel.study.service包下的所有公共print方法
*/
@Pointcut("execution(public * com.silvergravel.study.service.*.*print(..))")
private void pointcut() {
}
}
前置通知(Before Advice):在连接点执行但不能阻止执行流进行到连接点的通知。
/**
* "pointcut()为@Pointcut标注的方法
*/
@Before("pointcut()")
// @Before("execution(public * com.silvergravel.study.service.*.*print(..))")
public void before(JoinPoint joinPoint) throws Throwable {
System.out.println("Before");
}
后置返回通知(After Returning Advice):在连接点正常执行后运行的通知,如果发生异常则不运行该通知。
@AfterReturning(value = "pointcut()",returning = "retVal")
public void afterReturning(JoinPoint joinPoint,Object retVal) throws Throwable {
System.out.println("返回值:"+retVal);
System.out.println("AfterReturning");
}
后置异常通知(After Throwing Advice):在连接点执行发生异常后运行的通知。
@AfterThrowing(value = "pointcut()",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception) throws Throwable {
System.out.println("AfterThrowing");
}
后置最终通知(After Advice):跟
finally
一样,不管连接点正常执行还是执行异常,该通知都会运行。
@After("pointcut()")
public void afterC(JoinPoint joinPoint) throws Throwable {
System.out.println("After C");
}
环绕通知(Around Advice):可以在方法调用之前和之后执行自定义行为,如执行异常或者值不对可编写重试机制。(推荐使用)
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around Before");
Object proceed = joinPoint.proceed();
System.out.println("Around After");
return proceed;
}
joinPoint.proceed()
之前的代码为前置环绕,之后的代码为后置环绕。环绕通知的方法是一个Object返回类型的方法。参数需要ProceedingJoinPoint
对象。
以上这些注解标注的方法的执行顺序如下:
AOP Ordering 排序
如果存在多个由@Aspect
注解标注的类,在没有指定排序默认是自然排序的方式执行相关的方法,可以使用@Order
注解或者实现Ordered
接口,对切面类的以指定的顺序进行排序。
以下有两个切面类:OrderAspectFirst
、OrderAspectSecond
/**
* 使用 @Order(100)使得First优先级低于Second,常规First优于Second
* @author DawnStar
* @since : 2023/12/19
*/@Aspect
@Component
@Order(100)
@Slf4j
public class OrderAspectFirst {
@Pointcut("execution(public * com.silvergravel.study.service.*.*print(..))")
private void pointcut() {
}
/**
* "pointcut()为@Pointcut标注的方法
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint) throws Throwable {
log.info("Before First");
}
@AfterReturning(value = "pointcut()", returning = "retVal")
public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable {
log.info("返回值:" + retVal);
log.info("AfterReturning First");
}
@AfterThrowing(value = "pointcut()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) throws Throwable {
log.info("AfterThrowing First: {}", exception.getMessage());
}
@After("pointcut()")
public void after(JoinPoint joinPoint) throws Throwable {
log.info("After First");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around Before First");
Object proceed = joinPoint.proceed();
log.info("Around After First");
return proceed;
} }
@Aspect
@Component
@Slf4j
public class OrderAspectSecond implements Ordered {
@Pointcut("execution(public * com.silvergravel.study.service.*.*print(..))")
private void pointcut() {
}
/**
* "pointcut()为@Pointcut标注的方法
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint) throws Throwable {
log.info("Before Second");
}
@AfterReturning(value = "pointcut()", returning = "retVal")
public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable {
log.info("返回值:{}", retVal);
log.info("AfterReturning Second");
}
@AfterThrowing(value = "pointcut()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) throws Throwable {
log.error("AfterThrowing Second: {}", exception.getMessage());
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around Before Second");
Object proceed = joinPoint.proceed();
log.info("Around After Second");
return proceed;
}
@After("pointcut()")
public void after(JoinPoint joinPoint) throws Throwable {
log.info("After Second");
}
@Override
public int getOrder() {
return -100;
}}
根据自然顺序:OrderAspectFirst
顺序在OrderAspectSecond
之前,所以下图,但是两者都是加上了顺序并指定OrderAspectSecond
优先级高于OrderAspectFirst
,所以执行顺序就下图所示,紫色为OrderAspectSecond
AOP Pointcut表达式
Spring AOP 是完全由Java实现的,其目的是提供AOP实现和Spring IoC之间的紧密集成。所以没有提供最完整的AOP实现。
支持的切入点指示符
execution:用于匹配方法执行连接点。Spring AOP时使用的主要切入点指示符。
within:限制与某些类型中的连接点的匹配。
this:限制与连接点(使用 Spring AOP 时方法的执行)的匹配,其中 bean 引用(Spring AOP 代理)是给定类型的实例
target:限制与连接点(使用 Spring AOP 时方法的执行)的匹配,其中目标对象(被代理的应用程序对象)是给定类型的实例
args:限制与连接点(使用 Spring AOP 时方法的执行)的匹配,其中参数是给定类型的实例。
@target:限制与连接点的匹配,其中执行对象的类具有给定类型的注解。
@within:限制与具有给定注释的类型中的连接点匹配。
@args:限制与连接点的匹配(使用 Spring AOP 时方法的执行) ,其中传递的实际参数的运行时类型具有给定类型的注释
@annotation:限制匹配到连接点的主题所在的连接点有给定的注解
bean:指定Bean实例
其中最为常用的就是execution
方法级别的表达式,官方给予表示式如下:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
即:访问修饰符【可选】 方法签名 声明类型【即哪个包下,可选】 方法名(方法参数) 抛出异常【可选】
所有模式都遵循以下匹配规则:
*
:表示匹配全部或者部分参数(*)
:在参数匹配中,表示匹配一个参数,(*
,String)匹配一个使用两个参数的方法,其中第二个参数必须为String类型..
:表示零或多个参数,(..)
同样遵守该规则()
:匹配一个不带参数的方法
示例1:execution(public boolean com.silvergravel.study.service..checkUser(String,String))
表示匹配:com.silvergravel.study.service
包以及子包所有checkUser(String,String)
,返回签名为boolean
类型的public修饰的连接点。
示例2:execution(* com.silvergravel.study.controller.UserController.*(..)
表示匹配com.silvergravel.study.controller.UserController
类中的所有方法
AOP Proxy代理
Spring AOP可以使用CGLIB动态代理和JDK动态代理。
在@EnableAspectJAutoProxy
中,默认是使用了JDK的动态代理,如果在POM文件中使用spring-boot-starter-aop
配置,那么就无需在自己程序的配置类上添加这个注解,AOP的自动配置类会默认配置该注解,并且默认启用CGLIB动态代理,如下几张图所示:
下图在spring-configuration-metadata.json
元数据中,有兴趣可以了解Spring Boot的自动配置
如果不想自动配置,那么可以在application.yml
中配置以下配置:
spring:
aop:
auto: false # 默认true,表示添加 @EnableAspectJAutoProxy注解
proxy-target-class: false # 默认为true 启动CGLIB动态代理
一般情况通过默认DefaultAopProxyFactory#createAopProxy
生成相关类的代理类。
- JDK动态代理:通过反射类Proxy以及InvocationHandler接口实现的。动态代理的类必须实现一个接口。
- CGLIB动态代理:基于子类的代理。采用ASM字节码生成框架方法进行代理,比Java反射效率较高。但是不能对声明为final的方法进行代理。