当前位置: 首页 > news >正文

5-spring

一、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.jsqmd.com/news/32715/

相关文章:

  • 测试开锁公司
  • PHP 现代特性速查 写出更简洁安全的代码(完结篇)
  • 关系数据库归档方案
  • Java 如何运行一个编译过的类文件?
  • mongodb报错Sort exceeded memory limit of 104857600 bytes
  • mongostat 命令
  • Got Fatal Error 1236 或 MY-013114 Error
  • XMind 2024 pro 破解版下载及安装使用教程
  • Tailscale 虚拟局域网 安装
  • [转]Register an application
  • [转]Adobe Marketo 向 Azure 註冊應用程式,以取得用戶端 ID/應用程式 ID
  • Redis Lua沙箱逃逸漏洞分析与防护方案
  • pyslam - MKT
  • 【Linux dbus】1-连接消息总线守护进程,创建名字
  • 【Linux dbus】2-dbus发送消息(以创建方法调用为例)的过程
  • 记录一次Prism9隐式注册引发的事件聚合器失效问题
  • 20232318 2025-2026-1 《网络与系统攻防技术》实验四实验报告
  • 用友U8C销售订单开单比较慢
  • Winfrom机器人自动寻路
  • test first
  • Win11 install CUDA 12.5
  • 机器学习-逻辑回归算法-向量版代码
  • 星期三
  • 「学习笔记」文件包含
  • 【AI说Rust 03】如何在 macos m1 系统搭建 rust 开发环境
  • 厨房小白学做饭——4.干锅菜花
  • 操盘计划202511090017
  • Effective C++
  • 厨房小白学做饭——3.虎皮青椒
  • 20251105 之所思 - 人生如梦