写点什么

【Spring 学习笔记 (十四)】Spring AOP 通知中获取数据

作者:倔强的牛角
  • 2022 年 6 月 19 日
  • 本文字数:3268 字

    阅读完需:约 11 分钟

【Spring 学习笔记(十四)】Spring AOP 通知中获取数据

写在前面😘

大一电子信息工程新生,请多多关照,希望能在 InfoQ 社区记录自己的学习历程!

【Spring 学习笔记】 系列教程基于 Spring 5.2.10.RELEASE讲解。

一、环境准备

添加 AOP 依赖

pom.xml文件里添加 Spring AOP AspectJ 的 jar 包依赖


<dependencies>    <!--包含Spring AOP:有基本的AOP功能-->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.2.10.RELEASE</version>    </dependency>    <!--AspectJ框架有更强大的AOP功能-->    <dependency>        <groupId>org.aspectj</groupId>        <artifactId>aspectjweaver</artifactId>        <version>1.9.5</version>    </dependency></dependencies>
复制代码

创建目标接口和实现类

/*UserService接口*/public interface UserService {    //自我介绍方法    public string hello(String username,int age);}/*UserServiceImpl实现类*/@Repositorypublic class UserServiceImpl implements UserService {    @Override    public String hello(String username, int age) {        return "username:" + username + ",age:" + age;    }}
复制代码

创建通知类

  • 创建通知类,并指定切入点


/*通知类*/@Component//将这个类定义成 Bean@Aspect//将这个Bean定义为切面public class MyAdvice {    //指定UserService类中的hello方法为切入点    @Pointcut("execution(* com.bighorn.service.UserService.hello(..))")    private void pt1(){}}
复制代码

创建 Spring 核心配置类

/*Spring核心配置类*/@Configuration@ComponentScan("com.bighorn") //开启注解扫描@EnableAspectJAutoProxy //开启 AspectJ 的自动代理public class SpringConfig {}
复制代码

编写运行程序

  • hello()方法中传入两个参数:"bighorn",18


public class App {    public static void main(String[] args) throws SQLException {        //获取配置类初始化容器        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);        //从容器中获取UserService对象        UserService UserService = context.getBean(UserService.class);        //调用UserService的方法        String user = UserService.hello("bighorn", 18);        System.out.println(user);    }}
复制代码

二、JoinPoint 和 ProceedingJoinPoint

通知方法中如果有 JoinPoint 或者 ProceedingJoinPoint 参数,必须要把他们放在第一位

JoinPoint 接口

在切面方法中添加 JoinPoint 参数,就可以获取到封装了该方法信息的 JoinPoint 对象


适用场景:前置通知、后置通知、异常通知、返回通知

常用 API


  • 目标方法名: joinPoint.getSignature().getName());

  • 目标方法所属类的简单类名:joinPoint.getSignature().getDeclaringType().getSimpleName());

  • 目标方法所属类的类名:joinPoint.getSignature().getDeclaringTypeName();

  • 目标方法声明类型:Modifier.toString(joinPoint.getSignature().getModifiers()));

案例 1

  • 创建一个前置通知,在通知方法中传入 JoinPoint 参数,并调用一些方法获取目标方法的相关信息。


/*前置通知*/@Before("pt1()")public void before(JoinPoint jp) {    System.out.println("目标方法名为:" + jp.getSignature().getName());    System.out.println("目标方法所属类的简单类名:" + jp.getSignature().getDeclaringType().getSimpleName());    System.out.println("目标方法所属类的类名:" + jp.getSignature().getDeclaringTypeName());    System.out.println("目标方法声明类型:" + Modifier.toString(jp.getSignature().getModifiers()));    //获取传入目标方法的参数    Object[] args = jp.getArgs();    for (int i = 0; i < args.length; i++) {        System.out.println("第" + (i+1) + "个参数为:" + args[i]);    }    //    System.out.println("目标对象:" + jp.getTarget());    System.out.println("代理对象:" + jp.getThis());}
复制代码


运行结果如下👇


注意点

仔细观察,发现代理对象自己和被代理对象输出的内存地址一样啊!


其实不然,两个不同的对象内存地址不可能一样,代理类其实内部调用了目标类的方法,上面的目标类和代理类打印调用的是toString(),但是代理类调用的其实是目标类的toString(),所以打印的地址一样。但是你如果用==比较结果就是false了。


我们也可以通过 getClass()方法获取它们的 class 对象并打印出来,比较一下就会发现不同了。


System.out.println("目标对象:" + jp.getTarget().getClass());System.out.println("代理对象:" + jp.getThis().getClass());
复制代码


ProceedingJoinPoint 接口

ProceedingJoinPoint 接口 是 JoinPoint 的子接口,适用场景:只用在环绕通知中


而且环绕通知方法中也必须有 ProceedingJoinPoint 这个形参,不然就没意义了。

proceed 方法

  • proceed()方法是有两个构造方法的

  • 调用无参数的 proceed,当原始方法有参数,会在调用的过程中自动传入参数

  • 但是当需要修改原始方法的参数时,就只能采用有参数的 proceed


Object proceed() throws Throwable //执行目标方法Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法 
复制代码


  • 有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤:如去除字符串最后一位的空格等操作。

案例 2

对原始方法的第一个参数username进行拦截,把英文名变成大写


/*环绕通知*/@Around("pt1()")public Object around(ProceedingJoinPoint pjp) {    Object result = null;    try {        //获取传入目标方法的参数        Object[] args = pjp.getArgs();        //前置通知        System.out.println("目标方法执行前...原参数:"+Arrays.toString(args));        //将传入的英文名变成大写        args[0] = args[0].toString().toUpperCase();        //用新的参数args执行目标方法        result = pjp.proceed(args);        //返回通知        System.out.println("目标方法返回结果后...新参数:"+ Arrays.toString(args));    } catch (Throwable e) {        //异常通知        System.out.println("执行目标方法异常后...");        e.printStackTrace();    }    //后置通知    System.out.println("目标方法执行后...");    return result;}
复制代码


运行结果如下👇


三、获取返回值

对于目标方法的返回值,只有返回通知@AfterReturing环绕通知@Around这两个通知类型可以获取,下面来介绍如何获取。

环绕通知获取返回值

  • 使用Object result = pjp.proceed( );获取返回值,result就是原始方法的返回值,我们不但可以获取,如果需要还可以进行修改。

  • 案例 2 中有类似代码:result = pjp.proceed(args)。这里就不演示了

返回通知获取返回值

  • @AfterReturning注解中,使用 returning 指定返回值名称,且需要与返回通知方法中形参名称保持一致。


/*返回后通知*/@AfterReturning(value = "pt1()", returning = "ret")public void afterReturning(Object ret) {    System.out.println("返回通知,获取原始方法返回值--->" + ret);}
复制代码


运行结果如下👇


四、获取异常

对于目标方法抛出异常,只有异常通知@AfterThrowing环绕通知@Around这两个通知类型可以获取,下面来介绍如何获取。

环绕通知获取异常

  • 使用try-catch语句就能获取异常了,异常处理在 catch 中进行。在案例 2 中有类似代码,这里就不多赘述了。


try{    ret = pjp.proceed();}catch(Throwable e){    //根据业务需求处理异常    e.printStackTrace();}
复制代码

异常通常获取异常

  • 异常通常和返回通知类似,也要在注解上加一句话。不过@AfterThrowing 注解中是使用 throwing 指定异常名称,需要与方法形参名相同。


//异常后通知@AfterThrowing(value = "pt1()",throwing = "t")public void afterThrowing(Throwable t) {    System.out.println("异常通知获取到的异常"+t);}
复制代码

写在后面🍻

感谢观看啦✨

有什么不足,欢迎指出哦💖

发布于: 刚刚阅读数: 4
用户头像

把学问造就,还期身健行优。 2022.06.02 加入

大一在读,电子信息工程专业。 希望在InfoQ写作社区记录自己的学习历程!

评论

发布
暂无评论
【Spring 学习笔记(十四)】Spring AOP 通知中获取数据_Java_倔强的牛角_InfoQ写作社区