【Spring】Spring AOP 初识及实现原理解析

news2024/11/15 8:43:34

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE进阶 

目录

文章目录

一、初识AOP

1.1 什么是AOP?

1.2 AOP的组成

1.2.1 切面(Aspect)

1.2.2 切点(Pointcut)

1.2.3 连接点(Join Point)

1.2.4 通知(Advice)

1.3 AOP的使用场景

二、Srping AOP 实现

2.1 添加Spring AOP 依赖

2.2 定义切面和切点

2.3 定义通知

三、Spring AOP 实现原理

3.1 什么是动态代理?

3.2 JDK 动态代理实现

3.3 CGLIB 动态代理实现

3.4 JDK 和 CGLIB 实现的区别


一、初识AOP

1.1 什么是AOP?

AOP(Aspect Oriented Programming):面向切面编程,它是⼀种思想,它是对某⼀类事情的
集中处理。
在我们想要对某一件事情进行集中处理,就可以使用到AOP,它提供一种将程序中的横切关注点模块化的方式。在 AOP 中,我们将这些横切关注点称为“切面”,它们独立于业务逻辑模块,但是可以在程序运行的不同阶段被织入到业务逻辑中。

简单来说,AOP 就是对某一件事进行集中处理的思想方式~

1.2 AOP的组成

1.2.1 切面(Aspect)

切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包
括了连接点的定义。相当于处理某方面具体问题的一个类,包含多个方法,而这些方法就是切点和通知。

1.2.2 切点(Pointcut)

Pointcut 的作⽤就是提供⼀组规则来匹配连接点(Join Point),给满足规则的连接点添加通知(Advice),可以理解为用来进行主动拦截的规则(配置)

1.2.3 连接点(Join Point)

应⽤执⾏过程中能够插⼊切⾯的⼀个点,连接点可以理解为可能会触发AOP规则的所有点。(所有请求)

1.2.4 通知(Advice)

在AOP术语中,切面的工作被称之为通知。通知是切面在连接点上执行的动作。它定义了在何时(例如在方法调用之前或之后)以及如何(例如打印日志或进行性能监控)应用切面的行为。即,程序中被拦截请求触发的具体动作。

Spring 切⾯类中,可以在方法上使⽤以下注解,会设置⽅法为通知方法,在满⾜条件后会通知本
⽅法进⾏调⽤:

  1. 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执行。
  2. 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  3. 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  4. 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  5. 环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执行⾃定义的行为。

1.3 AOP的使用场景

在做任何一个系统都需要登录功能,那么几乎想要使用这个系统都需要我们进行验证用户登录状态,我们之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本。对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP来统⼀处理了。

 

除了统一登录判断外,使用AOP还可以实现:

  • 用户登录验证
  • 统⼀⽇志记录
  • 统⼀⽅法执⾏时间统计
  • 统⼀的返回格式设置
  • 统⼀的异常处理
  • 事务的开启和提交等

二、Srping AOP 实现

Spring AOP 的实现步骤如下:

  1. 添加 Spring AOP 框架⽀持
  2. 定义切⾯和切点:(1)创建切面类(2)配置拦截规则
  3. 定义通知

2.1 添加Spring AOP 依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-bo
ot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.2 定义切面和切点

使用 @Aspect 注解表明当前类为一个切面,而在切点中,我们要定义拦截的规则,具体实现如下:

@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {

    // 定义切点(配置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){

    }
}

在上述实现代码中,pointcut 为一个空方法,只是起到一个“标识”的作用,标识下面的通知方法具体指的是哪个切点,切点可以有多个。

切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
修饰符和异常可以省略

常见的切点表达式的示例:

  • 匹配特定类的所有方法:
  • execution(* com.example.MyClass.*(..)):匹配 com.example.MyClass 类中的所有方法。
  • 匹配特定包下的所有方法:
  • execution(* com.example.*.*(..)):匹配 com.example 包及其子包下的所有方法。
  • 匹配特定方法名的方法:
  • execution(* com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 类中名为 myMethod 的方法。
  • 匹配特定方法参数类型的方法:
  • execution(* com.example.MyClass.myMethod(String, int)):匹配 com.example.MyClass 类中具有一个 String 参数和一个 int 参数的 myMethod 方法。
  • 匹配特定返回类型的方法:
  • execution(String com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 类中返回类型为 String 的 myMethod 方法。
package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/hi")
    public String sayHi(String name){
        System.out.println("执行了Hi");
        return "Hi," + name;
    }

    @RequestMapping("/hello")
    public String sayHello(){
        System.out.println("执行了Hello");
        return "Hello,world";
    }
}

2.3 定义通知

通知定义的是被拦截方法具体要执行的业务。我们上面列出了可以使用哪些通知~这里举出例子

package com.example.demo.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {


    // 定义切点(配置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){

    }

    @Before("pointcut()")
    public void beforeAdvice(){
        System.out.println("执行了前置通知~");
    }

    @After("pointcut()")
    public void AfterAdvice(){
        System.out.println("执行了后置通知~");
    }

    /**
     * 环绕通知
     * @param joinPoint
     * @return
     */
    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint){
        System.out.println("进入了环绕通知~");
        Object obj = null;
        try {
        // 执⾏拦截⽅法
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("退出了环绕通知~");
        return obj;
    }

}

环绕通知是在前置通知之前和后置通知之后运行的~

  

三、Spring AOP 实现原理

Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有两种:JDK Proxy 和 CGLIB因此,Spring 对 AOP 的支持局限于方法级别的拦截。

  1. 默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类
  2. 没有实现接⼝的类,会基于 CGLIB ⽣成代理类
     

3.1 什么是动态代理?

动态代理(Dynamic Proxy)是一种设计模式,它允许 在运行时创建代理对象,并将方法调用转发给实际的对象。 动态代理可以用于实现横切关注点(如日志记录、性能监控、事务管理等)的功能,而无需修改原始对象的代码。

在Java中,动态代理通常使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。

调用者在调用方法时,会先转发给代理类创建的代理对象,随后再由代理对象转发给目标对象。

以下是使用动态代理的一般步骤:

  1. 创建一个实现InvocationHandler接口的类,该类将作为代理对象的调用处理程序。在InvocationHandler接口的invoke方法中,可以定义在方法调用前后执行的逻辑。
  2. 使用Proxy类的newProxyInstance方法创建代理对象。该方法接受三个参数:类加载器、代理接口数组和调用处理程序。它将返回一个实现指定接口的代理对象。
  3. 使用代理对象调用方法。当调用代理对象的方法时,实际上会调用调用处理程序的invoke方法,并将方法调用转发给实际的对象。
     

3.2 JDK 动态代理实现

先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代理类。

import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {
    //⽬标对象即就是被代理对象
    private Object target;
    public PayServiceJDKInvocationHandler( Object target) {
        this.target = target;
    }
    //proxy代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //1.安全检查
        System.out.println("安全检查");
    //2.记录⽇志
        System.out.println("记录⽇志");
    //3.时间统计开始
        System.out.println("记录开始时间");
    //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);
    //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
    public static void main(String[] args) {
        PayService target= new AliPayService();
    //⽅法调⽤处理器
        InvocationHandler handler =
                new PayServiceJDKInvocationHandler(target);
    //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler
        );
        proxy.pay();
    }
}

3.3 CGLIB 动态代理实现

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;

public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    //被代理对象
    private Object target;
    public PayServiceCGLIBInterceptor(Object target){
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, Method
            Proxy methodProxy) throws Throwable {
//1.安全检查
        System.out.println("安全检查");
//2.记录⽇志
        System.out.println("记录⽇志");
//3.时间统计开始
        System.out.println("记录开始时间");
//通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
    public static void main(String[] args) {
        PayService target= new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),n
                ew PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

3.4 JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接口, 之后是通过 InvocationHandler 及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。
  2. CGLIB 实现,被代理类可以不实现接口, 是通过继承被代理类,在运⾏时动态的⽣成代理类对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/839634.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OKR管理策略:为开发团队注入动力

引言 在这个快速变化的世界中&#xff0c;公司需要迅速应对市场变化&#xff0c;并保持其目标和战略的清晰性和一致性。而OKR&#xff08;Objectives and Key Results&#xff09;正是这个挑战的解决方案之一。OKR的实施可以帮助开发团队明确目标&#xff0c;关注关键结果&…

java使用openOffice将excel转换pdf时,将所有列显示在一页

1.接上文&#xff0c;格式转换的基础问题已解决&#xff0c;但还有些细节问题需要单独处理&#xff0c;如excel转换至pdf时&#xff0c;如何将所有列显示在一页的问题&#xff0c;此问题大家都有遇到&#xff0c;解决方案也比较多&#xff0c;我也尝试过重写某类&#xff0c;来…

arcgis--网络分析(理论篇)

1、定义概念 &#xff08;1&#xff09;网络&#xff1a;由一系列相互联通的点和线组成&#xff0c;用来描述地理要素&#xff08;资源&#xff09;的流动情况。 &#xff08;2&#xff09;网络分析&#xff1a;对地理网络&#xff08;如交通网络、水系网络&#xff09;&…

Emacs之set-face-attribute与font-lock-add-keywords用法区别(一百二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

怎样将项目jar包放到服务器上

目录 1、在配置文件中配置账号密码 2.在父级的pom里面&#xff0c;加上这个标签 3. deploy部署 4. 注&#xff1a;这两个id得匹配上&#xff08;原因&#xff1a;有的人会只有上传到测试包的权限&#xff0c;id对应&#xff0c;拥有账号密码的才能有权限&#xff09; 5.子项…

Milvus Cloud凭借AI原生,可视化优势荣登全球向量数据库性能排行榜VectorDBBench.com 榜首

在当今的大数据时代,随着人工智能技术的快速发展,向量数据库作为处理大规模数据的关键工具,其性能和效率越来越受到关注。最近,全球向量数据库性能排行榜 VectorDBBench.com 公布了一份最新的评估报告,引人瞩目的是,成立不到一年的新兴公司 Milvus Cloud 凭借其 AI 原生和…

leetcode 1290.二进制链表转整数

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;二进制链表转整数 ps&#xff1a; 1 0 1 (0 << 1) 1 0 1 1 (1 << 1) 0 2 0 2 (2 << 1) 1 4 1 5代码&#xff1a; int getDecimalValue(struct ListNode* head) {int ans 0;while(head !…

MySQL — InnoDB介绍

文章目录 InnoDB 主要特点InnoDB 架构In-Memory StructuresBuffer PoolChange BufferAdaptive Hash IndexLog Buffer On-Disk StructuresSystem TablespaceFile-Per-Table TablespacesGeneral TablespacesUndo TablespacesTemporary TablespacesDoublewrite BufferRedo LogUndo…

spring boot中web容器配置

web容器配置 spring boot 默认的web容器是 tomcat&#xff0c;如果需要换成其他的 web 容器&#xff0c;可以如下配置。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 默…

ROS添加节点

1 下载项目源码 &#xff08;1&#xff09;这里我使用是哔哩哔哩的博主源码机器人工匠王杰 https://github.com/6-robot/wpr_simulation.git &#xff08;2&#xff09;建立工作空间 在主目录下载建立如下文件夹 catkin_ws----       ----src &#xff08;3&#xff09;…

Spring Boot、Spring Cloud、Spring Alibaba 版本对照关系及稳定兼容版本

Spring Boot、Spring Cloud、Spring Alibaba 版本对照关系及稳定兼容版本 引言 在 Java 生态系统中&#xff0c;Spring Boot、Spring Cloud 和 Spring Alibaba 是非常流行的框架&#xff0c;它们提供了丰富的功能和优雅的解决方案。然而&#xff0c;随着不断的发展和更新&…

【51单片机】晨启科技,酷黑版,音乐播放器

四、音乐播放器 任务要求&#xff1a; 设计制作一个简易音乐播放器&#xff08;通过手柄板上的蜂鸣器发声&#xff0c;播放2到4首音乐&#xff09;&#xff0c;同时LED模块闪烁&#xff0c;给人视、听觉美的感受。 评分细则&#xff1a; 按下播放按键A6开始播放音乐&#xff0…

使用Socket实现TCP版的回显服务器

文章目录 1. Socket简介2. ServerSocket3. Socket4. 服务器端代码5. 客户端代码 1. Socket简介 Socket&#xff08;Java套接字&#xff09;是Java编程语言提供的一组类和接口&#xff0c;用于实现网络通信。它基于Socket编程接口&#xff0c;提供了一种简单而强大的方式来实现…

SpringBoot项目上传至服务器

1.服务器安装JDK1.8 通过包管理器安装 2.服务器安装数据库 参考链接&#xff1a; CentOS 7 通过 yum 安装 MariaDB - 知乎 1. 安装之后没有密码&#xff0c;所以需要设置密码&#xff0c;使用下面的语句 set password for rootlocalhost password(111111); 2.在数据库中建…

子集 II——力扣90

文章目录 题目描述题目描述 class Solution {public:vector<int> t;vector<vector<int>> ans;vector<vector

康冠医疗2021笔试题

笔试时间:2020.09.24。 岗位:嵌入式软件工程师。 题型:13道题,40分钟。 6道填空,2道简答,5道编程,时间紧任务重。 1、填空 4、考察extern关键字。 6、const可以用来代替define ,define 只是简单的代替,但是const还会进行类型检查。 怎么避免头文件重复包含: #…

前端 页面地址栏修改Url参数后回车 路由页面不刷新!

关于&#xff1a;页面地址栏修改Url参数后回车 路由页面不刷新的问题 需要回车两次才会触发刷新 重新加载 原因&#xff1a;因为你的路由Url是相同的&#xff0c;vue会认为你是同一个页面&#xff0c;从而复用已加载的页面 从缓存里面拿&#xff0c;而不会重新加载&#xff0c;…

关于java异常的整理

文章目录 一、异常分类二、throw、throws、try-catch-finally三、CglibAopProxy中对异常的处理4、关于UndeclaredThrowableException 一、异常分类 java异常层级结构 Throwable:所有异常的根接口 Error:严重错误,程序无法处理和恢复 例如VirtualMachineError,OOMError等 Excep…

React相关学习导引

目录 扩展学习资料 什么是React&#xff1f; 什么是Component 组件树结构 虚拟dom 为何要选React 选择React React生态 为何选择React 学习目录 思考一下~ 扩展学习资料 Awsome React知识体系 https://github.com/enaqx/awesome-react 了解React生态&#xff0c…

外边距合并可能产生的问题

外边距合并可能产生的问题&#xff0c;可能会出现合并和塌陷问题 1、这种现象&#xff0c;先要准备两个盒子&#xff1a; 2、 父元素往下走了20px 3、让孩子也走一走 4、父元素和子元素同时存在了margin的情况&#xff0c;就会出现塌陷的情况产生&#xff0c;不能都使用margin:…