• Spring AOP实现原理及代理模式


    理论

    AOP 灵魂三问

    1. AOP是什么?

    AOP中文叫做面向切面编程,为Aspect Oriented Programming的首字符缩写。意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。通过名字可以看出,它与面向对象编程OOP名称相似。其实AOP就是OOP的进一步延伸。是Spring框架中的重要内容之一。

    2. AOP有什么用?
    利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
    简单来说:使用AOP可以在不修改源码的基础上,增加新的功能。(显而易见,这对于大型项目开发来说是极具诱惑的。减少了版本更迭时的工作量)

    3. AOP是什么原理?
    AOP代表的是一个横向的关系,相比于纵向的继承关系更加简便。将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,可以选择性的提供业务逻辑。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。

    AOP的一些术语概念

    1. aspect (切面):切面由切点和通知组成,即包括横切逻辑的定义也包括连接点的定义

    我的理解是 切面可以看作一个载体,在具体实现中的表现是一个切面类,类中包括切点和通知以及织入的动作等

    1. ponitcut (切点):每个类拥有多个连接点,可以看作连接点的集合

    2. joinpoint(连接点):程序执行的某个特定位置,如某个方法调用前后

    我的理解:需要增强的目标类中的某个位置,及增强在这个位置调用

    1. weaving (织入):将增强添加到目标类的具体连接点的过程
    2. advice (通知) :是织入到目标类连接点上的一段代码,主要就是告诉需要增强到什么地方,需要增强的内容是什么。
    3. target (目标对象):通知织入的目标类
    4. aop Proxy (代理对象) :增强后产生的对象

    Spring AOP 底层实现

    通过JDK动态代理和CGLib代理在运行时期在对象初始化阶段织入代码的

    • JDK 动态代理:基于接口实现
    • CGLib 是基于类的继承实现的

    五种通知形式

    • Before advice :前置通知,目标方法前执行,无论目标方法是否遇到异常都执行
    • After returning advice:后置通知,目标方法执行后执行,前提是目标方法没有遇到异常,遇到异常则不执行
    • After throwing advice:异常通知,顾名思义,在目标方法抛出异常时执行
    • After finally advice:最终通知,在目标方法执行后执行,无视是否异常
    • Around advice:环绕通知:最强大的通知类型,可以控制目标方法的执行(通过调用ProceedingJoinPoint.proceed() 执行目标方法),可以在目标执行全过程中执行。

    实现

    如何写切面类

    1. 定义一个切面类 Aspect

    即声明的类, 增加@Component @Aspect 两个注解,Springboot中要引入spring-boot-stater-aop依赖包
    @Component :告诉Spring 容器要扫描这个类
    @Aspect:告诉Spring 这个类是个切面类

    1. 定义切点 Pointcut

    定义切点,并定义切点在那些地方执行,采用@Pointcut注解
    @Pointcut ( public * com.xxx.xxxx.*.(…) )
    规则:括号内,修饰符(可以不写但不能用 *) + 返回类型 + 那些包下的类 + 那些方法 + 方法参数
    ” 表示不限,“…” 两个点表示参数不限

    1. 定义Advice 通知

    利用通知的五种注解:@Before,@After,@AfterReturning,@AfterThrowing,@Around 来实现某些切点的增强动作,如@Before(“myPointcut()”),myPointcut为定义的切点

    具体举例

    这里创建的Springboot 项目,创建切面类,并在切面类中定义切点以及通知,利用输出日志信息来显示五种通知的不同执行时机。

    首先
    在Springboot项目中创建一个controller包,并再其下创建一个hellocontroller.java
    在这里插入图片描述

    在这个类的具体代码如下

    package com.example.aopdeom.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.IOException;
    
    @RestController
    public class hellocontroller {
    
        @GetMapping("/hello")
        public String hello(@RequestParam("name") String name) {
            return "Hello" + name;
        }
    
        @GetMapping("/bye")
        public String bye(@RequestParam("name") String name,@RequestParam("age") int age) throws IOException {
            if(age < 0){
                throw  new IOException();
            }
            return "Bye" + name + " age = " + age;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    其次
    定义切面类myAdvice

    其代码如下,定义了两个切点,并使用五种通知实现了五个通知,切点
    myPointcut拦截的是controller包下的所有类的方法,而myPointcut2只针对controller包下bye方法,并且不限参数,及如果有同名而不同参的bye也会拦截。

    package com.example.aopdeom;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    
    
    @Aspect
    @Component
    public class myAdvice {
        private Logger logger = LoggerFactory.getLogger(myAdvice.class);
    
        //定义切面
        @Pointcut(value = "execution( * com.example.aopdeom.controller.*.*(..))")
        public void myPointcut(){
    
        }
        @Pointcut(value = "execution( * com.example.aopdeom.controller.hellocontroller.bye(..))")
        public  void myPointcut2(){
    
        }
        @Around("myPointcut()")
        public Object myLogger(ProceedingJoinPoint pjp) throws Throwable {
            String className = pjp.getTarget().getClass().toString();
            String methodName = pjp.getSignature().getName();
            Object[] array = pjp.getArgs();
            ObjectMapper mapper = new ObjectMapper();
    
            logger.info("调用前" + className + ":" + methodName + " 传递的参数:" + mapper.writeValueAsString(array));
            Object obj = pjp.proceed();
            logger.info("调用后:" + className + ":" + methodName + " 返回值:" + mapper.writeValueAsString(obj));
            return obj;
        }
        @Before("myPointcut2()")
        public void myLogger2(JoinPoint pjp) throws Throwable {
            String className = pjp.getTarget().getClass().toString();
            String methodName = pjp.getSignature().getName();
            Object[] array = pjp.getArgs();
            ObjectMapper mapper = new ObjectMapper();
            logger.info("前置通知:" + className + ":" + methodName + " 传递的参数:" + mapper.writeValueAsString(array));
        }
        @After("myPointcut2()")
        public void myLogger3(JoinPoint pjp) throws Throwable {
            String className = pjp.getTarget().getClass().toString();
            String methodName = pjp.getSignature().getName();
            Object[] array = pjp.getArgs();
            ObjectMapper mapper = new ObjectMapper();
    
            logger.info("最终通知" + className + ":" + methodName + " 传递的参数:" + mapper.writeValueAsString(array));
        }
        @AfterReturning("myPointcut2()")
        public void myLogger4(JoinPoint pjp) throws Throwable {
            String className = pjp.getTarget().getClass().toString();
            String methodName = pjp.getSignature().getName();
            Object[] array = pjp.getArgs();
            ObjectMapper mapper = new ObjectMapper();
            logger.info("返回后通知" + className + ":" + methodName + " 传递的参数:" + mapper.writeValueAsString(array));
        }
        @AfterThrowing("myPointcut2()")
        public void myLogger5(JoinPoint pjp) throws Throwable {
            String className = pjp.getTarget().getClass().toString();
            String methodName = pjp.getSignature().getName();
            Object[] array = pjp.getArgs();
            ObjectMapper mapper = new ObjectMapper();
            logger.info("异常通知" + className + ":" + methodName + " 传递的参数:" + mapper.writeValueAsString(array));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    测试

    对于hello和bye调用时的情况。
    输入的参数:
    hello
    在这里插入图片描述
    bye
    在这里插入图片描述
    输出的日志:
    hello
    在这里插入图片描述
    bye
    在这里插入图片描述

    当产生异常时,各个通知的情况
    参数:
    在这里插入图片描述
    日志:
    在这里插入图片描述
    可以看到,产生日常后环绕通知中再方法执行后的日志不再输出,而且返回后通知也不再执行。

    以上就是关于AOP的详细知识,以及demo的实现,感谢你的阅读,希望对你有所帮助,有什么建议可以在评论区留言

  • 相关阅读:
    Jina AI正式将DocArray捐赠给Linux基金会
    Java之常用类
    我的第一个Spring MVC应用的过程与解释
    Unity VSCode一些插件
    如何安装 Elasticsearch
    LayUi之用户(URUD)
    【linux编程】linux文件IO高级I/O函数介绍和代码示例
    java集合类史上最细讲解 - HashMap篇
    Android中 BufferQueue 和 Gralloc
    C++ :输出 Hello, World
  • 原文地址:https://blog.csdn.net/qq_45515347/article/details/126697441