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日志并进行多维度分析。
相关文章
|
4天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
4天前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
7天前
|
安全 Java 开发者
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
14 1
|
6天前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
19天前
|
Java API Spring
Spring Boot 中的 AOP 处理
对 Spring Boot 中的切面 AOP 做了详细的讲解,主要介绍了 Spring Boot 中 AOP 的引入,常用注解的使用,参数的使用,以及常用 api 的介绍。AOP 在实际项目中很有用,对切面方法执行前后都可以根据具体的业务,做相应的预处理或者增强处理,同时也可以用作异常捕获处理,可以根据具体业务场景,合理去使用 AOP。
|
28天前
|
Java Spring 容器
Spring问题之Spring AOP是如何实现面向切面编程的
Spring问题之Spring AOP是如何实现面向切面编程的
|
24天前
|
缓存 安全 Java
Spring高手之路21——深入剖析Spring AOP代理对象的创建
本文详细介绍了Spring AOP代理对象的创建过程,分为三个核心步骤:判断是否增强、匹配增强器和创建代理对象。通过源码分析和时序图展示,深入剖析了Spring AOP的工作原理,帮助读者全面理解Spring AOP代理对象的生成机制及其实现细节。
17 0
Spring高手之路21——深入剖析Spring AOP代理对象的创建
|
4天前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
3月前
|
安全 Java Spring
Spring之Aop的底层原理
Spring之Aop的底层原理
|
3月前
|
设计模式 Java uml
Spring AOP 原理
Spring AOP 原理
24 0