Spring 之 AOP 原理详解

news2025/3/1 9:48:18

        Spring 是一个流行的 Java 企业应用程序开发框架。其中的 AOP(面向切面编程)是 Spring 框架中的一个核心概念。本文将介绍 Spring AOP 的底层实现原理,并通过源代码解析来详细阐述其实现过程。

什么是AOP?

         AOP是一种编程范式,用于在不修改原始代码的情况下向现有应用程序添加新功能。这种编程方式将应用程序分成许多独立的部分,称为切面。这些切面可以在应用程序的不同位置进行编写和维护,从而提高了应用程序的可重用性和可维护性

         AOP主要用于实现横切关注点(Cross-Cutting Concerns),例如日志记录、性能监测、事务管理等。通过AOP,我们可以将这些关注点与应用程序的其他部分分离开来,从而使应用程序更加模块化和易于维护。

实现原理

Spring AOP 的实现原理是基于动态代理字节码操作的。

        在编译时, Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件。在运行时, Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP的功能。

         Spring AOP 可以使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;否则,使用 CGLIB 代理。下面分别介绍这两种代理方式的实现原理。

JDK动态代理

        JDK 动态代理是 Java 自带的动态代理实现方式。使用JDK动态代理时,需要目标对象实现至少一个接口。JDK 动态代理会在运行时生成一个实现了目标对象接口的代理类,该代理类会在目标对象方法执行前后插入切面代码。

下面是JDK动态代理的实现代码:

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(
                getClass().getClassLoader(),
                advised.getTargetSource().getInterfaces(),
                this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
        MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
                advised.getTargetSource().getTarget(),
                method,
                args,
                methodInterceptor,
                advised.getTargetSource().getTargetClass()
        );
        return methodInvocation.proceed();
    }
}
复制代码

        在该代码中,JdkDynamicAopProxy 类实现了 AopProxy 和 InvocationHandler 接口。getProxy 方法返回一个代理对象,该代理对象实现了目标对象实现的所有接口。invoke 方法用于执行代理方法,该方法会在目标对象方法执行前后插入切面代码。

CGLIB 代理

        CGLIB 代理是一个基于字节码操作的代理方式,它可以为没有实现接口的类创建代理对象。CGLIB 代理会在运行时生成一个目标对象的子类,并覆盖其中的方法,以实现AOP的功能。

下面是 CGLIB 代理的实现代码:

public class CglibAopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public CglibAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        private final AdvisedSupport advised;

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            MethodInvocation methodInvocation = new CglibMethodInvocation(
                    advised.getTargetSource().getTarget(),
                    method,
                    args,
                    proxy,
                    advised.getMethodInterceptor(),
                    advised.getTargetSource().getTargetClass()
            );
            return methodInvocation.proceed();
        }
    }
}

复制代码

        在该代码中,CglibAopProxy 类实现了 AopProxy 接口。getProxy 方法返回一个代理对象,该代理对象是目标对象的子类,并覆盖了其中的方法。DynamicAdvisedInterceptor 类实现了 MethodInterceptor 接口,用于在目标对象方法执行前后插入切面代码。

AOP使用示例

        在了解了 Spring AOP的实现原理后,我们来看一下 Spring AOP的源码实现。Spring AOP的源码位于org.Springframework.aop包下,其中涉及到的类有:

Advised:一个包含切面信息的接口,用于描述切面的配置信息。

AdvisedSupport:Advised接口的默认实现类,包含了切面的配置信息。

AopProxy:AOP代理的接口,用于获取代理对象。

CglibAopProxy: CGLIB 代理的实现类。

JdkDynamicAopProxy:JDK动态代理的实现类。

MethodInvocation:方法调用的接口,用于封装目标对象方法的调用过程。

ReflectiveMethodInvocation:MethodInvocation接口的默认实现类,用于调用目标对象方法。

MethodInterceptor:方法拦截器的接口,用于实现切面的具体逻辑。

ProxyFactory:代理工厂,用于创建代理对象。

下面是一个使用 Spring AOP的示例代码:

@Service
public class UserServiceImpl implements UserService {

    @Override
    @LogAnnotation
    public void addUser(String username, String password) {
        System.out.println("addUser");
    }
}

复制代码

在该代码中,UserServiceImpl 类实现了 UserService 接口,并在 addUser 方法上添加了@LogAnnotation 注解。@LogAnnotation 注解是一个自定义注解,用于标记需要记录日志的方法。

下面是 @LogAnnotation 注解的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
}
复制代码

在使用 Spring AOP时,我们需要定义一个切面类,用于实现@LogAnnotation注解的具体逻辑。下面是一个定义了@LogAnnotation注解的切面类:

@Aspect
@Component
public class LogAspect {

    @Pointcut("@annotation(com.example.demo.annotation.LogAnnotation)")
    public void logPointcut() {}

    @Before("logPointcut()")
    public void before(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getName();
        System.out.println("before " + methodName);
    }

    @After("logPointcut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getName();
        System.out.println("after " + methodName);
    }
}
复制代码

        在该代码中,LogAspect 类使用 @Aspect 注解标记为切面类,并使用 @Component 注解将其注册为 Spring 的 Bean。

logPointcut 方法使用 @Pointcut 注解定义了切点,该切点匹配所有标记了@LogAnnotation 注解的方法。

before 方法和 after 方法分别使用 @Before 和 @After 注解定义了前置通知和后置通知。在 before 方法和 after 方法中,我们可以编写具体的日志记录逻辑。

下面是创建代理对象的代码:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class);
userService.addUser("test", "test");
复制代码

在该代码中,我们使用 ApplicationContext 从配置文件中获取了 UserServiceImpl 的 Bean,并调用了 addUser 方法。由于 addUser 方法标记了 @LogAnnotation 注解,因此 Spring 会自动将 LogAspect 类中定义的切面逻辑插入到该方法中。

总结

本文介绍了 Spring AOP 的实现原理,并通过源代码解析详细阐述了其实现过程。在使用 Spring AOP时,我们需要定义切面类,并为需要实现AOP的方法添加注解。 Spring 会自动将切面逻辑插入到这些方法中,从而实现AOP的功能。

 

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

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

相关文章

three.js之自定义一个正方体(网格)

本节主要通过自定义顶点和平面的方式,创造一个立方体。真正的开始走近three.js。 效果图 坐标系 坐标系支持右手定则。图中红色是x轴,绿色是y轴,蓝色是z轴 源码 引入的插件js【本人的csdn也有下载资源,如果打不开git可以在csd…

AI网站汇总(免费chatgpt)(60个持续增加中)

本文总结了6大类AI工具,包括:聊天AI、绘画AI、AI提示词、图像处理、UI设计和3D设计,汇总60个AI网站,一键收藏。 目录 一、聊天AI 二、绘画AI 三、AI提示词 四、图像处理

SQL笔记(2)——MySQL的表操作与索引(收藏吃灰版)

本文详细记录如何通过命令的方式修改MySQL的表结构,例如新增列、删除列等;不止学会了,你还学懂了,收藏吃灰~ 开始之前 上一篇文章创建了一些表,ER图如下。本文针对score表进行操作,场景就是新增一个备注rem…

自动化测试面试一周拿到3个offer,只因为我记下了这个文档

目录 一、接口测试基础 二、 接口测试工具 三、自动化测试 四、自动化测试工具 五、总结 一、接口测试基础 1、公司接口测试流程是什么? 从开发那边获取接口设计文档、分析接口并进行用例设计、并提前录入到接口测试工具 jmeter,等开发那边进行…

客户关系管理小程序实战教程01-需求分析

日常企业经常需要在网上拓展业务,通过互联网工具来宣传自己的产品。用户在看到企业宣传的内容后,如果有需要就会通过各种方式联系到企业。 为了方便的跟踪这些销售的机会,我们开发一款企业内部销售团队使用的小程序,便于管理潜在…

Win11的两个实用技巧系列之磁盘分区后再恢复的方法、调高进程的优先级方法

Win11磁盘分区后怎么恢复到分区前?Win11磁盘分区后在恢复的方法 很多人不知道win11磁盘分区怎么恢复回去?今日为你们带来的文章是win11磁盘分区的恢复方法,还有不清楚小伙伴和小编一起去学习一下吧 有不少小伙伴在使用电脑的时候经常会根据自身需求对其进行磁盘的…

【CSS】轮播图案例开发 ( 基本设置 | 子绝父相 | 浏览器水平居中 | 圆角设置 | 绝对定位居中设置 )

文章目录一、开发要点1、基本设置 - 取消默认内外边距 / 取消基本样式 / 图片自适应2、外层父容器设置 - 子绝父相 / 盒子浏览器水平居中 / 设置圆角 / 设置溢出隐藏3、左右按钮设置 - 绝对定位垂直居中设置 / 使用圆角矩形设置半圆 / 文字垂直居中4、底部小圆点设置 - 绝对定位…

Web_python_template_injection(Python模块注入)

打开链接,提示是Python的模块注入 我们先了解一些基本概念: 模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容…

智慧停车场解决方案,停车场导航技术怎么实现

停车场导航技术怎么实现?随着城市化的不断发展,停车场建的越来越大,同时也越来越复杂,停车、找车成为很多人感到十分头疼的问题。在这种情况下,一个高效的停车场电子地图应用已经成为城市交通管理中不可缺少的组成部分…

架构设计三原则

作为程序员,很多人都希望成为一名架构师,但并非简单地通过编程技能就能够达成这一目标。事实上,优秀的程序员和架构师之间存在一个明显的鸿沟——不确定性。 编程的本质是确定性的,也就是说,对于同一段代码&#xff0c…

【Java虚拟机】JVM类加载机制和双亲委派模型

文章目录1.JVM虚拟机类加载子系统2.双亲委派机制和JDK9模块化系统3.ClassLoader源码解读和自定义类加载器场景4.自定义ClassLoader类加载器案例实战5.不同类加载器加载同个class类1.JVM虚拟机类加载子系统 (1)什么是类加载子系统 是Java虚拟机的一个重…

MinIO基础教程

MinIO 1.MinIO安装 Minio 是个基于 Golang 编写的开源对象存储服务,存储非结构化数据,如:图片,视频,音乐等 官网地址:https://min.io/ 中文地址:http://minio.org.cn 官网文档( …

【LeetCode: 300. 最长递增子序列 | 暴力递归=>记忆化搜索=>动态规划】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

【Vue】收集表单数据 过滤器

收集表单数据 收集表单数据&#xff1a; 若&#xff1a;<input type"text"/>,则v-model收集的是value值&#xff0c;用户输入的就是value值若&#xff1a;<input type"radio"/>,则v-model收集的是value值&#xff0c;且要给标签配置value值若…

树莓派与STM32(rt1064)串口通信

目录 一、树莓派通信 1、硬件连线准备 2、安装Serial和打开树莓派串口 2.1安装Serial 2.2打开树莓派串口 2.3修改串口映射关系 3、树莓派代码 4、上位机 5、运行uart.py代码进行测试 5.1 树莓派发送&#xff0c;上位机接收 5.2上位机发送&#xff0c;树莓派接收 二、…

HopeHomi脚手架(四)redis、redisson模块

项目结构 Redis RedisSpiModuleImport 基于SPI。在项目启动的时候返回待加载类名 public class RedisSpiModuleImport implements SpiEnvironmentModuleImport {Overridepublic String[] readyImportClassName() {return new String[]{RedisConfiguration.class.getName()};…

裸机配置Java环境,解决 -bash: jps: command not found

目录 配置JDK 1、第一步&#xff1a;使用yum命令查找JDK 2、第二步&#xff1a;执行安装命令 3、第三步&#xff1a;验证是否安装成功 4、第四步&#xff1a;验证是否可用 5、第五步&#xff1a;安装开发环境 6、第六步&#xff1a;配置环境变量 今天申请了公司的开发机器&…

【openGauss实战10】备份与恢复

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

视觉SLAM ch12 建图(RGB-D)

一、RGB-D稠密建图 RGB-D相机通结构光和飞行时间获取深度。 稠密重建方法&#xff1a;根据估计的相机位姿&#xff0c;将RGB-D数据转化为点云&#xff0c;然后进行拼接&#xff0c;最终得到由离散的点组成的点云地图。 在此基础上&#xff0c;如果希望估计物体的表面&#x…

Python 实验二 Python语言基础

1.运用输入输出函数编写程序&#xff0c;将华氏温度转换成摄氏温度。换算公式&#xff1a;C(F-32)*5/9,其中 C为摄氏温度&#xff0c;F为华氏温度。 Ffloat(input("请输入你要转换的华氏温度&#xff1a;")) C(F-32)*5/9 print("转换为摄氏温度为&#xff1a;&…