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日志并进行多维度分析。
相关文章
|
2天前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
|
1天前
|
人工智能 Java API
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
本次分享的主题是阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手,由阿里云两位工程师分享。
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
|
11天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
49 8
|
1月前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
2月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
96 5
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
90 8
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
53 5
|
2月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
55 4