Spring 11: @AfterReturning后置通知

2年前 (2022) 程序员胖胖胖虎阿
145 0 0

@AfterReturning

图解

  • 后置通知的方法参数可以是目标方法的返回值,如果目标方法有返回值则可以获得该返回值
  • 后置通知在获取到目标方法的返回值后,是否可以修改该返回值,从而真正影响到目标方法的返回值?
  • 如果目标方法的返回值是基本数据类型(8 + 1),则影响不起作用
  • 如果目标方法的返回值是引用类型,则影响起作用

Spring 11: @AfterReturning后置通知

业务接口

package com.example.s02;

/**
 * 定义业务接口
 */
public interface SomeService {
    default String doSome(int orderNums){return null;}
}

业务实现类

package com.example.s02;

import org.springframework.stereotype.Service;

/**
 * 业务实现类
 */
@Service
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(int orderNums) {
        System.out.println("预定图书: " + orderNums + "册");
        return "order success";
    }
}

切面类

package com.example.s02;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 切面类
 */
@Aspect
@Component
public class SomeServiceAspect {
    /**
     * a.后置通知的方法规范:
     *  1.方法权限public
     *  2.返回值void
     *  3.方法名自定义
     *  4.方法可以有参数也可以没有参数,如果目标方法没有返回值,可以不带参数,建议带上参数,这样两种情况都可以应对
     *  5.使用AfterReturning注解表示是后置通知
     *  value参数:指定切入点表达式
     *  returning参数:用来接收目标方法的返回值,该参数的名称必须和后置切面方法的参数的名称相同
     *  为了顺利完成参数的过渡,让切面方法拿到目标方法返回值
     *
     * b.后置通知示例:
     *  public String doSome(int orderNums)
     *
     */
    @AfterReturning(value = "execution(* com.example.s02.*.*(..))", returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置切面功能: 检查预定图书后,剩余图书量");
        if(obj != null){
            if(obj instanceof String){
                String res = ((String) obj).toUpperCase();
                System.out.println("后置通知中获取到的目标方法的返回值: " + res);
            }
        }
    }
}

applicationContext.xml

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

    <!-- 添加包扫描 -->
    <context:component-scan base-package="com.example.s02"/>
    
    <!-- 绑定业务功能和切面功能-->
    <aop:aspectj-autoproxy/>
</beans>

测试

package com.example.test;

import com.example.s02.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAfterAspect {
    //测试返回值无法改变的情况
    @Test
    public void testAfterAspect01(){
        //创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //获取jdk动态代理对象
        SomeService service = (SomeService) ac.getBean("someServiceImpl");
        //调用业务功能(普通业务功能 + 后置切面功能)
        String res = service.doSome(10);
        System.out.println("目标方法的返回值: " + res);
    }
}

测试输出

  • 由测试结果可以看出,后置切面功能在目标方法执行后执行,且成功获取到了目标方法的返回值,但是后置切面对目标方法返回值的修改并未影响目标方法的返回结果
预定图书: 10册
后置切面功能: 检查预定图书后,剩余图书量
后置通知中获取到的目标方法的返回值: ORDER SUCCESS	//后置切面方法将获取到的目标方法返回值全部变成大写
目标方法的返回值: order success		//但是目标方法的返回值实质上并未受到改变

Process finished with exit code 0

新增实体类

  • 新增Student实体类
package com.example.s02;

public class Student {
    private String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student(String name) {
        this.name = name;
    }
}

新增业务功能

  • 在业务接口中新增返回引用类型的方法定义
    default Student change(String name){return null;}

新增实现类

  • 在业务实现类中对新增的接口方法进行实现
    @Override
    public Student change(String name) {
        return new Student(name);
    }

修改切面类中切面方法

  • 将后置切面方法做如下修改
    @AfterReturning(value = "execution(* com.example.s02.*.*(..))", returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置切面功能: 检查预定图书后,剩余图书量");
        if(obj != null){
            if(obj instanceof String){
                String res = ((String) obj).toUpperCase();
                System.out.println("后置通知中获取到的目标方法的返回值: " + res);
            }
            if(obj instanceof Student){
                String name = ((Student) obj).getName().toUpperCase();
                ((Student) obj).setName(name);
                System.out.println("后置通知中获取到的目标方法的返回值: " + obj);
            }
        }
    }

测试

    //测试返回值可以改变的情况
    @Test
    public void testAfterAspect02(){
        //创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //获取jdk动态代理对象
        SomeService agent = (SomeService) ac.getBean("someServiceImpl");
        //调用业务功能(普通业务功能 + 后置切面功能)
        Student student = agent.change("wangxiaohan");
        System.out.println("目标方法的返回值: " + student);
    }

测试输出

  • 可以看到,当目标方法返回的是引用类型时,后置通知中对目标方法的返回值的改变真正起到了作用
后置切面功能: 检查预定图书后,剩余图书量
后置通知中获取到的目标方法的返回值: Student{name='WANGXIAOHAN'}
目标方法的返回值: Student{name='WANGXIAOHAN'}

Process finished with exit code 0
版权声明:程序员胖胖胖虎阿 发表于 2022年11月9日 上午5:48。
转载请注明:Spring 11: @AfterReturning后置通知 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...