Spring AOP 面向切面编程(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring AOP 面向切面编程(下)

三. Advice 通知演示


刚刚已经演示过了 @Before 前置通知, 就不在赘述了


1. @After 注解


利用 @After 后置通知实现一个统一的日志处理功能


  • 建立切面
@Aspect
    @Component
    public class LogAOP {
    }


  • 创建切点
@Aspect
    @Component
    public class LogAOP {
        // 创建切点
        @Pointcut("execution(* com.example.demo.controller.LogController.* (..))")
        public void doPointcut() {
            // 切点只是为了配置规则, 并非具体实现. 因此为空方法
        }
    }


  • 创建后置通知
@Aspect
    @Component
    public class LogAOP {
        // 创建切点
        @Pointcut("execution(* com.example.demo.controller.LogController.* (..))")
        public void doPointcut() {
            // 切点只是为了配置规则, 并非具体实现. 因此为空方法
        }
        // 创建后置通知
        @After("doPointcut()")
        public void doAfter() {
            System.out.println("记录日志结束 ");
        }
    }


  • 建立连接点
@RestController
    public class LogController {
        // 创建日志对象
        public static final Logger log = LoggerFactory.getLogger(LogController.class);
        @RequestMapping("/user/log")
        public String longLog() {
            System.out.println("执行登陆功能");
            log.info("记录操作日志 : 用户成功登陆");
            return "Spring AOP";
        }
    }


访问路由方法模拟执行用户登陆


b0857873b6dab954b773820da7a25914.png389ed4705751bfaa79042b4358bedd1f.png


可以看到, 当执行登陆后, 记录下操作日志, 此时该连接点方法执行结束, 执行后置通知 " 日志记录结束 "


2. @AfterReturning 注解


还是刚刚的登陆操作, 当执行的是 @AfterReturning 返回通知时, 预期在登陆操作结束后执行


  • 建立切面
@Aspect 
    @Component 
    public class UserAOP {
    }


  • 创建切点
@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut() {
        // 切点只是为了配置规则, 并非具体实现. 因此为空方法
    }


  • 建立返回通知
@AfterReturning("doPointcut()")
    public void doAfterReturning() {
        System.out.println("执行返回通知 ");
    }


  • 创建连接点
@RequestMapping("user/login")
    public String login() {
        System.out.println("执行登陆操作 ");
        return "Spring AOP";
    }


执行登陆方法

7ac69f590bc5cc68e3be13988c78b467.png10b42605ef841e74b41e73bfe4a7a6be.png


3. @AfterThrowing 注解


异常通知, 当执行匹配的连接点的方法遇到异常结束后返回通知

还是刚刚的切面里, 同样的切点, 建立异常通知, 执行登陆方法

@AfterThrowing("doPointcut()")
    public void doAfterThrowing() {
        System.out.println("执行异常通知 ");
    }


可以看到抛异常了, 并且控制台也在异常之后打印了异常通知

6295a2391d1fbbb43debbafeea999c8d.png82e888be436a20b98b8b1d5666dacb08.png


4. @Around 注解


环绕通知, 被通知的方法本身被通知包裹着, 也就是执行环绕通知在被通知的方法执行之前会发一次通知, 在被执行方法执行结束后也会执行一次通知.


还是在刚刚的切面和切点, 建立环绕通知

// 添加事件本身
// ProceedingJoinPoint 表示正在执行的方法或表达式的连接点
// 配置环绕通知
@Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("开始执行环绕通知" );
    long startTime = System.currentTimeMillis();
    // 因为是将方法包围起来执行, 因此只能在方法里调用本身
    Object object = joinPoint.proceed(); // 调用连接点的方法
    System.out.println("结束执行环绕通知" );
    long endTime = System.currentTimeMillis();
    System.out.println("时间差为 : " + (endTime - startTime));
    return object; // 计算时间差后并获取秒数毫秒级
}


可以看到, 环绕通知里面有 joinPoint, 前面说到它是连接点的意思. 这里不难明白, 切面需要通过连接点来确定何时调用环绕通知和如何调用它们. 因此这里传入的必须是连接点. 而前面的前置、后置通知等切面只需要在正确匹配的连接点之前或者之后通知就可以了.


建立连接点

@RequestMapping("/user/count")
    public String fun1() {
        System.out.println("执行了 count 方法");
        int count = 0;
        for(int i = 0; i < 1000000000; i++) {
            count++;
        }
        return "统计方法";
    }


通过环绕通知, 我们就可以统计某个方法的具体执行时间了, 从而作为该方法是否需要优化的重要依据之一.

28859cd6906c5bc41639cc76fadf582b.png


0692762a9820fdae3dd6c0f0f4d1c2a6.png


5. 环绕和前置后置通知同时执行


你可能发现了, 环绕通知其实就是一个前置通知搭配一个后置通知. 那么, 既然前置后置都有了, 为什么还需要环绕通知呢 ? 他们放在一起执行会报错呢 ? 还是有什么联系呢 ?

public class UserAOP {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void doPointcut() {
        // 切点只是为了配置规则, 并非具体实现. 因此为空方法
    }
    // 配置前置通知
    @Before("pointcut()") // 里面填写针对那个切点的通知, 可以有多个切点
    public void doBefore() {
        System.out.println("执行 before 前置通知 : 登陆检验" );
    }
    @After("doPointcut()")
    public void doAfter() {
        System.out.println("执行 After 后置通知 : 登陆检验结束");
    }
    // 添加事件本身
    // ProceedingJoinPoint 表示正在执行的方法或表达式的连接点
    // 配置环绕通知
    @Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("开始执行环绕通知" );
        long startTime = System.currentTimeMillis();
        // 因为是将方法包围起来执行, 因此只能在方法里调用本身
        Object object = joinPoint.proceed(); // 调用连接点的方法
        System.out.println("结束执行环绕通知" );
        long endTime = System.currentTimeMillis();
        System.out.println("时间差为 : " + (endTime - startTime));
        return object; // 计算时间差后并获取秒数毫秒级
    }
}


a69e85a83a38c186c01a7dfb0a394450.png


bcf434ed5fc5148dfa4cb5320c345b9b.png

可以看到, 前置和后置通知总是在之间环绕通知的. 这是为什么 ?


由于环绕通知需要在目标方法执行之前和之后分别执行, 以便正确的控制目标方法的执行


具体来说, 如果放在前置和后置执行之间, 那么当目标方法执行时, 环绕通知也会被调用. 通俗一点理解就是执行方法时的前置通知时间是极短的, 如果在这执行前置通知还需要执行环绕通知有可能会导致重复执行, 从而导致代码重复和性能损失.


6. 总结


Spring AOP 除了上面的这几样简单的统一功能处理外, 还有很多功能可以实现, 只要它符合统一集中处理的思想, 也就是符合 AOP 的思想. 就可以用 Spring AOP 来实现.


四. Spring AOP 原理分析



Spring AOP 倒地是怎么执行的呢 ? 它为什么就知道那些连接点是我们匹配的, 那些是我们不需要的呢 ?


Spring AOP 是构建在动态代理基础上的. 因此 Spring 对 AOP 的支持局限于方法级别的拦截.


我们上面所写的 Spring AOP 它都是原生的. 而在 JDK 中早已替我们封装了


  1. JDK 动态代理


通过反射的机制, 在运行的时候生成一个代理对象, 并将所有的方法调用转发给代理对象. 代理对象实现了 InvocationHandler 接口,并重写了 invoke() 方法, 该方法会在代理对象被调用的时候执行.


  1. CGLIB 动态代理


和 JDK 动态代理类似, 也是通过代理对象来实现方法调用的转发. 但 CGLIB 动态代理比 JDK 动态代理更加的灵活, 因为它可以处理继承关系中私有的方法中的私有方法.


无论是那种方式, 在 Spring AOP 都可以使用动态代理的方式来实现切面逻辑



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
27天前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
49 1
|
18天前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
2天前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
14 2
|
9天前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
45 9
|
3天前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
11 1
|
21小时前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
9 0
|
1天前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
11 0
|
3天前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
24 0
|
1月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
39 13
|
1月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理