Spring——AOP中5大通知注解的理解与使用

简介: Spring——AOP中5大通知注解的理解与使用

文章目录:

1.引子

1.1 大致步骤 

1.2 pom.xml文件中加入maven依赖

1.2.1 spring依赖 

1.2.2 spring-aspects依赖

1.2.3 单元测试依赖 

1.3 Spring配置文件中声明AspectJ的自动代理生成器

2.AOP中的5大通知注解

2.1 @Before:前置通知

2.2 @AfterReturning:后置通知

2.3 @Around:环绕通知

2.4 @AfterThrowing:异常通知

2.5 @After:最终通知

3.@Pointcut 定义切入点


1.引子


承接了上一篇文章:https://blog.csdn.net/weixin_43823808/article/details/114637706

这篇文章中,主要介绍一下通过 AspectJ 框架更好的理解和使用AOP5大通知注解。

AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。


1.1 大致步骤 

使用apsectj框架的注解,实现前置通知,步骤如下:

1.新建Maven项目

 

2.修改pom.xml,加入依赖

   spring-context依赖、spring-aspects依赖、junit

 

3.创建业务接口和实现类

 

4.创建一个切面类(普通类)

   1) 在类的上面加入@Aspect

   2) 在类中定义方法,方法表示切面的功能。在方法的上面加入AspectJ框架中的通知注解

      例如:@Before(value="切入点表达式")

 

5.创建spring配置文件

   1) 声明目标对象

   2) 声明切面类对象

   3) 声明自动代理生成器

 

6.创建测试类,测试目标方法执行时,增加切面的功能


1.2 pom.xml文件中加入maven依赖


1.2.1 spring依赖 


<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>


1.2.2 spring-aspects依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>


1.2.3 单元测试依赖 


<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>


1.3 Spring配置文件中声明AspectJ的自动代理生成器

<?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"
       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">
    <!-- 声明自动代理生成器,目的是创建目标对象的代理 -->
    <aop:aspectj-autoproxy />
</beans>

2.AOP中的5大通知注解


2.1 @Before:前置通知

/**
 * 前置通知方法的定义
 * 1) 方法是public
 * 2) 返回值是void
 * 3) 方法名称自定义
 * 4) 可以有参数,也可以无参数。如果有,参数是JoinPoint
 *
 * @Before: 前置通知
 *     属性: value 切入点表达式,表示切面的执行位置。在这个方法执行时,会同时执行切面的功能
 *     位置: 放在方法的上面
 *     特点: 1) 执行时间在目标方法之前先执行
 *           2) 不会影响目标方法的执行
 *           3) 不会修改目标方法的执行结果
 *  切面类中的通知方法,可以有参数。必须是JoinPoint
 *  JoinPoint: 表示正在执行的业务方法,相当于反射中的Method
 *   使用要求: 必须是参数列表的第一个
 *       作用: 获取方法执行时的信息,例如方法名称、方法的参数集合
 */
package com.bjpowernode.service;
/**
 *
 */
public interface SomeService {
    void doSome(String name,Integer age);
    void doOther();
}
package com.bjpowernode.service.impl;
import com.bjpowernode.service.SomeService;
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("业务方法doSome(),创建商品的订单");
    }
    @Override
    public void doOther() {
        System.out.println("业务方法doOther()");
    }
}
package com.bjpowernode.handle;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
 *  @Aspect: 切面类的注解
 *      位置: 放在某个类的上面
 *      作用: 表示当前类是切面类
 */
@Aspect
public class MyAspect {
    @Before(value = "execution(public void com.bjpowernode.service.impl.SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint joinPoint) {
        //获取方法的完整定义
        System.out.println("前置通知,获取目标方法的定义: " + joinPoint.getSignature());
        //获取方法的名称
        System.out.println("前置通知,获取目标方法的名称: " + joinPoint.getSignature().getName());
        //获取方法执行时的参数
        Object[] args=joinPoint.getArgs();
        for(Object obj : args) {
            System.out.println("前置通知,获取目标方法的参数: " + obj);
        }
        String methodName=joinPoint.getSignature().getName();
        if(methodName.equals("doSome")) {
            //切面的代码
            System.out.println("doSome输出日志=========前置通知,切面的功能,在目标方法之前先执行: " + new Date());
        }else if(methodName.equals("doOther")) {
            //切面的代码
            System.out.println("doOther输出日志========前置通知,作为方法名称、参数的记录");
        }
    }
}
    <!-- 声明目标对象 -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
    <!-- 声明切面类对象 -->
    <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/>
    <!-- 声明自动代理生成器,目的是创建目标对象的代理 -->
    <aop:aspectj-autoproxy />
    @Test
    public void test01() {
        String config="applicationContext.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        SomeService service= (SomeService) ctx.getBean("someService");
        System.out.println("service === " + service.getClass().getName());
        service.doSome("张起灵",20);
        //service.doOther();
    }

2.2 @AfterReturning:后置通知

/**
 *  后置通知方法的定义
 *  1) 方法是public
 *  2) 返回值是void
 *  3) 方法名称自定义
 *  4) 方法有参数,推荐使用Object
 *
 *  @AfterReturning: 后置通知
 *  属性: value 切入点表达式
 *       returning 自定义的变量,表示目标方法的返回值的返回值。
 *                 自定义变量的名称必须和通知方法的形参名一样
 *  位置: 放在方法的上面
 *  特点: 1) 在目标方法之后执行
 *        2) 能获取到目标方法的执行结果
 *        3) 不会影响目标方法的执行
 *  方法的参数Object res,表示目标方法的返回值,使用res接收doOther的调用结果
 */
package com.bjpowernode.service;
/**
 *
 */
public interface SomeService {
    String doOther(String name,Integer age);
}
package com.bjpowernode.service.impl;
import com.bjpowernode.service.SomeService;
public class SomeServiceImpl implements SomeService {
    @Override
    public String doOther(String name, Integer age) {
        System.out.println("执行业务方法doOther(),处理库存");
        return "abcd";
    }
}
package com.bjpowernode.handle;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
/**
 *  @Aspect: 切面类的注解
 *      位置: 放在某个类的上面
 *      作用: 表示当前类是切面类
 */
@Aspect
public class MyAspect {
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
                    returning = "res")
    public void myAfterReturning(Object res) {
        System.out.println("后置通知,在目标方法之后执行的,可以拿到执行结果" + res);
    }
}
    <!-- 声明目标对象 -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
    <!-- 声明切面类对象 -->
    <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/>
    <!-- 声明自动代理生成器,目的是创建目标对象的代理 -->
    <aop:aspectj-autoproxy />
    @Test
    public void test01() {
        String config="applicationContext.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        SomeService service= (SomeService) ctx.getBean("someService");
        System.out.println("service === " + service.getClass().getName());
        service.doOther("张三",25);
    }

2.3 @Around:环绕通知

/**
 *  环绕通知方法的定义
 *  1) 方法是public
 *  2) 必须有返回值,推荐使用Object类型
 *  3) 方法名称自定义
 *  4) 必须有ProceedingJoinPoint参数
 *
 *  @Around: 环绕通知
 *      属性: value 切入点表达式
 *      位置: 在方法定义的上面
 *  测试方法中,调用目标方法doFirst(String name)去执行,
 *  实际上目标方法doFirst(String name)并未执行,而是执行了myAround(ProceedingJoinPoint proceedingJoinPoint)
 *  特点: 1) 在目标方法的前和后都能增强功能
 *        2) 控制目标方法是否执行
 *        3) 修改目标方法的执行结果
 */
package com.bjpowernode.service;
/**
 *
 */
public interface SomeService {
    String doFirst(String name);
}
package com.bjpowernode.service.impl;
import com.bjpowernode.service.SomeService;
public class SomeServiceImpl implements SomeService {
    @Override
    public String doFirst(String name) {
        System.out.println("执行业务方法doFirst(),处理库存");
        return "doFirst";
    }
}
package com.bjpowernode.handle;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
 *  @Aspect: 切面类的注解
 *      位置: 放在某个类的上面
 *      作用: 表示当前类是切面类
 */
@Aspect
public class MyAspect {
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //获取方法执行时的参数值
        String name="";
        Object[] args=proceedingJoinPoint.getArgs();
        if(args!=null && args.length>0) {
            Object arg=args[0];
            if(arg!=null) {
                name=(String)arg;
            }
        }
        System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=== " + new Date());
        Object methodReturn=null;
        if(name.equals("李四")) {
            //相当于执行目标方法doFirst(String name)
            methodReturn = proceedingJoinPoint.proceed();
        }
        System.out.println("执行了环绕通知,在目标方法之后,增加了事务功能");
        return methodReturn;
    }
}
    <!-- 声明目标对象 -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
    <!-- 声明切面类对象 -->
    <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/>
    <!-- 声明自动代理生成器,目的是创建目标对象的代理 -->
    <aop:aspectj-autoproxy />
    @Test
    public void test01() {
        String config="applicationContext.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        SomeService service= (SomeService) ctx.getBean("someService");
        System.out.println("service === " + service.getClass().getName());
        String ret=service.doFirst("李四");
        System.out.println("ret调用目标方法的结果 === " + ret);
    }

2.4 @AfterThrowing:异常通知

/**
 *  异常通知方法的定义
 *  1) 方法是public
 *  2) 返回值是void
 *  3) 方法名称自定义
 *  4) 方法有参数Exception
 *
 *  @AfterThrowing: 异常通知
 *  属性: value 切入点表达式
 *       throwing 自定义变量,表示目标方法抛出的异常。变量名必须和通知方法的形参名一样
 *  位置: 在方法上面
 *  特点: 1) 在目标方法抛出异常之后执行的,若没有异常 则不执行
 *        2) 能获取到目标方法的异常信息
 *        3) 不是异常处理程序,可以得到发生异常的通知,可以发送邮件、短信通知开发人员
 *           可以看作是目标方法的监控程序
 *  可以看作以下语句块:
 *  try {
 *      SomeServiceImpl.doSecond(..)
 *  }catch(Exception ex) {
 *      myAfterThrowing(Exception ex)
 *  }
 */


package com.bjpowernode.service;
/**
 *
 */
public interface SomeService {
    void doSecond(String name);
}
package com.bjpowernode.service.impl;
import com.bjpowernode.service.SomeService;
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSecond(String name) {
        System.out.println("执行业务方法doSecond(),处理库存" + (10/0));
    }
}
package com.bjpowernode.handle;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
/**
 *  @Aspect: 切面类的注解
 *      位置: 放在某个类的上面
 *      作用: 表示当前类是切面类
 */
@Aspect
public class MyAspect {
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:" + ex.getMessage());
    }
}
    <!-- 声明目标对象 -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
    <!-- 声明切面类对象 -->
    <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/>
    <!-- 声明自动代理生成器,目的是创建目标对象的代理 -->
    <aop:aspectj-autoproxy />

2.5 @After:最终通知

/**
 *  最终通知方法的定义
 *  1) 方法是public
 *  2) 返回值是void
 *  3) 方法名称自定义
 *  4) 方法无参数
 *
 *  @After: 最终通知
 *  属性: value 切入点表达式
 *  位置: 在方法上面
 *  特点: 1) 在目标方法之后执行的
 *        2) 总是会被执行
 *        3) 可以用来做程序最后的收尾工作,例如清除临时数据、变量,清理内存
 *  可以看作如下语句块:
 *  try {
 *      SomeServiceImpl.doThird(..)
 *  }finally {
 *      myAfter()
 *  }
 */
package com.bjpowernode.service;
/**
 *
 */
public interface SomeService {
    void doThird();
}
package com.bjpowernode.service.impl;
import com.bjpowernode.service.SomeService;
public class SomeServiceImpl implements SomeService {
    @Override
    public void doThird() {
        System.out.println("执行业务方法doThird()");
    }
}
package com.bjpowernode.handle;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
/**
 *  @Aspect: 切面类的注解
 *      位置: 放在某个类的上面
 *      作用: 表示当前类是切面类
 */
@Aspect
public class MyAspect {
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter() {
        System.out.println("最终通知,总是会被执行的");
    }
}
    <!-- 声明目标对象 -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
    <!-- 声明切面类对象 -->
    <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/>
    <!-- 声明自动代理生成器,目的是创建目标对象的代理 -->
    <aop:aspectj-autoproxy />
    @Test
    public void test01() {
        String config="applicationContext.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        SomeService service= (SomeService) ctx.getBean("someService");
        System.out.println("service === " + service.getClass().getName());
        service.doThird();
    }


3.@Pointcut 定义切入点


当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。

其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 execution value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

下面的代码只给出切面类:👇👇👇


package com.bjpowernode.handle;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
 *  @Aspect: 切面类的注解
 *      位置: 放在某个类的上面
 *      作用: 表示当前类是切面类
 */
@Aspect
public class MyAspect {
    @Before(value = "mypt()")
    public void myBefore() {
        System.out.println("前置通知,在目标方法之前先执行的");
    }
    @After(value = "mypt()")
    public void myAfter() {
        System.out.println("最终通知,总是会被执行的");
    }
    /**
     * @Pointcut: 定义和管理切入点,不是通知注解
     *       属性: value 切入点表达式
     *       位置: 在一个自定义方法的上面,此方法被看作是切入点表达式的别名
     *            在其他通知注解中,可以使用此方法名称,表示使用这个切入点表达式
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void mypt() {
        //无需代码
    }
}



相关文章
|
15天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
142 73
|
10天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
45 21
|
2天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
27 8
|
15天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
15天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
81 5
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
81 8
|
2月前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
49 4
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
155 2