环绕通知

Spring AOP 五种通知类型

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

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

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

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

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

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

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

功能最强大的即为 around 环绕通知。在环绕通知的 切面方法中,可以控制目标方法是否执行,可以在目标方法运行前,目标方法运行后执行,还可以获得目标方法的返回类型。可以说,环绕通知包括了其他四种通知的全部功能。

定义目标类与目标方法

maven 依赖与 “第一个 Spring AOP 应用” 里面一样,这里不再重复

定义一个 UserService 类 和 UserDao 类。

public class UserService {
    private UserDao userDao;

    public void createUser(){
        System.out.println("执行创建用户业务逻辑");
        userDao.insert();
    }

    public String generateRandomPassword(String type , Integer length){
        System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
        return "Zxcquei1";
    }
    
    // getter 和 setter 方法 省略
 
}
public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}

新的需求

假设有这么一个需求,需要获取 UserService 运行过程 的执行时间。

Spring AOP 环绕通知 实现新需求

定义切面类

切面方法为 check, 传入参数不再是 JoinPoint 类型, 而是 ProceedingJoinPoint 类型。 ProceedingJoinPoint 更加强大,主要体现在 其 proceed 方法, 其作用是执行目标方法。如果不调用 proceed 方法,则不执行 proceed 方法。再加上 ProceedingJoinPoint 本身也支持 getTarget, getSignature, getArgs 方法, 也就是说 环绕通知的切面方法可以控制目标方法是否执行,可以在目标方法执行前,后分别进行扩展,可以获取目标方法的返回值,可以说是非常强大。

public class MethodChecker {
    //ProceedingJoinPoint 是 JoinPoint 的升级版, 在原有功能外, 还可以控制目标方法是否执行
    public Object check(ProceedingJoinPoint pjp) throws  Throwable{
        try {
            long startTime = new Date().getTime();
            Object ret = pjp.proceed();//执行目标方法
            long endTime = new Date().getTime();
            long duration = endTime - startTime;
            if (duration>=1000){
                String className = pjp.getTarget().getClass().getName();//获取目标类的名称
                String methodName = pjp.getSignature().getName();//获取目标方法的名称
                Object[] args = pjp.getArgs();//获取目标方法参数
                SimpleDateFormat sdt= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSSS");
                String now = sdt.format(new Date());
                System.out.println("========"+now + " : "+ className+" : "+methodName+ " 参数个数 : " + args.length +" ( "+ duration +" ms)"+"========");
            }
            return ret;
        } catch (Throwable throwable) {
            System.out.println("Exception message : "+ throwable.getMessage());
            throw throwable;
        }
    }
}

配置 applicationContext.xml

环绕通知 使用 <aop:around> 标签 进行配置

<?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="methodChecker" class="indi.chester.spring.aop.aspect.MethodChecker"/>
    
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(public * indi.chester..*.*(..))"/>
        <aop:aspect ref="methodChecker">
            <!-- 环绕通知,使用 methodChecker.check 方法进行环绕通知, 切点为 pointcut -->
            <aop:around method="check" 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();
    }
}

运行结果:

执行创建用户业务逻辑
新增用户数据
========2022-02-19 10:22:02 0037 : indi.chester.spring.aop.service.UserService : createUser 

Last updated