Springboot AOP 详解

一、测试实例

// 一:拦截有所controller层请求切面定义,注意包路径package com.example.MySpringBootTest;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ControllerAspect {@Pointcut("execution(* com.example.MySpringBootTest.controller.*.*(..))")public void controllerMethods() {System.out.println("this is ControllerAspect controllerMethods");}@Before("controllerMethods()")public void beforeControllerMethod(JoinPoint joinPoint) {System.out.println("Before executing controller method: " + joinPoint.getSignature().getName());}@After("controllerMethods()")public void afterControllerMethod(JoinPoint joinPoint) {System.out.println("After executing controller method: " + joinPoint.getSignature().getName());}}// 二:拦截有所service层请求切面定义,注意包路径package com.example.MySpringBootTest;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ServiceAspect {@Pointcut("execution(* com.example.MySpringBootTest.service.impl.*.*(..))")public void serviceMethod() {}@Before("serviceMethod()")public void beforeMethond() {System.out.println("sevice before method .");}@After("serviceMethod()")public void afterMethod() {System.out.println("service after method .");}@Around("serviceMethod()")public Object aroundControllerMethod(ProceedingJoinPoint pjp) throws Throwable {System.out.println("Around advice - Before calling target method");Object result = pjp.proceed();System.out.println("Around advice - After calling target method");return result;}
}// 三:定义业务service,注意包路径,跟拦截器路径要保持一致,不然拦截器不生效package com.example.MySpringBootTest.service.impl;import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;@Service
public class Userservice {public String addUser() {System.out.println("Adding user exec .");return "addUser return...";}}// 四:测试结果Before executing controller method: user
Around advice - Before calling target method
sevice before method .
Adding user exec .
service after method .
Around advice - After calling target method
addUser return...
After executing controller method: user

二、原理说明

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许开发者在不修改原有业务逻辑的基础上,对程序进行增强,如日志记录、事务管理、权限验证等。下面详细介绍 AOP 的原理及底层实现。

AOP 核心概念

在深入了解 AOP 原理之前,需要先了解几个核心概念:

  • 切面(Aspect):一个切面可以包含多个通知和切入点,它是横切关注点的模块化,例如日志记录、事务管理等都可以定义为一个切面。
  • 通知(Advice):通知定义了在目标方法执行的不同时机要执行的操作,常见的通知类型有 @Before(前置通知)、@After(后置通知)、@Around(环绕通知)、@AfterReturning(返回通知)和 @AfterThrowing(异常通知)。
  • 切入点(Pointcut):切入点用于定义哪些方法会被增强,通过表达式来匹配目标方法,例如 execution(* com.example.service.*.*(..)) 表示匹配 com.example.service 包下所有类的所有方法。
  • 连接点(Join Point):连接点是程序执行过程中可以插入切面的点,在 Java 中通常指方法的执行。
  • 目标对象(Target Object):被一个或多个切面增强的对象。
  • 代理对象(Proxy Object):通过 AOP 框架创建的包含增强逻辑的对象,客户端调用的实际上是代理对象。

AOP 原理

AOP 的核心原理是通过代理模式,在目标对象的基础上创建一个代理对象,将切面逻辑织入到目标对象的方法执行过程中。当客户端调用代理对象的方法时,代理对象会在目标方法执行的前后或周围插入切面逻辑,从而实现对目标方法的增强。

三、AOP 底层实现方式

1. JDK 动态代理

  • 原理:JDK 动态代理是基于接口的代理方式,它通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。当使用 JDK 动态代理时,目标对象必须实现至少一个接口,代理对象会实现相同的接口,从而可以替代目标对象。
  • 示例代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 定义接口
interface Subject {void request();
}// 实现接口的目标对象
class RealSubject implements Subject {@Overridepublic void request() {System.out.println("RealSubject: Handling request.");}
}// 实现 InvocationHandler 接口的代理处理器
class ProxyHandler implements InvocationHandler {private Object target;public ProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method execution");Object result = method.invoke(target, args);System.out.println("After method execution");return result;}
}// 测试代码
public class JdkProxyExample {public static void main(String[] args) {RealSubject realSubject = new RealSubject();ProxyHandler proxyHandler = new ProxyHandler(realSubject);Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class<?>[]{Subject.class},proxyHandler);proxySubject.request();}
}

  • 优缺点
    • 优点:基于 JDK 原生实现,无需额外依赖,性能较高。
    • 缺点:只能代理实现了接口的类,无法代理普通类。

Springboot 工作流程说明

工作流程概述

当开启 Spring AOP 功能后,Spring 会在容器初始化时为需要增强的目标对象创建代理对象。在客户端调用这些目标对象的方法时,实际上调用的是代理对象的对应方法。代理对象会根据 AOP 配置的切面逻辑,在目标方法执行前后插入相应的通知(如前置通知、后置通知等),然后再调用真实对象的方法,从而实现对真实对象方法的增强。

详细说明
1. 代理对象的创建

Spring 会根据目标对象是否实现接口来选择不同的代理方式:

  • JDK 动态代理:如果目标对象实现了接口,Spring 会使用 JDK 动态代理创建代理对象。代理对象会实现与目标对象相同的接口,客户端通过接口类型引用代理对象。
  • CGLIB 代理:如果目标对象没有实现接口,Spring 会使用 CGLIB 代理创建代理对象。CGLIB 代理通过继承目标类来创建代理对象,客户端可以通过目标类类型引用代理对象。
2. 调用过程示例

以下是一个简单的 Spring Boot AOP 示例,展示代理对象的调用过程:

定义接口和实现类

// 定义接口
public interface UserService {void saveUser();
}// 实现接口的目标类
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService {@Overridepublic void saveUser() {System.out.println("Saving user...");}
}

定义切面类

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class UserServiceAspect {// 定义切入点@Pointcut("execution(* com.example.service.UserService.*(..))")public void userServiceMethods() {}// 前置通知@Before("userServiceMethods()")public void beforeSaveUser() {System.out.println("Before saving user...");}// 后置通知@After("userServiceMethods()")public void afterSaveUser() {System.out.println("After saving user...");}
}

测试调用

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication implements CommandLineRunner {@Autowiredprivate UserService userService;public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}@Overridepublic void run(String... args) throws Exception {// 调用代理对象的方法userService.saveUser();}
}
3. 调用过程分析
  • 当 DemoApplication 中的 userService.saveUser() 被调用时,由于 UserService 是一个接口,Spring 会使用 JDK 动态代理创建一个实现了 UserService 接口的代理对象。
  • 代理对象在接收到 saveUser 方法调用后,会先执行 UserServiceAspect 中定义的前置通知 beforeSaveUser 方法。
  • 接着,代理对象会调用真实的 UserServiceImpl 对象的 saveUser 方法。
  • 最后,代理对象会执行 UserServiceAspect 中定义的后置通知 afterSaveUser 方法。
总结

通过使用代理对象,Spring AOP 可以在不修改目标对象代码的情况下,灵活地在目标方法执行前后插入自定义的增强逻辑,实现诸如日志记录、事务管理、权限验证等功能。

2.  CGLIB 代理

1. CGLIB 代理原理概述

CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它被广泛用于实现 AOP 中的代理机制。CGLIB 代理的核心原理是通过字节码技术,在运行时动态生成目标类的子类,并重写目标类的方法,在重写的方法中插入额外的增强逻辑,从而实现对目标类方法的代理。与 JDK 动态代理不同,JDK 动态代理要求目标对象必须实现接口,而 CGLIB 代理可以直接对普通类进行代理,因为它是通过继承目标类来实现的。

2. 底层实现细节

CGLIB 主要基于以下几个关键类和技术来实现代理:

  • Enhancer 类:这是 CGLIB 中用于创建代理对象的核心类。它允许开发者指定要代理的目标类、回调接口(用于处理方法调用)等信息,然后通过 create() 方法创建代理对象。
  • MethodInterceptor 接口:这是一个回调接口,当代理对象的方法被调用时,会触发 MethodInterceptor 接口的 intercept 方法。在 intercept 方法中,开发者可以编写自定义的增强逻辑,并决定是否调用目标对象的原始方法。
  • 字节码生成技术:CGLIB 使用字节码生成库(如 ASM)来动态生成代理类的字节码。在运行时,它会根据目标类的信息和开发者指定的回调逻辑,生成一个新的子类字节码,并加载到 JVM 中。

3. 用例说明

3.1 添加依赖

如果你使用的是 Maven 项目,需要在 pom.xml 中添加 CGLIB 依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.4.0</version>
</dependency>
3.2 定义目标类
// 目标类,没有实现任何接口
public class TargetClass {public void doSomething() {System.out.println("TargetClass is doing something.");}
}
3.3 实现 MethodInterceptor 接口
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;// 实现 MethodInterceptor 接口,用于处理方法调用
public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 在目标方法执行前添加增强逻辑System.out.println("Before method execution");// 调用目标对象的原始方法Object result = proxy.invokeSuper(obj, args);// 在目标方法执行后添加增强逻辑System.out.println("After method execution");return result;}
}
3.4 创建代理对象并测试
import net.sf.cglib.proxy.Enhancer;public class CglibProxyExample {public static void main(String[] args) {// 创建 Enhancer 对象Enhancer enhancer = new Enhancer();// 设置要代理的目标类enhancer.setSuperclass(TargetClass.class);// 设置回调接口enhancer.setCallback(new MyMethodInterceptor());// 创建代理对象TargetClass proxy = (TargetClass) enhancer.create();// 调用代理对象的方法proxy.doSomething();}
}

4. 代码解释

  • Enhancer 类:用于创建代理对象。通过 setSuperclass 方法指定要代理的目标类,通过 setCallback 方法指定回调接口(即 MethodInterceptor 的实现类),最后调用 create 方法创建代理对象。
  • MyMethodInterceptor 类:实现了 MethodInterceptor 接口,在 intercept 方法中,我们可以在目标方法执行前后添加自定义的增强逻辑。proxy.invokeSuper(obj, args) 用于调用目标对象的原始方法。
  • 测试代码:创建代理对象并调用其方法,此时会触发 MyMethodInterceptor 的 intercept 方法,从而实现对目标方法的增强。

5. 注意事项

  • 由于 CGLIB 是通过继承目标类来实现代理的,所以目标类不能是 final 类,因为 final 类不能被继承。
  • 目标类中的 final 方法也不能被代理,因为 final 方法不能被重写。

四、两种代理方式比较

在 Java 中,JDK 动态代理和 CGLIB 代理是实现 AOP(面向切面编程)时常用的两种代理方式,下面从多个方面对它们进行比较。

1. 代理机制

  • JDK 动态代理
    • 基于接口实现。它通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建代理对象,代理对象会实现与目标对象相同的接口。客户端调用代理对象时,实际上是调用 InvocationHandler 的 invoke 方法,在该方法中可以实现对目标方法的增强。
  • CGLIB 代理
    • 基于继承实现。CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它通过字节码技术在运行时动态生成目标类的子类,并重写目标类的方法,在重写的方法中插入增强逻辑。

2. 适用场景

  • JDK 动态代理
    • 适用于目标对象实现了接口的情况。当目标对象有明确的接口定义时,使用 JDK 动态代理可以保证代码的规范性和可维护性,符合面向接口编程的原则。
  • CGLIB 代理
    • 适用于目标对象没有实现接口的情况。由于 CGLIB 是通过继承目标类来创建代理对象,所以可以对普通类进行代理,弥补了 JDK 动态代理只能代理接口的不足。

3. 性能表现

  • JDK 动态代理
    • 创建代理对象的速度较快,因为它是基于 Java 反射机制实现的,只需要在运行时动态生成代理类的字节码并加载到 JVM 中。但在方法调用时,由于需要通过反射来调用目标方法,会有一定的性能开销。
  • CGLIB 代理
    • 创建代理对象的速度相对较慢,因为它需要使用字节码生成技术动态生成目标类的子类字节码,涉及到复杂的字节码操作。但在方法调用时,由于是直接调用子类的方法,不需要通过反射,所以方法调用的性能较高。

4. 代码复杂度

  • JDK 动态代理
    • 代码相对简单,只需要实现 InvocationHandler 接口,并重写 invoke 方法即可。对于熟悉 Java 反射机制的开发者来说,容易理解和掌握。
  • CGLIB 代理
    • 代码稍微复杂一些,需要引入 CGLIB 库,并且要实现 MethodInterceptor 接口,在 intercept 方法中处理方法调用和增强逻辑。同时,还需要使用 Enhancer 类来创建代理对象。

5. 限制条件

  • JDK 动态代理
    • 只能代理实现了接口的类,无法对普通类进行代理。如果目标对象没有实现接口,使用 JDK 动态代理会抛出异常。
  • CGLIB 代理
    • 目标类不能是 final 类,因为 final 类不能被继承。同时,目标类中的 final 方法也不能被代理,因为 final 方法不能被重写。

6. Spring AOP 中的选择

  • Spring AOP 在默认情况下会根据目标对象是否实现接口来选择代理方式:
    • 如果目标对象实现了接口,Spring AOP 会使用 JDK 动态代理。
    • 如果目标对象没有实现接口,Spring AOP 会使用 CGLIB 代理。
    • 也可以通过配置强制使用 CGLIB 代理,例如在 Spring Boot 中,可以通过在配置类上添加 @EnableAspectJAutoProxy(proxyTargetClass = true) 注解来实现。

总结

JDK 动态代理和 CGLIB 代理各有优缺点,在实际开发中,需要根据具体的业务场景和需求来选择合适的代理方式。如果目标对象实现了接口,且对创建代理对象的速度要求较高,那么可以选择 JDK 动态代理;如果目标对象没有实现接口,或者对方法调用的性能要求较高,那么可以选择 CGLIB 代理。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tag标签的使用

一个非常适合运用在vue项目中的组件&#xff1a;Tag标签。 目录 一、准备工作 1、安装element-plus库 2、配置element-plus库 二、Tag标签入门 1、打开element官网&#xff0c;搜索tag标签 2、体验Tag标签的基础用法 三、Tag标签进阶训练1 1、定义一个数组&#xff0c;…

算法-图-数据结构(邻接矩阵)-BFS广度优先遍历

邻接矩阵广度优先遍历&#xff08;BFS&#xff09;是一种用于遍历或搜索图的算法&#xff0c;以下是具体介绍&#xff1a; 1. 基本概念 图是一种非线性的数据结构&#xff0c;由顶点和边组成&#xff0c;可分为无向图、有向图、加权图、无权图等。邻接矩阵是表示图的一种数…

Ryu:轻量开源,开启 SDN 新程

1. Ryu 控制器概述 定位&#xff1a;轻量级、开源的SDN控制器&#xff0c;专为开发者和研究人员设计&#xff0c;基于Python实现。开发者&#xff1a;由日本NTT实验室主导开发&#xff0c;遵循Apache 2.0开源协议。核心理念&#xff1a;简化SDN应用开发&#xff0c;提供友好的…

内容中台架构下智能推荐系统的算法优化与分发策略

内容概要 在数字化内容生态中&#xff0c;智能推荐系统作为内容中台的核心引擎&#xff0c;承担着用户需求与内容资源精准匹配的关键任务。其算法架构的优化路径围绕动态特征建模与多模态数据融合展开&#xff0c;通过深度强化学习技术实现用户行为特征的实时捕捉与动态更新&a…

【odoo18-文件管理】在uniapp上访问odoo系统上的图片

在uniapp上访问odoo系统上的图片 1、以url的形式访问 a&#xff1a;以odoo本身的域名&#xff0c;比如http://127.0.0.1:8069/web/image/product.template/3/image_128?unique1740380422000&#xff0c;这种方式需要解决跨域的问题。 b&#xff1a;以文件服务器的形式&…

DeepSeek掘金——基于DeepSeek-R1构建文档问答机器人

DeepSeek掘金——基于DeepSeek-R1构建文档问答机器人 在这个项目中,我们将结合本地 AI 的隐私与 Deepseek R1 的智能,创建一个完全本地化、推理驱动的问答机器人。 在人工智能 (AI) 日益融入我们日常生活的时代,一个问题仍然处于最前沿:隐私。尽管基于云的 AI 系统功能强大…

计算机毕业设计Hadoop+Spark+DeepSeek-R1大模型民宿推荐系统 hive民宿可视化 民宿爬虫 大数据毕业设计(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

将maya模型物体材质转化为面材质

将maya模型物体材质转化为面材质&#xff0c;以在导出abc时继承材质信息&#xff1b; 运行一下python代码&#xff1a; import maya.cmds as cmds objListcmds.ls(slTrue) for obj in objList:shapeNodeNamecmds.listRelatives(obj, shapesTrue)sgNodesListcmds.listConnecti…

数据库面试题(基础常考!!!)

在数据库领域&#xff0c;无论是日常开发还是面试场景&#xff0c;都有一些高频且重要的问题需要我们深入理解和掌握。本文将对这些常见面试题进行详细阐述&#xff0c;帮助大家更好地应对面试和实际工作中的挑战。 面试题一&#xff1a;三范式详解 什么是三范式 三范式是关…

论文笔记(七十二)Reward Centering(三)

Reward Centering&#xff08;三&#xff09; 文章概括摘要3 基于值的奖励中心化4 案例研究&#xff1a; 以奖励为中心的 Q-learning5 讨论、局限性与未来工作致谢 文章概括 引用&#xff1a; article{naik2024reward,title{Reward Centering},author{Naik, Abhishek and Wan…

鸿蒙开发深入浅出01(基本环境搭建、页面模板与TabBar)

鸿蒙开发深入浅出01&#xff08;基本环境搭建、页面模板与TabBar&#xff09; 1、效果展示2、下载 DevEco Studio3、创建项目4、新建页面模板5、更改应用信息6、新建以下页面7、Index.ets8、真机运行9、图片资源文件 1、效果展示 2、下载 DevEco Studio 访问官网根据自己的版本…

蓝桥杯第十六届嵌入式模拟编程题解析

由硬件框图可以知道我们要配置LED 和按键 LED 先配置LED的八个引脚为GPIO_OutPut&#xff0c;锁存器PD2也是&#xff0c;然后都设置为起始高电平&#xff0c;生成代码时还要去解决引脚冲突问题 按键 按键配置&#xff0c;由原理图按键所对引脚要GPIO_Input 生成代码&#xf…

二叉树的遍历知识点及习题

一、知识点 1二叉树的遍历理解为按照预先定好的搜索路径访问树里的每个节点&#xff0c;且每个节点仅访问一次 2假设根节点为N&#xff0c;左子树为L&#xff0c;右子树为R&#xff0c;常见的三种遍历方法分别是先&#xff08;前&#xff09;序遍历NLR 根左右&#xff0c;中序…

“conda”不是内部或外部命令,也不是可运行的程序或批处理文件

有的时候&#xff0c;我们发现在cmd黑框中输入conda时&#xff0c;cmd会显示“conda”不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件&#xff0c;那这时候该怎么解决呢&#xff1f; Step01&#xff1a;我们找到Anconda的安装目录。然后找到里面的bin文件夹&am…

特辣的海藻!3

基础知识点 判断一个数是否是2的幂次 方法一&#xff1a;位运算 所有2的幂次数的二进制表示中有且仅有一个1&#xff0c;进行位运算 n&(n-1) 后结果为0 检查正数&#xff1a;n > 0&#xff08;负数和0不是2的幂次&#xff09;位运算&#xff1a; n & ( n -1) 会…

苍穹外卖中的模块总结

本文总结苍穹外卖项目中可复用的通用设计 sky-common constant存放常量类&#xff0c;包括消息常量&#xff0c;状态常量 context是上下文对象&#xff0c;封装了threadlocal package com.sky.context;public class BaseContext {public static ThreadLocal<Long> thre…

Threejs教程一【三要素】

场景 场景是一个容器&#xff0c;用于容纳所有的物体、光源、相机等元素。 // 创建场景 const scene new THREE.Scene(); //修改背景颜色&#xff0c;颜色支持十六进制、rgb、hsl、贴图等 scene.background new THREE.Color(0x000000);相机 相机决定了渲染的结果&#xff…

Deepseek和Grok 3对比:写一段冒泡排序

1、这是访问Grok 3得到的结果 2、grok3输出的完整代码&#xff1a; def bubble_sort(arr):n len(arr) # 获取数组长度# 外层循环控制排序轮数for i in range(n):# 内层循环比较相邻元素&#xff0c;j的范围逐渐减少for j in range(0, n - i - 1):# 如果当前元素大于下一个元…

TCP/UDP调试工具推荐:Socket通信图解教程

TCP/UDP调试工具推荐&#xff1a;Socket通信图解教程 一、引言二、串口调试流程三、下载链接 SocketTool 调试助手是一款旨在协助程序员和网络管理员进行TCP和UDP协议调试的网络通信工具。TCP作为一种面向连接、可靠的协议&#xff0c;具有诸如连接管理、数据分片与重组、流量和…

Open WebUI 是什么

Open WebUI 是什么 Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离线运行。它支持各种 LLM 运行器,如 Ollama 和 OpenAI 兼容的 API,并内置了 RAG 推理引擎,使其成为强大的 AI 部署解决方案。 https://github.com/open-webui/open-webui 🚀 …