前置,后置,返回后,异常通知

Spring AOP 五种通知类型

Spring AOP 一共提供5种类型的通知:

  • before 前置通知, 在目标方法运行前, 先去执行 切面类 的 切面方法

  • after 后置通知,在目标方法运行后,再执行切面类的切面方法

  • after return 后置返回通知,在目标方法返回数据以后,再执行切面类的切面方法

  • after throwing 异常通知, 在目标方法抛出异常后, 再执行切面类的切面方法

  • around 环绕通知,最为强around 环绕通知,最为强大,可自定义通知执行时机,可决定目标方法是否运行

其中 after 和 after return 本质上没有区别,其执行的先后顺序由 applicationContext 中的 标签顺序决定

详解前四种通知类型

切面类中的 printExecutionTime, doAfter, doAfterReturning, doAfterThrowing 方法用来分别演示 前置, 后置,后置返回, 异常通知类型。

 //切面类
public class MethodAspect {
    //切面方法, 用于快速扩展额外功能
    //JointPoint, 连接点, 通过连接点可以获取目标方法的信息
    public void printExecutionTime(JoinPoint joinPoint){
        SimpleDateFormat sdt= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSSS");
        String now = sdt.format(new Date());
        String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
        String methodName = joinPoint.getSignature().getName();//获取目标方法的名称
        System.out.println(now +" : "+className+"."+methodName);
        Object[] args = joinPoint.getArgs();//获取目标方法参数
        System.out.println("----> 参数个数" + args.length );
        for (Object arg :args){
            System.out.println("--->参数 : "+ arg);
        }
    }

    // 后置通知方法,在目标方法运行后,再执行切面类的切面方法
    public void doAfter(JoinPoint joinPoint){
        System.out.println("触发后置通知");
    }

    // 后置返回通知方法, 在目标方法返回数据后,再执行切面类的切面方法
    // ret  接受到的目标方法返回值
    public void doAfterReturning(JoinPoint joinPoint, Object ret){
        System.out.println("返回后置通知 :"+ret);
    }

    // 异常通知方法, 在目标方法抛出异常后, 再执行切面类的切面方法
    // th 接受到的异常
    public void doAfterThrowing(JoinPoint joinPoint, Throwable th){
        System.out.println("异常通知 :"+ th);
         System.out.println("异常通知 :"+ th);
     }

}

在 applicationContext.xml 文件中添加四种通知标签: <aop: before>, <aop: after>, <aop: after-returning>, <aop: after-throwing>。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="indi.chester.spring.aop.dao.UserDao"/>
 
    <bean id="userService" class="indi.chester.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
 

    <!-- 切面类 -->
    <bean id="methodAspect" class="indi.chester.spring.aop.aspect.MethodAspect"/>

    <!-- 配置 AOP -->
    <aop:config>
        <!-- pointcut 切点, 使用 execution 表达式描述切面的作用范围  -->

        <!-- pointcut 切点表达式: pubilc 访问修饰符, * 任意返回类型, .. 包通配符, *.* 任意类的任意方法, (..) 参数通配符 -->
        <!-- 作用于 indi.chester 包下所有public类的运行 -->
        <aop:pointcut id="pointcut" expression="execution(public * indi.chester..*.*(..))"/>

        <!-- 定义切面类 -->
        <aop:aspect ref="methodAspect">
            <!-- before 前置通知, 在目标方法运行前, 先去执行 切面类 的 切面方法-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>

            <!-- after 后置通知,在目标方法运行后,再执行切面类的切面方法,after和after-returning 的运行顺序是根据配置文件的先后顺序-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>

            <!-- after-returning 后置返回通知,在目标方法返回数据以后,再执行切面类的切面方法,after和after-returning 的运行顺序是根据配置文件的先后顺序-->
            <aop:after-returning method="doAfterReturning"  returning="ret" pointcut-ref="pointcut"/>

            <!-- after-throwing 异常通知, 在目标方法抛出异常后, 再执行切面类的切面方法,after和after-throwing 的运行顺序是根据配置文件的先后顺序-->
            <aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

测试一下:

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser();
        userService.generateRandomPassword("MD5", 16);
    }

}

运行结果:

2022-02-18 22:47:38 0495 : indi.chester.spring.aop.service.UserService.createUser
----> 参数个数0
执行创建用户业务逻辑
2022-02-18 22:47:38 0513 : indi.chester.spring.aop.dao.UserDao.insert
----> 参数个数0
新增用户数据
触发后置通知
返回后置通知 :null
触发后置通知
返回后置通知 :null
2022-02-18 22:47:38 0521 : indi.chester.spring.aop.service.UserService.generateRandomPassword
----> 参数个数2
--->参数 : MD5
--->参数 : 16
按MD5方式生成16位随机密码
触发后置通知
返回后置通知 :Zxcquei1

为了创造出一个异常,需要修改 UserService 类中的 createUser 方法

public class UserService {
    private UserDao userDao;

    public void createUser(){
        //为了演示 <aop:after-throwing>
        if(1==1){
            throw new RuntimeException("用户已存在");
        }
        System.out.println("执行创建用户业务逻辑");
        userDao.insert();
    }

    public String generateRandomPassword(String type , Integer length){
        System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
        return "Zxcquei1";
    }

    // getter setter 方法省略
}

再次运行, 发现捕获到了异常:

2022-02-18 22:49:10 0757 : indi.chester.spring.aop.service.UserService.createUser
----> 参数个数0
触发后置通知
异常通知 :java.lang.RuntimeException: 用户已存在
Exception in thread "main" java.lang.RuntimeException: 用户已存在
	at indi.chester.spring.aop.service.UserService.createUser(UserService.java:11)
	at indi.chester.spring.aop.service.UserService$$FastClassBySpringCGLIB$$af5d8448.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.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.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.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.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.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 indi.chester.spring.aop.service.UserService$$EnhancerBySpringCGLIB$$caa6fbaf.createUser(<generated>)
	at indi.chester.spring.aop.SpringApplication.main(SpringApplication.java:11)

Last updated