5-spring

news/2025/11/6 7:32:52/文章来源:https://www.cnblogs.com/chaoshang8/p/19195059

一、AOP
关于 Spring AOP,我将从以下几个方面进行详细的阐述:是什么、为什么、核心概念、实现原理、以及应用场景。

一、AOP 是什么?

AOP,面向切面编程,是一种编程范式。它的核心思想是:将那些遍布在应用多个模块中的、与核心业务逻辑无关的横切关注点(如日志、事务、安全等)分离出来,形成独立的“切面”,然后通过“织入”的方式应用到目标对象上。

我们可以这样理解:
• OOP(面向对象) 的核心单元是类(Class),它通过封装、继承、多态来建立对象的层级结构,解决的是纵向的代码复用问题。

• AOP(面向切面) 的核心单元是切面(Aspect),它像一把刀,横向地将多个不同类中的相同逻辑“切”出来,形成一个独立的模块,解决的是横向的代码复用问题。

二、为什么需要 AOP?

在没有 AOP 之前,我们的代码可能是这样的:
@Service
public class OrderService {

public void createOrder() {// 1. 记录日志System.out.println("[INFO] 开始创建订单...");// 2. 开启事务TransactionManager.beginTransaction();try {// --- 核心业务逻辑 ---// 3. 验权if (!userHasPermission()) {throw new SecurityException("无权限");}// 4. 执行核心操作// ... (创建订单的代码)// --- 核心业务逻辑结束 ---// 5. 提交事务TransactionManager.commit();// 6. 记录日志System.out.println("[INFO] 订单创建成功。");} catch (Exception e) {// 7. 回滚事务TransactionManager.rollback();// 8. 记录错误日志System.out.println("[ERROR] 创建订单失败: " + e.getMessage());throw e;}
}// UserService, ProductService 等都有大量重复的日志、事务代码...

}

存在的问题:

  1. 代码重复:日志、事务等代码散落在各个业务方法中。
  2. 代码耦合:业务代码与非业务代码(横切逻辑)高度纠缠,核心业务逻辑不清晰。
  3. 维护困难:如果需要修改日志格式或事务策略,需要改动所有相关方法,容易出错。

使用 AOP 之后:
业务方法变得非常纯粹和清晰:
@Service
public class OrderService {
@Transactional // 通过AOP管理事务
public void createOrder() {
// 纯粹的、只关注业务的代码
// ... (创建订单的代码)
}
}

而日志、事务等横切逻辑被抽取到独立的切面(Aspect) 中。

三、AOP 核心概念详解

概念 解释 生活化比喻(办理业务)

Aspect(切面) 横切关注点的模块化。一个切面是通知和切点的结合。 一个“VIP客户服务流程”手册,里面规定了VIP客户在办理业务前后需要做的特殊事情。

Joinpoint(连接点) 程序执行过程中一个明确的点,如方法调用、异常抛出等。Spring AOP 中,连接点总是代表方法的执行。 银行窗口中“办理业务”这个动作本身。

Pointcut(切点) 一个表达式,用于匹配哪些连接点需要被通知。切点定义了通知在“哪里”执行。 VIP客户服务手册中规定:“所有存款业务的办理过程”。这是一个匹配规则。

Advice(通知) 切面在特定的连接点上执行的动作。通知定义了“什么时候”做什么事。 手册中规定的具体动作,比如“客户来时送杯茶”,“办完后赠送小礼品”。

Target Object(目标对象) 被一个或多个切面所通知的对象。 正在办理存款业务的普通窗口工作人员。

Weaving(织入) 将切面应用到目标对象,从而创建代理对象的过程。 银行将“VIP客户服务流程”手册落实到对窗口工作人员的培训中,让工作人员具备了新的行为。

AOP Proxy(AOP 代理) AOP 框架创建的对象,它是织入的结果。在 Spring 中,代理可以是 JDK 动态代理 或 CGLIB 代理。 这位经过培训后的窗口工作人员,他现在既会办业务,也会提供VIP服务,他就是一个“增强版”的代理对象。

通知的类型(Advice):
• @Before:在目标方法执行前执行。

• @AfterReturning:在目标方法成功执行后执行。

• @AfterThrowing:在目标方法抛出异常后执行。

• @After(Finally):在目标方法执行后(无论成功与否) 执行,类似于 try-catch-finally 中的 finally。

• @Around:最强大的通知,它包围了连接点,可以在方法执行前后自定义行为,并控制是否执行目标方法。它需要显式调用 ProceedingJoinPoint.proceed()。

四、Spring AOP 的实现原理

Spring AOP 的底层是基于代理模式实现的。

  1. 如果目标对象实现了接口:默认使用 JDK 动态代理。
    ◦ 运行时动态创建一个实现了相同接口的代理类。

    ◦ 代理类持有目标对象的引用,并在方法调用前后插入切面逻辑。

  2. 如果目标对象没有实现任何接口:则使用 CGLIB 字节码生成库。
    ◦ 通过继承目标类,生成一个子类作为代理。

    ◦ 重写父类的方法,在方法中加入增强逻辑。

Spring 的选择策略:优先使用 JDK 动态代理,如果不行(无接口)则使用 CGLIB。可以通过配置强制使用 CGLIB。

五、一个完整的代码示例

假设我们有一个简单的计算器服务,我们需要为它的所有方法添加日志功能。

  1. 定义业务组件(目标对象)
    // 接口
    public interface CalculatorService {
    int add(int a, int b);
    int divide(int a, int b);
    }

// 实现类
@Service
public class CalculatorServiceImpl implements CalculatorService {
@Override
public int add(int a, int b) {
System.out.println("执行 add 方法");
return a + b;
}

@Override
public int divide(int a, int b) {System.out.println("执行 divide 方法");return a / b;
}

}

  1. 定义切面(Aspect)
    @Aspect // 声明这是一个切面
    @Component
    public class LoggingAspect {

    // 定义切点:匹配 CalculatorService 接口下的所有方法
    @Pointcut("execution(* com.example.service.CalculatorService.*(..))")
    public void calculatorMethods() {}

    // @Before 通知:在方法执行前执行
    @Before("calculatorMethods()")
    public void logBefore(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();
    System.out.println("[日志] 准备执行方法: " + methodName + ", 参数: " + Arrays.toString(args));
    }

    // @AfterReturning 通知:在方法成功返回后执行
    @AfterReturning(pointcut = "calculatorMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("[日志] 方法执行成功: " + methodName + ", 结果: " + result);
    }

    // @AfterThrowing 通知:在方法抛出异常后执行
    @AfterThrowing(pointcut = "calculatorMethods()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("[日志] 方法执行异常: " + methodName + ", 异常: " + ex.getMessage());
    }

    // @Around 通知:最强大的通知,可以控制方法是否执行
    @Around("calculatorMethods()")
    public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    try {
    // 继续执行目标方法
    Object result = pjp.proceed();
    return result;
    } finally {
    long duration = System.currentTimeMillis() - start;
    System.out.println("[性能] 方法 " + pjp.getSignature().getName() + " 执行耗时: " + duration + "ms");
    }
    }
    }

  2. 启用 AOP
    在配置类上添加 @EnableAspectJAutoProxy 注解。
    @Configuration
    @EnableAspectJAutoProxy // 开启基于注解的 AOP 功能
    @ComponentScan("com.example")
    public class AppConfig {
    }

  3. 测试结果
    当我们调用 calculatorService.add(1, 2) 时,控制台会输出:

[日志] 准备执行方法: add, 参数: [1, 2]
执行 add 方法
[日志] 方法执行成功: add, 结果: 3
[性能] 方法 add 执行耗时: 15ms

六、AOP 的应用场景总结

  1. 声明式事务管理(@Transactional):这是 Spring AOP 最经典的应用。
  2. 日志记录:统一、无侵入地记录方法入参、出参、执行时间等。
  3. 安全控制和权限检查(@PreAuthorize):在方法执行前进行权限验证。
  4. 缓存(@Cacheable):方法执行前先查缓存,执行后更新缓存。
  5. 错误处理和异常封装:统一将系统异常转换为用户友好的异常信息。
  6. 性能监控:使用 @Around 监控方法执行时间。

总结

总结来说,Spring AOP 通过动态代理技术,提供了一种优雅的解决方案,将横切关注点与核心业务逻辑分离开。它的核心在于切点(Pointcut) 定义了在哪里增强,通知(Advice) 定义了增强的时机和内容。这使得我们的代码更加高内聚、低耦合,易于维护和扩展,是 Spring 框架体系的基石之一。

二、Spring ObjectFactory 是什么
我来解释一下 Spring 框架中的 ObjectFactory。

简单来说,ObjectFactory 是一个用于延迟依赖查找或延迟创建对象的函数式接口。它的核心目的是将对目标对象的获取操作封装起来,实现延迟和按需供给。

  1. 核心定义与作用

• 接口定义:它是一个非常简单的接口,通常定义为 ObjectFactory,核心方法只有一个 T getObject()。每次调用这个方法,都可能返回一个目标对象的新实例(或代理)。

• 核心思想:延迟:它并不在容器初始化时就确定并持有目标对象,而是将“如何获取目标对象”这个行为封装起来。只有在真正调用 getObject() 方法时,才会去执行实际的查找或创建逻辑。

  1. 主要应用场景

ObjectFactory 最典型的应用场景是解决特定作用域下的依赖注入问题,尤其是原型Bean(prototype)注入到单例Bean(singleton) 的场景。

场景举例:

假设我们有一个单例的 OrderService,它每次处理订单时,都需要一个新的、独立的 OrderValidator(配置为原型作用域)。

如果直接通过 @Autowired 注入 OrderValidator,由于 OrderService 是单例的,在初始化时依赖注入只会发生一次,这会导致 OrderService 始终持有同一个 OrderValidator 实例,违背了我们将 OrderValidator 设为原型的初衷。

解决方案就是使用 ObjectFactory
@Service
public class OrderService { // 单例

// 不直接注入 OrderValidator,而是注入它的“工厂”
private final ObjectFactory<OrderValidator> validatorFactory;// 通过构造器注入工厂
public OrderService(ObjectFactory<OrderValidator> validatorFactory) {this.validatorFactory = validatorFactory;
}public void processOrder(Order order) {// 在需要的时候,通过工厂获取一个全新的原型Bean实例OrderValidator validator = validatorFactory.getObject();validator.validate(order);// ... 其他业务逻辑
}

}

通过这种方式:
• 单例Bean(OrderService)在启动时就能正常初始化,不会因为依赖一个原型Bean而被卡住。

• 按需获取:每次调用 processOrder 方法时,通过 validatorFactory.getObject() 都能从 Spring 容器中拿到一个全新的 OrderValidator 实例,完美实现了原型作用域的效果。

  1. 与相关接口的对比

为了更好地理解,可以把它和几个容易混淆的接口做个比较:

接口/类 核心区别

FactoryBean FactoryBean 本身是一个特殊的 Bean,它的任务是创建另一种类型的对象。从容器中按名字查找,得到的是它 getObject() 返回的对象。而 ObjectFactory 本身不是 Bean,它是一个依赖注入的“抓手”,用于延迟获取其他已存在的Bean。

Provider (JSR-330) ObjectFactory 是 Spring 自有的接口。Provider 是 JSR-330(Jakarta Dependency Injection)标准中的接口,作用与 ObjectFactory 完全一样。在 Spring 中,两者可以视为等效的,通常可以互换使用。

BeanFactory 这是容器的根接口,是所有 Bean 的“超级工厂”,功能庞大。而 ObjectFactory 是一个非常简单、职责单一的接口,可以看作是 BeanFactory 在特定依赖查找场景下的一个抽象和简化。

总结

面试官,总结一下,Spring 的 ObjectFactory 是一个用于实现延迟依赖查找的工具接口。它通过封装对象的获取逻辑,主要解决了将生命周期较短的Bean(如原型Bean)安全地注入到生命周期较长的Bean(如单例Bean)中所带来的作用域不匹配问题,确保了每次都能获取到符合预期作用域的新实例。

三、

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

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

相关文章

PHP 现代特性速查 写出更简洁安全的代码(完结篇)

PHP 现代特性速查 写出更简洁安全的代码(完结篇) 三部曲完结篇,讲区分老手和新手的高级模式:长期进程的内存管理、现代并发原语、生产环境的运维改进、小而关键的 API 改进。 原文链接 PHP 现代特性速查 写出更简洁…

关系数据库归档方案

目录一、分区表归档(推荐优先使用,对业务侵入最低)核心操作流程:优点:缺点:二、历史表分离(适合未建分区表的场景,实现简单)核心操作流程:增强工具:pt-archiver(Percona Toolkit)优点:缺点:三、跨库归档…

李开复:美国在AI硬件赛道已败给中国!中国AI算力将是美国十倍!警告AI速度失控:先上车再修路,将酿成重大事故!

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087在旧金山TED AI大会上,创新工场董事长、AI科学家李开复通过视频连…

NPU和GPU差别--AI算力对比

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087NPU就像个从小就被培养成"AI专家"的孩子。他从出生开始就…

Java 如何运行一个编译过的类文件?

Java 如何运行一个编译过的类文件?运行编译后的 Java 类文件(.class文件)需要使用 JRE(Java Runtime Environment)提供的java命令。以下是详细的操作步骤、注意事项及常见场景处理: 一、运行的基本前提已编译的.…

mongodb报错Sort exceeded memory limit of 104857600 bytes

mongodb报错Sort exceeded memory limit of 104857600 bytes在 MongoDB 查询场景中,“Sort exceeded memory limit of 104857600 bytes” 是典型的性能类故障,尤其在处理大量数据排序时易触发。该错误本质是排序操作…

mongostat 命令

mongostat 命令mongostat 是 MongoDB 自带的轻量级监控工具,功能类似 Linux 的 vmstat,能按固定时间间隔(默认 1 秒)采集实例运行指标并输出,是运维人员排查性能瓶颈、监控实例健康状态的核心工具。本文将从连接方…

Got Fatal Error 1236 或 MY-013114 Error

Got Fatal Error 1236 或 MY-013114 Error错误事务(errant transaction) 比如开始GTID的复制:1 2 3 4 5 6 7 8mysql > show replica status\G *************************** 1. row *************************** ..…

XMind 2024 pro 破解版下载及安装使用教程

XMind 2024 pro 破解版下载及安装使用教程前言 XMind 是一款专业的全球领先的商业思维导图软件,在国内使用广泛,拥有强大的功能、包括思维管理、商务演示、与办公软件协同工作等功能。 它采用全球先进的Eclipse RCP软…

Tailscale 虚拟局域网 安装

Tailscale 虚拟局域网 安装Tailscale 是一款基于 WireGuard 协议开发的轻量级网络工具,被称作"零配置虚拟局域网"神器。它能让散落在不同地方的设备通过加密通道组成一个专属的私人网络(虚拟局域网),实现…

[转]Register an application

[转]Register an application注册应用程序 - Training | Microsoft Learn 本文转自:Register an application - Training | Microsoft Learn注册应用程序已完成100 XP12 分钟注册应用程序会在应用与 Microsoft 标识平…

[转]Adobe Marketo 向 Azure 註冊應用程式,以取得用戶端 ID/應用程式 ID

[转]Adobe Marketo 向 Azure 註冊應用程式,以取得用戶端 ID/應用程式 ID本文转自:向 Azure 註冊應用程式,以取得用戶端 ID/應用程式 ID | Adobe Marketo EngageAzure Active Directory將您的內部部署目錄延伸至雲端…

Redis Lua沙箱逃逸漏洞分析与防护方案

本文详细分析了Redis Lua脚本子系统中的UAF漏洞CVE-2025-49844(RediShell),该漏洞允许攻击者逃逸沙箱执行任意代码。文章提供了修复方案、临时缓解措施,并介绍了FortiGuard系列产品的防护能力,包括漏洞检测、入侵…

pyslam - MKT

pyslam 1 创建环境 pyslamREM 保存当前目录 set STARTING_DIR=%cd% set ROOT_DIR=.. cd %ROOT_DIR%REM 检查 conda conda --version if errorlevel 1 (echo 错误:请先安装 condapauseexit )REM 创建环境 call script…

【Linux dbus】1-连接消息总线守护进程,创建名字

前言 本文章以函数dbus_bus_get()和dbus_bus_request_name()两个函数为引子,介绍如何连接消息总线守护进程,并为当前进程起一个名字dbus_bus_get 函数核心概念 dbus_bus_get 是 D-Bus 库(特别是在 libdbus 这个底层…

【Linux dbus】2-dbus发送消息(以创建方法调用为例)的过程

消息(方法调用)创建后的典型流程 仅仅创建消息头是不够的,完整的调用流程如下:创建方法调用消息 - dbus_message_new_method_call添加参数 - dbus_message_append_args发送消息 - dbus_connection_send(可选)等待…

CSP-S 2025 复赛解析

刚写完,暂时只放了代码,具体思路争取在7号晚上补完[CSP-S 2025] 社团招新 / club 题目描述 小 L 是学校算法协会的成员。在今年的学校社团招新中,小 L 一共招收了 \(n\) 个新成员,其中 \(n\) 为偶数。现在小 L 希望…

记录一次Prism9隐式注册引发的事件聚合器失效问题

直接上代码 1、我的注册从App的RegisterTypes方法迁移到了模块public class AccountModule : IModule{public void OnInitialized(IContainerProvider containerProvider){}public void RegisterTypes(IContainerRegis…

20232318 2025-2026-1 《网络与系统攻防技术》实验四实验报告

一、实验内容 本次实验围绕恶意代码分析的核心流程展开,具体包含四项关键任务:一是识别恶意代码的文件类型标识,完成文件脱壳操作与字符串提取,为后续分析扫清障碍;二是运用 IDA Pro 工具对指定 exe 文件开展静态…