likes
comments
collection
share

Spring AOP之@Aspect注解学习总结

作者站长头像
站长
· 阅读数 31

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对象。


以上这些注解标注的方法的执行顺序如下:

Spring AOP之@Aspect注解学习总结

AOP Ordering 排序

如果存在多个由@Aspect注解标注的类,在没有指定排序默认是自然排序的方式执行相关的方法,可以使用@Order注解或者实现Ordered接口,对切面类的以指定的顺序进行排序。

以下有两个切面类:OrderAspectFirstOrderAspectSecond

/**  
 * 使用 @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

Spring AOP之@Aspect注解学习总结

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 AOP之@Aspect注解学习总结

Spring AOP之@Aspect注解学习总结

下图在spring-configuration-metadata.json元数据中,有兴趣可以了解Spring Boot的自动配置

Spring AOP之@Aspect注解学习总结

如果不想自动配置,那么可以在application.yml中配置以下配置:

spring:  
  aop:  
    auto: false  # 默认true,表示添加 @EnableAspectJAutoProxy注解
    proxy-target-class: false # 默认为true 启动CGLIB动态代理

一般情况通过默认DefaultAopProxyFactory#createAopProxy生成相关类的代理类。

Spring AOP之@Aspect注解学习总结

  • JDK动态代理:通过反射类Proxy以及InvocationHandler接口实现的。动态代理的类必须实现一个接口。
  • CGLIB动态代理:基于子类的代理。采用ASM字节码生成框架方法进行代理,比Java反射效率较高。但是不能对声明为final的方法进行代理。

Github

java-skill-learn/aop-spring-boot-study/README.md at main · DawnSilverGravel/java-skill-learn (github.com)

Spring AOP之@Aspect注解学习总结

参考文档

Core Technologies (spring.io)

转载自:https://juejin.cn/post/7361255930647953408
评论
请登录