@Aspect

一.定义

AOP是指在程序运行期间动态地将某段代码切入到指定位置并运行的编程方式。

AOP详解可参考:https://blog.csdn.net/javazejian/article/details/56267036

二.切点表达式

切点表达式详解可参考:https://www.cnblogs.com/zhangxufeng/p/9160869.html

2-1execution表达式

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型、类名、方法名和参数名等与方法相关的部件。并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法:

execution([visibility-pattern] ret-type-pattern [declaring-type-pattern]method-name-pattern(param-pattern) [throws-pattern])
  • 中括号[]包括的内容表示在写切点表达式时可以省略。而其他的不能省略,但可用通配符代替。
  • visibility-pattern:方法的可见性,如public, protected;
  • ret-type-pattern:方法的返回值类型,如void, Integer;
  • declaring-type-pattern:方法所在类的全路径(可以包括类名),如com.myaop.MathCalculator;
  • method-name-pattern:方法名,如division();
  • param-pattern:方法的参数类型,如java.lang.String;
  • throws-pattern:方法抛出的异常类型,如java.lang.Exception。

如下是一个使用execution表达式的例子:

execution(public * com.myaop.MathCalculator.division(Integer,..))

这个切点表达式将会匹配:使用public修饰、返回值为任意类型、是com.myaop.MathCalculator类中的名为division的方法、这个方法可以有多个参数,但第一个参数必须是Integer类型(int不匹配)。

2-2通配符

上述示例中使用了..通配符,通配符主要有两种:

  • *通配符:主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
    • 如表达式:
      execution(* com.myaop.MathCalculator.*())

      返回值为任意类型、在com.myaop.MathCalculator类中、无参的方法。

    • 如表达式:
      execution(* com.myaop.Math*.*())

      返回值为任意类型、在com.myaop.Math为前缀的类中、无参的方法。

  • ..通配符:表示0个或多个项,主要用于declaring-type-pattern和param-pattern中。如果用于declaring-type-pattern中,则表示匹配当前包及其子包;如果用于param-pattern中,则表示匹配0个或多个参数。
    • 如表达式:
      execution(* com..divi*(..))

      返回值为任意类型、在com包及其子包下所有以divi开头、参数任意的方法。

    • 特别注意:如果..用于declaring-type-pattern中,在表示匹配当前包及其子包的同时,还相当于在匹配的declaring-type-pattern结果后再加了一个分隔符(.)。如果将MathCalculator类放在包路径com.myaop.business下,则有:
    • 上图中表达式表示为:返回值为任意类型,在包com包及其子包下的所有类的以divi开头、参数任意的方法。

三.使用切面

3-1导入AOP依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>

具体使用时还需引进spring-context的相关依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

3-2创建业务类

创建业务类(MathCalculator)。

package com.myaop;

import org.springframework.stereotype.Component;

@Component
public class MathCalculator {
    public int division(int dividend, int divisor) {
        System.out.println("--执行division方法");
        int result = dividend / divisor;
        return result;
    }
}

3-3创建切面类

package com.myaop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
//Aspect注解只是用来告诉容器这是一个切面类,但并不能将此类作为一个组件添加进容器中
@Aspect
public class MyAspect {

    //定义切点
    @Pointcut("execution(int com.myaop.MathCalculator.*(..))")
    public void pointCut() {
    }

    //前置通知:在目标方法执行前执行
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("before 方法" + methodName);
    }

    //后置通知:在目标方法执行后执行。无论目标方法是否正常执行(后置通知无论如何都会被执行)
    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("after 方法" + methodName);
    }

    /**
     * 环绕通知:
     * 注意:
     * 1.joinPoint.proceed()执行后的结果需要返回出去。
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("around 方法" + methodName + ",proceed()前的代码块执行");
        Object result = joinPoint.proceed();
        System.out.println("around 方法" + methodName + ",proceed()后的代码块执行");
        return result;
    }

    /**
     * 返回通知:在目标方法正常执行并返回后执行
     * 注意:
     * 1.注解中的value参数需要放在最前面
     */
    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("afterReturning 方法" + methodName);
    }

    /**
     * 异常通知:在目标方法执行过程中出现异常且方法自动结束后执行
     * 注意:
     * 1.如果目标方法出现异常但是被方法中的try...catch捕获了而没有抛出,则异常通知不会执行
     * 2.注解中的value参数需放在最前面。
     */
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("afterThrowing 方法" + methodName);
    }
}

3-4创建配置类

通过配置类将切面和业务类作为组件添加到容器中,并通过@EnableAspectJAutoProxy注解开启切面功能。

package com.myaop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(value = "com.myaop")
@EnableAspectJAutoProxy
public class MyConfiguration {
}

3-5测试切面功能

package com.myaop;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        MathCalculator mathCalculator = context.getBean("mathCalculator", MathCalculator.class);
        mathCalculator.division(1, 1);
    }
}

当mathCalculator.division(1,1)时,运行结果为:

around 方法division,proceed()前的代码块执行
before 方法division
---执行 方法division
around 方法division,proceed()后的代码块执行
after 方法division
afterReturning 方法division

当mathCalculator.division(1,0)时(目标方法会出现除数为0的异常),运行结果为:

around 方法division,proceed()前的代码块执行
before 方法division
---执行 方法division
after 方法division
afterThrowing 方法division
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.myaop.MathCalculator.division(MathCalculator.java:9)
	at com.myaop.MathCalculator$$FastClassBySpringCGLIB$$c5dccaaf.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
	at com.myaop.MyAspect.around(MyAspect.java:41)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:55)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
	at com.myaop.MathCalculator$$EnhancerBySpringCGLIB$$cf10f693.division(<generated>)
	at com.myaop.Main.main(Main.java:9)

四.同一个切面内不同通知执行的顺序

4-1spring-context.5.2.4.RELEASE

当使用如下依赖版本时:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>

遵循如下规律:

public Object aop(Method method,Object object) {
    try {
        try {
            /*doAround start*/
            doBefore();
            method.invoke(object);
            /*doAround end*/
        }catch(){
        	throw ...;
        } 
        finally {
            doAfter();
        }
        doAfterReturning();
    } catch (Exception e) {
        doAfterThrowing();
    }
}

doAround start为around通知方法中joinPoint.proceed()之前的代码块;

doAround end为around通知方法中joinPoint.proceed()之后的代码块;

目标方法正常执行的情况下,执行顺序为:@Around @Before 目标方法 @Around @After @AfterReturning;

目标方法抛出了异常的情况下,执行顺序为:@Around @Before 目标方法 @After @AfterThrowing。

4-2spring-context.5.2.10.RELEASE

当使用如下依赖版本时(spring-context.5.2.4RELEASE更新为了spring-context.5.2.10.RELEASE):

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>

就不遵循4-1中的规律了。目标方法执行后的通知执行顺序变了。

当mathCalculator.division(1,1)时,运行结果为:

around 方法division,proceed()前的代码块执行
before 方法division
---执行 方法division
afterReturning 方法division
after 方法division
around 方法division,proceed()后的代码块执行

Process finished with exit code 0

当mathCalculator.division(1,0)时(目标方法会出现除数为0的异常),运行结果为:

around 方法division,proceed()前的代码块执行
before 方法division
---执行 方法division
afterThrowing 方法division
after 方法division
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.myaop.MathCalculator.division(MathCalculator.java:9)
	at com.myaop.MathCalculator$$FastClassBySpringCGLIB$$c5dccaaf.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:55)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
	at com.myaop.MyAspect.around(MyAspect.java:41)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
	at com.myaop.MathCalculator$$EnhancerBySpringCGLIB$$adde1634.division(<generated>)
	at com.myaop.Main.main(Main.java:9)

Process finished with exit code 1
posted @ 2022-07-28 16:40  certainTao  阅读(100)  评论(0)    收藏  举报