Spring13——AOP通知类型

33-AOP通知类型

前面的案例中,有涉及到如下内容

@Before("pt()")

它所代表的含义是将通知添加到切入点方法执行的前面。
除了这个注解外,还有没有其他的注解,换个问题就是除了可以在前面加,能不能在其他的地方加?

类型介绍

我们先来回顾下AOP通知:

AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
那么具体可以将通知添加到哪里呢?一共提供了5种通知类型

  • 前置通知
  • 后置通知
  • 环绕通知(重点)
  • 返回后通知(了解)
  • 抛出异常后通知(了解)

为了更好的理解这几种通知类型,我们来看一张图
在这里插入图片描述

  1. 前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容
  2. 后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容
  3. 返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加
  4. 抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加
  5. 环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能,具体是如何实现的,需要我们往下学习。

环境准备

  • 创建一个Maven项目
  • pom.xml添加Spring依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
  • 添加BookDao和BookDaoImpl类
public interface BookDao {
    public void update();

    public int select();
}

@Repository
public class BookDaoImpl implements BookDao {

    public void update() {
        System.out.println("book dao update ...");
    }

    public int select() {
        System.out.println("book dao select ...");
        return 100;
    }
}
  • 创建Spring的配置类
@Configuration
@ComponentScan("com.yolo")
@EnableAspectJAutoProxy
public class SpringConfig {
}

通知类型的使用

前置通知

修改MyAdvice,在before方法上添加@Before注解

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.yolo.dao.BookDao.update())")
    private void pt() {}

    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }
}

运行程序,输出如下
在这里插入图片描述

后置通知

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.yolo.dao.BookDao.update())")
    private void pt() {}

    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }

    @After("pt()")
    public void after(){
        System.out.println("after advice ...");
    }
}

运行程序,输出如下
在这里插入图片描述

环绕通知

  • 基本使用
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.yolo.dao.BookDao.update())")
    private void pt() {}

//    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }

//    @After("pt()")
    public void after(){
        System.out.println("after advice ...");
    }

    @Around("pt()")
    public void around(){
        System.out.println("around before advice ...");
        System.out.println("around after advice ...");
    }
}

运行程序,输出如下
在这里插入图片描述
运行结果中,通知的内容打印出来,但是原始方法的内容却没有被执行。

因为环绕通知需要在原始方法的前后进行增强,所以环绕通知就必须要能对原始操作进行调用,具体如何实现?

  • 在方法参数中添加ProceedingJoinPoint,同时在需要的位置使用proceed()调用原始操作,注意抛出异常
@Around("pt()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around before advice ...");
        // 表示对原操作的调用
        point.proceed();
        System.out.println("around after advice ...");
    }

运行程序,输出如下
在这里插入图片描述

  • 注意事项
    • 当原始方法中有返回值时
      • 修改MyAdvice,对BookDao中的select方法添加环绕通知
      • 修改App类,调用select方法
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(int com.yolo.dao.BookDao.select())")
    private void pt2() {}

    @Around("pt2()")
    public void aroundSelect(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around before advice ...");
        // 表示对原操作的调用
        point.proceed();
        System.out.println("around after advice ...");
    }
}
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        System.out.println(bookDao.select());
    }
}

运行程序,报错:org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int com.yolo.dao.BookDao.select()
在这里插入图片描述
错误大概的意思是:空的返回不匹配原始方法的int返回

  • void就是返回Null
  • 原始方法是BookDao下的select方法,返回值是int

所以如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值,具体解决方案为:

@Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around before advice ...");
        // 表示对原操作的调用
        Object res = point.proceed();
        System.out.println("around after advice ...");
        return res;
    }
  • 运行程序,结果如下
    在这里插入图片描述

返回后通知

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(int com.yolo.dao.BookDao.select())")
    private void pt2() {
    }

    @AfterReturning("pt2()")
    public void afterReturning() {
        System.out.println("afterReturning advice ...");
    }
}

运行程序,结果如下
在这里插入图片描述
注意:
返回后通知是需要在原始方法select正常执行后才会被执行,如果select()方法执行的过程中出现了异常,那么返回后通知是不会被执行。后置通知是不管原始方法有没有抛出异常都会被执行。
现在我们在select()方法中加一个异常

public int select() {
        System.out.println("book dao select ...");
        int i = 1/0;
        return 100;
    }

运行程序,输出如下,没有输出afterReturning advice …
在这里插入图片描述
我们再换成后置输出,

@After("pt2()")
    public void after(){
        System.out.println("after advice ...");
    }

运行程序,结果如下,输出了after advice
在这里插入图片描述

异常后通知

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(int com.yolo.dao.BookDao.select())")
    private void pt2() {
    }

    @AfterThrowing("pt2()")
    public void afterThrowing() {
        System.out.println("afterThrowing advice ...");
    }
}

运行程序,输出如下
在这里插入图片描述
去掉原方法中的异常

public int select() {
        System.out.println("book dao select ...");
//        int i = 1/0;
        return 100;
    }

再运行,结果如下
在这里插入图片描述
学习完这5种通知类型,我们来思考下环绕通知是如何实现其他通知类型的功能的?

因为环绕通知是可以控制原始方法执行的,所以我们把增强的代码写在调用原始方法的不同位置就可以实现不同的通知类型的功能,如
在这里插入图片描述

通知类型总结

知识点1:@After
在这里插入图片描述
知识点2:@AfterReturning
在这里插入图片描述
知识点3:@AfterThrowing
在这里插入图片描述
知识点4:@Around
在这里插入图片描述
知识点5:@Before
在这里插入图片描述
环绕通知注意事项

  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型
  4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常
    在这里插入图片描述
Spring AOPSpring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解的AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解的AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值