《深入浅出Spring》SpringAOP 详解AspectJ

AspectJ是什么?

AspectJ是一个面向切面的框架,是目前最好用,最方便的AOP框架,和spring中的aop可以集成在一起使用,通过Aspectj提供的一些功能实现aop代理变得非常方便。

AspectJ使用步骤

1.创建一个类,使用@Aspect标注
2.@Aspect标注的类中,通过@Pointcut定义切入点
3.@Aspect标注的类中,通过AspectJ提供的一些通知相关的注解定义通知
4.使用AspectJProxyFactory结合@Ascpect标注的类,来生成代理对象

栗子:

public class Service1 {
    public void m1() {
        System.out.println("我是 m1 方法");
    }
    public void m2() {
        System.out.println(10 / 0);
        System.out.println("我是 m2 方法");
    }
}

通过AspectJ来对Service1进行增强,来2个通知,一个前置通知,一个异常通知,这2个通知需要对Service1中的所有方法生效,实现如下:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
//@1:这个类需要使用@Aspect进行标注
@Aspect
public class Aspect1 {
    //@2:定义了一个切入点,可以匹配Service1中所有方法
    @Pointcut("execution(* Service1.*(..))")
    public void pointcut1() {
    }
    //@3:定义了一个前置通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效
    @Before(value = "pointcut1()")
    public void before(JoinPoint joinPoint) {
        //输出连接点的信息
        System.out.println("前置通知," + joinPoint);
    }
    //@4:定义了一个异常通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效
    @AfterThrowing(value = "pointcut1()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        //发生异常之后输出异常信息
        System.out.println(joinPoint + ",发生异常:" + e.getMessage());
    }
}

@1:类上使用@Aspect标注

@2:通过@Pointcut注解标注在方法上面,用来定义切入点

@3:使用@Before标注在方法上面,定义了一个前置通知,通过value引用了上面已经定义的切入点,表示这个通知会对Service1中的所有方法生效,在通知中可以通过这个类名.方法名()引用@Pointcut定义的切入点,表示这个通知对这些切入点有效,若@Before和@Pointcut在一个类的时候,直接通过方法名()引用当前类中定义的切入点

@4:这个使用@AfterThrowing定义了一个异常通知,也是对通过value引用了上面已经定义的切入点,表示这个通知会对Service1中的所有方法生效,若Service1中的方法抛出了Exception类型的异常,都会回调afterThrowing方法。

public void test1() {
        try {
            //对应目标对象
            Service1 target = new Service1();
            //创建AspectJProxyFactory对象
            AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
            //设置被代理的目标对象
            proxyFactory.setTarget(target);
            //设置标注了@Aspect注解的类
            proxyFactory.addAspect(Aspect1.class);
            //生成代理对象
            Service1 proxy = proxyFactory.getProxy();
            //使用代理对象
            proxy.m1();
            proxy.m2();
        } catch (Exception e) {
        }
    }
}

运行输出

前置通知,execution(void Service1.m1()) 我是
m1 方法 前置通知,execution(void
Service1.m2()) execution(void
Service1.m2()),发生异常:/ by zero

AspectJProxyFactory原理

@Aspect标注的类上,这个类中,可以通过通过@Pointcut来定义切入点,可以通过@Before、@Around、@After、@AfterRunning、@AfterThrowing标注在方法上来定义通知,定义好了之后,将@Aspect标注的这个类交给AspectJProxyFactory来解析生成Advisor链,进而结合目标对象一起来生成代理对象,大家可以去看一下源码,比较简单,这里就不多解释了。

本文的重点在@Aspect标注的类上,@Aspect中有2个关键点比较重要

  • @Pointcut:标注在方法上,用来定义切入点,有11种用法,本文主要讲解这11种用法。
  • @Aspect类中定义通知:可以通过@Before、@Around、@After、@AfterRunning、@AfterThrowing标注在方法上来定义通知,

@Pointcut的12种用法

作用
用来标注在方法上来定义切入点。

定义
格式:@ 注解(value=“表达标签 (表达式格式)”)

如:

@Pointcut("execution(* .Service1.*(..))")

表达式标签(10种)

  • execution:用于匹配方法执行的连接点
  • within:用于匹配指定类型内的方法执行
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口的类型匹配
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口的类型匹配
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
    -@within:用于匹配所以持有指定注解类型内的方法
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
  • @annotation:用于匹配当前执行方法持有指定注解的方法
  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

1、execution

使用execution(方法表达式)匹配方法执行。

execution格式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

  • 其中带 ?号的 modifiers-pattern?,declaring-type-pattern?,throws-pattern?是可选项
  • ret-type-pattern,name-pattern, parameters-pattern是必选项
  • modifier-pattern? 修饰符匹配,如public 表示匹配公有方法
  • ret-type-pattern 返回值匹配,* 表示任何返回值,全路径的类名等
  • declaring-type-pattern? 类路径匹配
    -name-pattern 方法名匹配, 代表所有,set,代表以set开头的所有方法
  • (param-pattern) 参数匹配,指定方法参数(声明的类型),(…)代表所有参数,(*,String)代表第一个参数为任何值,第二个为String类型,(…,String)代表最后一个参数是String类型
  • throws-pattern? 异常类型匹配

举例说明

表达式 描述
public .(…) 任何公共方法的执行
* com.javacode…IPointcutService.*() com.javacode包及所有子包下IPointcutService接口中的任何无参方法
* com.javacode…IPointcutService.*() com.javacode包及所有子包下IPointcutService接口中的任何无参方法
* com.javacode….(…) com.javacode包及所有子包下任何类的任何方法
* com.javacode…IPointcutService.() com.javacode包及所有子包下IPointcutService接口的任何只有一个参数方法
* com.javacode…IPointcutService+.*() com.javacode包及所有子包下IPointcutService接口及子类型的的任何无参方法
* Service1.*(String) 匹配Service1中只有1个参数的且参数类型是String的方法
* Service1.(,String) 匹配Service1中只有2个参数的且第二个参数类型是String的方法
* Service1.*(…,String) 匹配Service1中最后1个参数类型是String的方法

类型匹配语法
很多地方会按照类型的匹配,先来说一下类型匹配的语法。

首先让我们来了解下AspectJ类型匹配的通配符:

  • *:匹配任何数量字符
  • …:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数(0个或者多个参数)
  • +:匹配指定类型及其子类型;仅能作为后缀放在类型模式后边
表达式 说明
java.lang.String 匹配String类型
java.*.String 匹配java包下的任何一级子包下的String类型,如匹配java.lang.String,但不匹配java.lang.ss.String
java…* 匹配java包及任何子包下的任何类型,如匹配java.lang.String、java.lang.annotation.Annotation
java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型
java.lang.Number+ 匹配java.lang包下的任何Number类型及其子类型,如匹配java.lang.Number,也匹配java.lang.Integer、java.math.BigInteger

2、within

用法
within(类型表达式):目标对象target的类型是否和within中指定的类型匹配

表达式 描述
within(com.javacode…*) com.javacode包及子包下的任何方法执行
within(com.javacode…IPointcutService+) com.javacode包或所有子包下IPointcutService类型及子类型的任何方法
within(com.javacode.Service1) 匹配类com.javacode.Service1中定义的所有方法,不包含其子类中的方法

匹配原则

target.getClass().equals(within表达式中指定的类型)

栗子:

@Pointcut("within(C1+)") 匹配C1类型
@Pointcut("within(C2)") 匹配C2类型

3、this

用法
this(类型全限定名):通过aop创建的代理对象的类型是否和this中指定的类型匹配;注意判断的目标是代理对象;this中使用的表达式必须是类型全限定名,不支持通配符。

匹配原则

如:this(x),则代理对象proxy满足下面条件时会匹配
x.getClass().isAssignableFrom(proxy.getClass());

栗子:


//匹配proxy是Service3类型的所有方法
    @Pointcut("this(Service3)")	

4、target

用法
target(类型全限定名):判断目标对象的类型是否和指定的类型匹配;注意判断的是目标对象的类型;表达式必须是类型全限定名,不支持通配符。

匹配原则
如:target(x),则目标对象target满足下面条件时会匹配
x.getClass().isAssignableFrom(target.getClass());

栗子:

//目标类型必须是Service3类型的
    @Pointcut("target(Service3)")
    public void pc() {
    }

within、this、target对比

表达式标签 判断的对象 判断规则(x:指表达式中指定的类型)
within target对象 target.getClass().equals(表达式中指定的类型)
this proxy对象 x.getClass().isAssignableFrom(proxy.getClass());
target target对象 x.getClass().isAssignableFrom(target.getClass());

5、args

用法

args(参数类型列表)匹配当前执行的方法传入的参数是否为args中指定的类型;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,不支持通配符;args属于动态切入点,也就是执行方法的时候进行判断的,这种切入点开销非常大,非特殊情况最好不要使用。

举例说明

表达式 描述
args(String) 匹配只有一个参数且传入的参数类型是String类型的方法
args(*,String) 匹配只有2个参数的且第2个参数类型是String的方法
args(…,String) 匹配最后1个参数类型是String的方法

栗子:

//匹配只有1个参数其类型是String类型的
    @Pointcut("args(String)")

args会在调用的过程中对参数实际的类型进行匹配,比较耗时,慎用。

6、@within

用法

@within(注解类型):匹配指定的注解内定义的方法。

匹配规则
调用目标方法的时候,通过java中Method.getDeclaringClass()获取当前的方法是哪个类中定义的,然后会看这个类上是否有指定的注解。
被调用的目标方法Method对象.getDeclaringClass().getAnnotation(within中指定的注解类型) != null

栗子:

/**
     * 定义目标方法的类上有Ann9注解
     */
    @Pointcut("@within(Ann9)")
    public void pc() {
    }
  • 目标对象上有@within中指定的注解,这种情况时,目标对象的所有方法都会被拦截。
  • 定义注解时未使用@Inherited,说明子类无法继承父类上的注解,子类重写父类方法不会拦截父类方法
  • 在注解的定义上面加上@Inherited,此时子类可以继承父类的注解,子类重写父类方法,会拦截子类和父类的方法

7、@target

用法
@target(注解类型):判断目标对象target类型上是否有指定的注解;@target中注解类型也必须是全限定类型名。

匹配规则
target.class.getAnnotation(指定的注解类型) != null
2种情况可以匹配

注解直接标注在目标类上
注解标注在父类上,但是注解必须是可以继承的,即定义注解的时候,需要使用@Inherited标注

  • 注解直接标注在目标类上,这种情况目标类会被匹配到。
//@1:目标类上有@Ann1注解
    @Pointcut("@target(Ann1)")
    public void pc() {
    }
  • 注解标注在父类上,注解上没有@Inherited,这种情况下,目标类无法匹配到
 /**
     * 匹配目标类上有Ann7注解
     */
    @Pointcut("@target(Ann7)")
    public void pc() {
    }

8、@args

用法
@args(注解类型):方法参数所属的类上有指定的注解;注意不是参数上有指定的注解,而是参数类型的类上有指定的注解。

案例1

  • @Pointcut(“@args(Ann8)”):匹配方法只有一个参数,并且参数所属的类上有Ann8注解

可以匹配下面的代码,m1方法的第一个参数类型是Car类型,Car类型上有注解Ann8

@Ann8
class Car {
}
public void m1(Car car) {
    System.out.println("我是m1");
}
  • @Pointcut(“@args(*,Ann8)”):匹配方法只有2个参数,且第2个参数所属的类型上有Ann8注解

可以匹配下面代码

@Ann8
class Car {
}
public void m1(String name,Car car) {
    System.out.println("我是m1");
}
  • @Pointcut(“@args(…,Ann8)”):匹配参数数量大于等于1,且最后一个参数所属的类型上有Ann8注解
  • @Pointcut(“@args(*,Ann8,…)”):匹配参数数量大于等于2,且第2个参数所属的类型上有Ann8注解
  • @Pointcut(“@args(…,Ann8,*)”):匹配参数数量大于等于2,且倒数第2个参数所属的类型上有Ann8注解

9、@annotation

用法
@annotation(注解类型):匹配被调用的方法上有指定的注解。

@Pointcut("@annotation(Ann12)")
    public void pc() {
    }

10、bean

用法
bean(bean名称):这个用在spring环境中,匹配容器中指定名称的bean。

 //拦截spring容器中名称为beanService2的bean
    @Pointcut("bean(beanService2)")
    public void pc() {
    }

11、reference pointcut

表示引用其他命名切入点。
有时,我们可以将切入专门放在一个类中集中定义。
其他地方可以通过引用的方式引入其他类中定义的切入点。

语法如下:

@Pointcut(“完整包名类名.方法名称()”)

若引用同一个类中定义切入点,包名和类名可以省略,直接通过方法就可以引用。
比如下面,我们可以将所有切入点定义在一个类中

public class AspectPcDefine {
    @Pointcut("bean(bean1)")
    public void pc1() {
    }
    @Pointcut("bean(bean2)")
    public void pc2() {
    }
}
@Aspect
public class Aspect14 {
    @Pointcut("AspectPcDefine.pc1()")
    public void pointcut1() {
    }
    @Pointcut("AspectPcDefine.pc1() ||AspectPcDefine.pc2()")
    public void pointcut2() {
    }
}

12、组合型的pointcut

Pointcut定义时,还可以使用&&、||、!运算符。

  • &&:多个匹配都需要满足
  • ||:多个匹配中只需满足一个
  • !:匹配不满足的情况下
  1. @Pointcut(“bean(bean1) || bean(bean2)”) //匹配bean1或者bean2
  2. @Pointcut(“@target(Ann1) && @Annotation(Ann2)”) //匹配目标类上有Ann1注解并且目标方法上有Ann2注解
  3. @Pointcut(“@target(Ann1) && !@target(Ann2)”) // 匹配目标类上有Ann1注解但是没有Ann2注解

思维导图

《深入浅出Spring》SpringAOP 详解AspectJ_第1张图片

@Aspect中5中通知

@Before:前置通知, 在方法执行之前执行
@Aroud:环绕通知, 围绕着方法执行
@After:后置通知, 在方法执行之后执行
@AfterReturning:返回通知, 在方法返回结果之后执行
@AfterThrowing:异常通知, 在方法抛出异常之后

@Before:前置通知

定义一个前置通知

@Aspect
public class BeforeAspect {
    @Before("execution(* Service1.*(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("我是前置通知!");
    }
}

类上需要使用@Aspect标注
任意方法上使用@Before标注,将这个方法作为前置通知,目标方法被调用之前,会自动回调这个方法
被@Before标注的方法参数可以为空,或者为JoinPoint类型,当为JoinPoint类型时,必须为第一个参数
被@Before标注的方法名称可以随意命名,符合java规范就可以,其他通知也类似

@Before中value的值为切入点表达式,也可以采用引用的方式指定切入点,如:

@Aspect
public class BeforeAspect {
    @Pointcut("execution(* Service1.*(..))")
    public void pc() {
    }
    @Before("BeforeAspect.pc()")
    public void before(JoinPoint joinPoint) {
        System.out.println("我是前置通知!");
    }
}

此时,before方法上面的切入引用了pc方法上面的@Pointcut的值

对应的通知类
@Before通知最后会被解析为下面这个通知类

org.springframework.aop.aspectj.AspectJMethodBeforeAdvice

通知中获取被调方法信息
通知中如果想获取被调用方法的信息,分2种情况

非环绕通知,可以将org.aspectj.lang.JoinPoint作为通知方法的第1个参数,通过这个参数获取被调用方法的信息
如果是环绕通知,可以将org.aspectj.lang.ProceedingJoinPoint作为方法的第1个参数,通过这个参数获取被调用方法的信息

JoinPoint:连接点信息

org.aspectj.lang.JoinPoint

提供访问当前被通知方法的目标对象、代理对象、方法参数等数据:

package org.aspectj.lang;  
import org.aspectj.lang.reflect.SourceLocation;
public interface JoinPoint {  
    String toString();         //连接点所在位置的相关信息  
    String toShortString();     //连接点所在位置的简短相关信息  
    String toLongString();     //连接点所在位置的全部相关信息  
    Object getThis();         //返回AOP代理对象
    Object getTarget();       //返回目标对象  
    Object[] getArgs();       //返回被通知方法参数列表,也就是目前调用目标方法传入的参数  
    Signature getSignature();  //返回当前连接点签名,这个可以用来获取目标方法的详细信息,如方法Method对象等
    SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置  
    String getKind();        //连接点类型  
    StaticPart getStaticPart(); //返回连接点静态部分  
}

ProceedingJoinPoint:环绕通知连接点信息
用于环绕通知,内部主要关注2个方法,一个有参的,一个无参的,用来继续执行拦截器链上的下一个通知。

package org.aspectj.lang;
import org.aspectj.runtime.internal.AroundClosure;
public interface ProceedingJoinPoint extends JoinPoint {
    /**
     * 继续执行下一个通知或者目标方法的调用
     */
    public Object proceed() throws Throwable;
    /**
     * 继续执行下一个通知或者目标方法的调用
     */
    public Object proceed(Object[] args) throws Throwable;
}

Signature:连接点签名信息
注意JoinPoint#getSignature()这个方法,用来获取连接点的签名信息,这个比较重要

Signature getSignature();
通常情况,spring中的aop都是用来对方法进行拦截,所以通常情况下连接点都是一个具体的方法,Signature有个子接口

org.aspectj.lang.reflect.MethodSignature
JoinPoint#getSignature()都可以转换转换为MethodSignature类型,然后可以通过这个接口提供的一些方法来获取被调用的方法的详细信息。

@Around:环绕通知

环绕通知会包裹目标目标方法的执行,可以在通知内部调用ProceedingJoinPoint.process方法继续执行下一个拦截器。
用起来和@Before类似,但是有2点不一样
若需要获取目标方法的信息,需要将ProceedingJoinPoint作为第一个参数
通常使用Object类型作为方法的返回值,返回值也可以为void
特点
环绕通知比较特殊,其他4种类型的通知都可以用环绕通知来实现。

@Aspect
public class AroundAspect3 {
    @Pointcut("execution(*Service1.*(..))")
    public void pc() {
    }
    @Around("AroundAspect3.pc()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取连接点签名
        Signature signature = joinPoint.getSignature();
        //将其转换为方法签名
        MethodSignature methodSignature = (MethodSignature) signature;
        //通过方法签名获取被调用的目标方法
        Method method = methodSignature.getMethod();
        long startTime = System.nanoTime();
        //调用proceed方法,继续调用下一个通知
        Object returnVal = joinPoint.proceed();
        long endTime = System.nanoTime();
        long costTime = endTime - startTime;
        //输出方法信息
        System.out.println(String.format("%s,耗时(纳秒):%s", method.toString(), costTime));
        //返回方法的返回值
        return returnVal;
    }
}

对应的通知类
@Around通知最后会被解析为下面这个通知类

org.springframework.aop.aspectj.AspectJAroundAdvice

@After:后置通知

后置通知,在方法执行之后执行,用法和前置通知类似。

特点 不管目标方法是否有异常,后置通知都会执行 这种通知无法获取方法返回值
可以使用JoinPoint作为方法的第一个参数,用来获取连接点的信息

@Aspect
public class AfterAspect4 {
    @Pointcut("execution(*Service1.*(..))")
    public void pc() {
    }
    @After("AfterAspect4.pc()")
    public void after(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        System.out.println(String.format("%s,执行完毕!", methodSignature.getMethod()));
    }
}

对应的通知类
@After通知最后会被解析为下面这个通知类

org.springframework.aop.aspectj.AspectJAfterAdvice

这个类中有invoke方法,这个方法内部会调用被通知的方法,其内部采用try…finally的方式实现的,所以不管目标方法是否有异常,通知一定会被执行。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        //继续执行下一个拦截器
        return mi.proceed();
    }
    finally {
        //内部通过反射调用被@After标注的方法
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
}

@AfterReturning:返回通知

用法
返回通知,在方法返回结果之后执行。

特点
可以获取到方法的返回值 当目标方法返回异常的时候,这个通知不会被调用,这点和@After通知是有区别的

@Aspect
public class AfterReturningAspect5 {
    @Pointcut("execution(*Service1.*(..))")
    public void pc() {
    }
    @AfterReturning(value = "AfterReturningAspect5.pc()", returning = "retVal")
    public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        System.out.println(String.format("%s返回值:%s", methodSignature.getMethod(), retVal));
    }
}

注意@AfterReturning注解,用到了2个参数

value:用来指定切入点 returning:用来指定返回值对应方法的参数名称,返回值对应方法的第二个参数,名称为retVal

对应的通知类
@AfterReturning通知最后会被解析为下面这个通知类

org.springframework.aop.aspectj.AspectJAfterReturningAdvice

@AfterThrowing:异常通知

用法
在方法抛出异常之后会回调@AfterThrowing标注的方法。

@AfterThrowing标注的方法可以指定异常的类型,当被调用的方法触发该异常及其子类型的异常之后,会触发异常方法的回调。也可以不指定异常类型,此时会匹配所有异常。

未指定异常类型
未指定异常类型,可以匹配所有异常类型,如下

@AfterThrowing(value = "切入点")
public void afterThrowing()

指定异常类型
通过@AfterThrowing的throwing指定参数异常参数名称,我们用方法的第二个参数用来接收异常,第二个参数名称为e,下面的代码,当目标方法发生IllegalArgumentException异常及其子类型异常时,下面的方法会被回调。

@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e)

特点
不论异常是否被异常通知捕获,异常还会继续向外抛出。

@Aspect
public class AfterThrowingAspect6 {
    @Pointcut("execution(*Service1.*(..))")
    public void pc() {
    }
    @AfterThrowing(value = "AfterThrowingAspect6.pc()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        System.out.println(String.format("%s发生异常,异常信息:%s", methodSignature.getMethod(), e.getMessage()));
    }
}

对应的通知类
@AfterThrowing通知最后会被解析为下面这个通知类

org.springframework.aop.aspectj.AspectJAfterThrowingAdvice

来看一下这个类的invoke方法,这个方法是关键

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        //继续调用下一个拦截器链
        return mi.proceed();
    }
    catch (Throwable ex) {
        //判断ex和需要不糊的异常是否匹配
        if (shouldInvokeOnThrowing(ex)) {
            //通过反射调用@AfterThrowing标注的方法
            invokeAdviceMethod(getJoinPointMatch(), null, ex);
        }
        //继续向外抛出异常
        throw ex;
    }
}

几种通知对比

通知类型 执行时间点 可获取返回值 目标方法异常时是否会执行
@Before 方法执行之前
@Around 环绕方法执行 自己控制
@After 方法执行后
@AfterReturning 方法执行后
@AfterThrowing 方法发生异常后

《深入浅出Spring》SpringAOP 详解AspectJ_第2张图片

@EnableAspectJAutoProxy

@EnableAspectJAutoProxy自动为bean创建代理对象

目前为止,上面的文章基本上都是硬编码的方式一个个为目标对象创建代理的,但是,我们使用spring的过程中,可能需要对大量bean创建代理,比如我们需拦截所有的service的方法,打印耗时日志,对大量service bean做权限校验,做事务处理等等,这些功能都可以通过aop的方式来实现,若采用硬编码的方式一个个创建,那是相当难受的事情。
Spring中提供了批量的方式,为容器中符合条件的bean,自动创建代理对象,也就是我们本文要说的@EnableAspectJAutoProxy。

@EnableAspectJAutoProxy可以自动为spring容器中符合条件的bean创建代理对象,@EnableAspectJAutoProxy需要结合@Aspect注解一起使用。用法比较简单,下面我们通过案例来看一下。

@Component
public class CarService {
    public void say() {
        System.out.println("我是CarService");
    }
}
@Component
public class UserService {
    public void say(){
        System.out.println("我是UserService");
    }
}

通过Aspect来定义一个前置通知,需要拦截上面2个bean的所有方法,在方法执行之前输出一行日志

@Component //@1
@Aspect //@2
public class Aspect1 {
    @Pointcut("execution(* com.yuan11.SpringAOP..*(..))") //@3
    public void pc() {
    }
    @Before("com.yuan11.SpringAOP.Aspect1.pc()") //@4
    public void before(JoinPoint joinPoint) {
        System.out.println("我是前置通知,target:" + joinPoint.getTarget()); //5
    }
}

Aspect1中有4个关键信息
@1:使用 @Component 将这个类注册到spring容器;
@2:使用 @Aspect 标注着是一个 AspectJ 来定义通知的配置类;
@3:定义切入点,目前的配置,会拦截test1包及其子包中所有类的所有方法,而CarService和UserService刚好满足,所以会被拦截;
@4:定义一个前置通知,这个通知会对@3定义的切入点起效;
@5:目标方法执行执行,输出一行日志;

下面来一个spring配置类

@ComponentScan //@1
@EnableAspectJAutoProxy //@2
public class MainConfig1 {
}

@1:@ComponentScan 注解的作用会扫描当前包中的类,将标注有 @Component 的类注册到spring容器;
@2:@EnableAspectJAutoProxy 这个注解比较关键,用来启用自动代理的创建,简单点理解:会找到容器中所有标注有@Aspect注解的bean以及Advisor类型的bean,会将他们转换为Advisor集合,spring会通过Advisor集合对容器中满足切入点表达式的bean生成代理对象,整个都是spring容器启动的过程中自动完成的,原理稍后介绍。

 public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig1.class);
        context.refresh();
        UserService userService = context.getBean(UserService.class);
        userService.say();
        CarService carService = context.getBean(CarService.class);
        carService.say();
    }

《深入浅出Spring》SpringAOP 详解AspectJ_第3张图片

通知执行顺序

@EnableAspectJAutoProxy 允许spring容器中通过Advisor 、@Aspect 来定义通知,当spring容器中存在多个Advisor、@Aspect时,组成的拦截器调用链顺序是什么样的呢?在介绍这个之前,我们需要先回顾一下aop中4种通知相关知识。

spring aop中4种通知(Advice)

org.aopalliance.intercept.MethodInterceptor
org.springframework.aop.MethodBeforeAdvice
org.springframework.aop.AfterReturningAdvice
org.springframework.aop.ThrowsAdvice

所有的通知最终都需要转换为MethodInterceptor类型的通知,然后组成一个MethodInterceptor列表,我们称之为方法调用链或者拦截器链,上面列表中后面3通过下面的转换器将其包装为MethodInterceptor类型的通知:

org.springframework.aop.MethodBeforeAdvice -> org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor
org.springframework.aop.AfterReturningAdvice -> org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor
org.springframework.aop.ThrowsAdvice -> org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor

下面我们再来看一下4种通知的用法和执行过程,以方便我们理解其执行顺序。

org.aopalliance.intercept.MethodInterceptor:方法拦截器
方法拦截器,这个比较强大,可以在方法执行前后执行一些增强操作,其他类型的通知最终都会被包装为 MethodInterceptor 来执行。

下面我们自定义一个MethodInterceptor

class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("我是MethodInterceptor start");
        //调用invocation.proceed()执行下一个拦截器
        Object result = invocation.proceed();
        System.out.println("我是MethodInterceptor end");
        //返回结果
        return result;
    }
}

org.springframework.aop.MethodBeforeAdvice:方法前置通知
方法前置通知,可以在方法之前定义增强操作。

下面我们自定义一个MethodBeforeAdvice

class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
        System.out.println("我是MethodBeforeAdvice");
    }
}

MethodBeforeAdvice最终会被包装为MethodBeforeAdviceInterceptor类型,然后放到拦截器链中去执行,通过MethodBeforeAdviceInterceptor代码可以理解MethodBeforeAdvice的执行过程

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
    private final MethodBeforeAdvice advice;
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //调用MethodBeforeAdvice的before方法,执行前置通知
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        //执行下一个拦截器
        return mi.proceed();
    }
}

org.springframework.aop.AfterReturningAdvice:方法返回通知
方法返回通知,用来在方法执行完毕之后执行一些增强操作。

下面我们自定义一个AfterReturningAdvice

class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
        System.out.println("我是AfterReturningAdvice");
    }
}

AfterReturningAdvice最终会被包装为AfterReturningAdviceInterceptor类型,然后放到拦截器链中去执行,通过AfterReturningAdviceInterceptor代码可以理解AfterReturningAdvice的执行过程

public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
    private final AfterReturningAdvice advice;
    public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //执行下一个拦截器,可以获取目标方法的返回结果
        Object retVal = mi.proceed();
        //调用方法返回通知的afterReturning方法,会传入目标方法的返回值等信息
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }
}

org.springframework.aop.ThrowsAdvice:异常通知
当目标方法发生异常时,可以通过 ThrowsAdvice 来指定需要回调的方法,我们在此可以记录一些异常信息,或者将异常信息发送到监控系统等。

下面我们自定义一个ThrowsAdvice

/**
 * 用来定义异常通知
 * 方法名必须是afterThrowing,格式参考下面2种定义
 * 1. public void afterThrowing(Exception ex)
 * 2. public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
 */
class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        System.out.println("我是ThrowsAdvice");
    }
}

ThrowsAdvice最终会被包装为ThrowsAdviceInterceptor类型,然后放到拦截器链中去执行,通过ThrowsAdviceInterceptor代码可以理解ThrowsAdvice的执行过程,ThrowsAdviceInterceptor 构造参数传入一个自定义的 ThrowsAdvice 对象

public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
    private final Object throwsAdvice;
    public ThrowsAdviceInterceptor(Object throwsAdvice) {
        this.throwsAdvice = throwsAdvice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        } catch (Throwable ex) {
            //调用 ThrowsAdvice 中的 afterThrowing 方法来处理异常
            this.throwsAdvice.afterThrowing(。。。。);
            //将异常继续往外抛
            throw ex;
        }
    }
}

拦截器链执行过程
假如目标方法上面有好几个通知,调用目标方法执行,spring会将所有的通知转换得到一个MethodInterceptor列表,然后依次按照下面的方式执行,会先调用第一个拦截器的MethodInterceptor#invoke(MethodInvocation invocation)方法,会传递一个MethodInvocation类型的参数,在此方法中,我们可以调用MethodInvocation#processd方法去执行第二个拦截器,然后依次按照这样的过程执行,到了最后一个MethodInterceptor中,再次调用MethodInvocation#processd时,会调用目标方法。
《深入浅出Spring》SpringAOP 详解AspectJ_第4张图片
4种通知的执行顺序
结合上面的过程,假如目标方法上面依次添加了下面4种通知,我们来分析一下他们的执行过程

结合上面的过程,假如目标方法上面依次添加了下面4种通知,我们来分析一下他们的执行过程

class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("我是MethodInterceptor start");
        //调用invocation.proceed()执行下一个拦截器
        Object result = invocation.proceed();
        System.out.println("我是MethodInterceptor end");
        //返回结果
        return result;
    }
}
class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
        System.out.println("我是MethodBeforeAdvice");
    }
}
class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
        System.out.println("我是AfterReturningAdvice");
    }
}
class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        System.out.println("我是ThrowsAdvice");
    }
}

根据通知的规定,非MethodInterceptor类型的通知,都会被包装为MethodInterceptor类型的,上面除了第一个之外,其他3个都会被转换为MethodInterceptor,转换之后变成了下面这样:

class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("我是MethodInterceptor start");
        //调用mi.proceed()执行下一个拦截器
        Object retVal = mi.proceed();
        System.out.println("我是MethodInterceptor end");
        //返回结果
        return retVal;
    }
}
class MyMethodBeforeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("我是MethodBeforeAdvice");
        //调用mi.proceed()执行下一个拦截器
        Object retVal = mi.proceed();
        return retVal;
    }
}
class MyAfterReturningAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //调用mi.proceed()执行下一个拦截器
        Object retVal = mi.proceed();
        System.out.println("我是AfterReturningAdvice");
        return retVal;
    }
}
class MyThrowsAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            //调用mi.proceed()执行下一个拦截器
            return mi.proceed();
        } catch (Throwable ex) {
            System.out.println("我是ThrowsAdvice");
            throw ex;
        }
    }
}
根据通知链的执行过程,最终变成了下面这样:

System.out.println("我是MethodInterceptor start");
System.out.println("我是MethodBeforeAdvice"); 
Object retVal = null; try {
    retVal = 通过反射调用目标方法获取返回值; } catch (Throwable ex) {
    System.out.println("我是ThrowsAdvice");
    throw ex; 
  } 
System.out.println("我是AfterReturningAdvice"); 
System.out.println("我是MethodInterceptor end"); return retVal;

将上面4个通知用到下面目标对象中

public static class Service3 {
    public String say(String name) {
        return "你好:" + name;
    }
}

执行下面代码生成代理,然后通过代理调用say方法

Service3 target = new Service3();
Service3 proxy = 对target通过aop生成代理对象;
System.out.println(proxy.say("java"));

被4个拦截器链包裹之后,System.out.println(proxy.say(“路人”));执行过程变成了下面这样

System.out.println("我是MethodInterceptor start");
System.out.println("我是MethodBeforeAdvice");
Object retVal = null;
try {
    retVal = target.say("java");
} catch (Throwable ex) {
    System.out.println("我是ThrowsAdvice");
    throw ex;
}
System.out.println("我是AfterReturningAdvice");
System.out.println("我是MethodInterceptor end");
System.out.println(retVal);

最终会输出

我是MethodInterceptor start
我是MethodBeforeAdvice
我是AfterReturningAdvice
我是MethodInterceptor end
你好:java

单个@Aspect中多个通知的执行顺序

@Aspect标注的类中可以使用下面5种注解来定义通知
@Before
@Around
@After
@AfterReturning
@AfterThrowing

@EnableAspectJAutoProxy中为通知指定顺序

@EnableAspectJAutoProxy用在spring环境中,可以通过@Aspect以及Advisor来定义多个通知,当spring容器中有多个@Aspect、Advisor时,他们的顺序是什么样的呢?

我们先看一下如何为@Aspect、自定义Advisor指定顺序。

为@Aspect指定顺序:用@Order注解
需要在@Aspect标注的类上使用@org.springframework.core.annotation.Order注解,值越小,通知的优先级越高。

@Aspect
@Order(1)
public class AspectOrder1{}

多个@Aspect、Advisor排序规则

排序规则
1、在spring容器中获取@Aspect、Advisor类型的所有bean,得到一个列表 list1

2、对list1按照order的值升序排序,得到结果list2

3、然后再对list2中@Aspect类型的bean内部的通知进行排序,规则

@AfterThrowing
@AfterReturning
@After
@Around
@Before
4、最后运行的时候会得到上面排序产生的方法调用链列表去执行。

@EnableAspectJAutoProxy另外2个功能

这个注解还有2个参数,大家看一下下面的注释,比较简单,就不用案例演示了。

public @interface EnableAspectJAutoProxy {
    /**
     * 是否基于类来创建代理,而不是基于接口来创建代理
     * 当为true的时候会使用cglib来直接对目标类创建代理对象
     * 默认为 false:即目标bean如果有接口的会采用jdk动态代理来创建代理对象,没有接口的目标bean,会采用cglib来创建代理对象
     */
    boolean proxyTargetClass() default false;
    /**
     * 是否需要将代理对象暴露在ThreadLocal中,当为true的时候
     * 可以通过org.springframework.aop.framework.AopContext#currentProxy获取当前代理对象
     */
    boolean exposeProxy() default false;
}

@EnableAspectJAutoProxy原理

@EnableAspectJAutoProxy会在spring容器中注册一个bean

org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator是BeanPostProcessor类型的,BeanPostProcessor大家应该比较熟悉了,bean后置处理器,可以在bean声明周期中对bean进行操作,比如对bean生成代理等;而AnnotationAwareAspectJAutoProxyCreator就是对符合条件的bean,自动生成代理对象,源码就这里就不细说了,有兴趣的可以从postProcessAfterInitialization方法看,比较简单。

你可能感兴趣的:(Spring,spring,java,后端)