0%

Spring AOP 注解开启全流程源码解析:从 <aop:aspectj-autoproxy/> 到代理生成

Spring AOP 注解功能(如 @Aspect@Before)的开启依赖 <aop:aspectj-autoproxy/> 配置,其底层是一套 “自定义标签解析→核心处理器注册→BeanPostProcessor 介入→AOP 代理生成” 的完整链路。从 “自定义标签解析”“核心处理器初始化”“BeanPostProcessor 调用时机”“AOP 代理生成” 四个维度,逐行拆解 AOP 注解开启的底层逻辑。

前置知识:Spring 自定义标签解析机制

<aop:aspectj-autoproxy/> 并非 Spring 内置的 “默认标签”(如 <bean><import>),而是自定义标签。Spring 解析自定义标签需依赖两个核心配置文件和一套解析流程:

自定义标签的核心配置文件

Spring 通过类路径下的 META-INF/spring.handlersMETA-INF/spring.schemas 关联自定义标签与解析逻辑:

  • spring.handlers:映射 “命名空间(namespace)” 到 “标签处理器(NamespaceHandler)”,告诉 Spring 用哪个类解析该命名空间下的标签;
  • spring.schemas:映射 “XSD Schema 地址” 到 “本地 XSD 文件”,用于校验自定义标签的语法合法性。
示例:AOP 自定义标签的配置
1
2
3
4
5
# spring.handlers:aop 命名空间对应 AopNamespaceHandler
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

# spring.schemas:aop XSD 地址对应本地文件(避免联网下载)
http\://www.springframework.org/schema/aop/spring-aop-3.1.xsd=org/springframework/aop/config/spring-aop-3.1.xsd

自定义标签的解析流程

当 Spring 解析 XML 配置遇到自定义标签(如 <aop:aspectj-autoproxy/>)时,执行以下步骤:

  1. 提取命名空间:从标签的 xmlns:aop 属性获取命名空间(http://www.springframework.org/schema/aop);
  2. 获取处理器:通过 NamespaceHandlerResolverspring.handlers 中找到对应的 AopNamespaceHandler
  3. 初始化处理器:调用 AopNamespaceHandler.init() 注册标签解析器;
  4. 解析标签:根据标签名称(aspectj-autoproxy)调用对应的解析器(AspectJAutoProxyBeanDefinitionParser)。
阅读全文 »

消息重复消费问题:原因分析与解决方案

消息重复消费是分布式消息系统中常见的问题,指同一消息被消费者多次处理,可能导致业务数据不一致(如重复下单、重复扣款)。本文深入分析重复消费的根源,并提供可落地的解决方案,确保消息处理的幂等性。

消息重复消费的根本原因

消息重复的本质是 “消息传递状态不明确”:发送方或中间件无法确定消息是否已被正确处理,从而触发重试机制。具体可分为两类场景:生产者重复发送中间件重复投递

生产者重复发送消息

生产者(消息发送方)因 “不确定消息是否已被中间件接收” 而重复发送,常见原因:

场景 具体描述
中间件响应超时 生产者发送消息后,中间件已成功存储但响应缓慢,生产者触发超时重试,导致重复发送。
网络波动 消息已到达中间件,但返回的 “发送成功” 响应在网络传输中丢失,生产者误认为发送失败并重试。
中间件故障 中间件处理消息时崩溃,重启后未记录已接收的消息,生产者重试时再次发送。

示例:订单系统发送 “创建订单” 消息,中间件存储成功但返回响应时宕机,订单系统未收到确认,5 秒后重试发送,导致中间件收到两条相同消息。

消息中间件重复投递

中间件(如 RabbitMQ、Kafka)因 “不确定消费者是否已处理消息” 而重复投递,常见原因:

阅读全文 »

消息丢失问题全解析:原因与解决方案

消息丢失是消息队列使用中最常见的可靠性问题,可能发生在生产者发送、消息队列存储、消费者处理三个环节。本文针对每个环节的丢失原因,结合主流消息队列(Kafka、RabbitMQ 等)的特性,提供具体解决方案,确保消息从发送到消费的全链路可靠性。

生产者丢失数据:确保消息成功投递到中间件

生产者(消息发送方)丢失数据的核心原因是:消息未成功传递到消息队列,可能因网络波动、中间件故障或配置不当导致。

丢失原因分析

  • 异步发送未确认:生产者采用异步发送模式,消息暂存在本地缓冲区(如 Kafka 的 buffer.memory),若缓冲区满或发送线程异常,消息可能丢失。
  • 发送超时 / 网络中断:消息发送过程中网络中断,或中间件响应超时,生产者未收到 “发送成功” 确认,误以为发送失败但未重试。
  • 中间件接收失败:中间件(如 Broker)因磁盘满、权限不足等原因拒绝接收消息,生产者未处理错误导致消息丢失。

解决方案

(1)使用同步发送 + 确认机制
  • 生产者发送消息后,等待中间件返回确认(同步阻塞),确保消息被接收。

  • 示例(Kafka):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 同步发送,等待结果
    ProducerRecord<String, String> record = new ProducerRecord<>("topic", "key", "value");
    try {
    // 同步发送,返回RecordMetadata表示成功
    RecordMetadata metadata = kafkaProducer.send(record).get();
    System.out.println("消息发送成功,偏移量:" + metadata.offset());
    } catch (Exception e) {
    // 发送失败,执行重试逻辑
    retrySend(record);
    }
(2)配置中间件确认级别(Kafka 关键)

Kafka 通过 acks 参数控制确认级别,决定消息何时被视为 “发送成功”:

  • acks=0:生产者不等待任何确认,消息发送即视为成功(最快但最不安全,可能丢失)。
  • acks=1:仅等待 Leader 副本写入本地日志后确认(默认值,若 Leader 宕机且未同步到 Follower,可能丢失)。
  • acks=-1(或 all):等待 Leader 及所有 ISR(同步副本集)写入后确认(最安全,确保至少有 2 个副本存储消息,避免单点故障)。

推荐配置acks=all + 合理的 retries(重试次数,如 retries=3),确保消息在网络波动时可重试并被确认。

(3)本地消息表 + 定时重试(最终一致性方案)
  • 核心思路:将消息先写入本地数据库(与业务操作在同一事务),确保业务成功后消息不丢失,再异步发送到中间件,失败则定时重试。
  • 步骤:
    1. 业务执行时,在本地事务中插入 “待发送消息” 到消息表(如 producer_messages)。
    2. 事务提交后,通过线程池异步发送消息到中间件。
    3. 发送成功后,更新消息表状态为 “已发送”;失败则由定时任务(如 Quartz)重试。

消息队列丢失数据:确保中间件持久化存储

消息队列丢失数据的核心原因是:消息未被持久化,或持久化后因故障(如宕机)导致数据丢失。

阅读全文 »

Spring 循环依赖深度解析:三级缓存的设计本质与 AOP 协同机制

Spring 对循环依赖的处理是其 IOC 容器设计的精髓之一,尤其是三级缓存的运用,不仅解决了依赖循环问题,更兼顾了 AOP 代理等复杂场景。从 “循环依赖分类→三级缓存工作机制→三级缓存必要性(为什么二级缓存不够)” 三个维度,彻底讲透 Spring 循环依赖的解决方案。

循环依赖的两种类型与处理差异

循环依赖指两个或多个 Bean 相互依赖形成闭环(如 A→B→A)。Spring 对不同注入方式的循环依赖处理方式截然不同:

1. 构造器循环依赖:无法解决,直接报错

当 Bean 通过构造器注入形成循环依赖时,Spring 无法解决,会抛出 BeanCurrentlyInCreationException

核心原因:实例化阶段就依赖对方,无提前暴露机会

构造器注入的依赖在 Bean 实例化阶段(调用构造器)就必须满足,而此时被依赖的 Bean 可能尚未实例化,形成 “先有鸡还是先有蛋” 的死锁。

源码验证:创建前的状态检查

Spring 通过 beforeSingletonCreation() 方法跟踪正在创建的 Bean,若检测到循环依赖,直接报错:

1
2
3
4
5
6
7
protected void beforeSingletonCreation(String beanName) {
// 若当前 Bean 已在创建中(存在于 singletonsCurrentlyInCreation),则抛出异常
if (!this.inCreationCheckExclusions.contains(beanName) &&
!this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

示例流程(A 构造器依赖 B,B 构造器依赖 A):

阅读全文 »

IP 地址与网络诊断工具:ping 与 traceroute 详解

IP 地址是网络中设备的唯一标识,而pingtraceroute是诊断网络连接、定位故障的核心工具。它们通过发送特定数据包,帮助我们判断设备可达性、分析路由路径并排查延迟问题。以下是对这两个工具的深度解析:

ping:检测网络可达性的 “敲门砖”

ping(Packet Internet Groper)基于 ICMP(互联网控制消息协议)工作,通过向目标 IP 或域名发送请求数据包并等待回应,判断目标是否在线及网络连接质量。

工作原理

  1. 本地设备向目标发送ICMP Echo Request(回显请求)数据包,包含随机数据和序列号。
  2. 若目标可达,会返回ICMP Echo Reply(回显应答)数据包,携带相同的随机数据。
  3. 本地设备计算请求与应答的时间差(延迟),并统计丢包率,以此评估网络状况。

输出结果解析(以示例为例)

1
2
3
4
5
6
7
8
9
10
11
ping zhhll.icu
PING isfox-github-io.vercel.app (76.223.121.104): 56 data bytes
# 目标域名解析为IP:76.223.121.104,发送56字节数据包
64 bytes from 76.223.121.104: icmp_seq=0 ttl=107 time=100.365 ms
# 收到64字节应答(56字节数据+8字节ICMP头),序列号0,TTL=107,延迟100.365毫秒
...
--- isfox-github-io.vercel.app ping statistics ---
6 packets transmitted, 6 packets received, 0.0% packet loss
# 发送6个包,接收6个,丢包率0%(网络通畅)
round-trip min/avg/max/stddev = 100.365/105.318/107.179/2.359 ms
# 往返延迟:最小100ms,平均105ms,最大107ms,标准差2.36ms(延迟波动小)

关键参数解读

  • ttl(Time To Live):生存时间,每经过一个路由器减 1,减为 0 时数据包被丢弃(防止环路)。示例中ttl=107,说明数据包经过了128-107=21个路由器(默认初始 TTL 值:Windows 为 128,Linux 为 64)。
  • time:往返延迟(RTT),反映数据从本地到目标再返回的时间,延迟越低网络质量越好。
  • 丢包率:丢失数据包占总发送数的比例,丢包率高可能意味着网络拥堵或链路故障。

常用场景

  • 快速判断目标设备是否在线(如ping 192.168.1.1检测路由器是否可达)。
  • 排查网络延迟问题(如游戏卡顿前ping服务器,确认是否为网络延迟导致)。
  • 验证 DNS 解析是否正确(如ping zhhll.icu返回的 IP 是否与预期一致)。

traceroute:追踪数据包的 “长征路线”

traceroute(Windows 中为tracert)用于追踪数据包从本地到目标设备所经过的所有路由器(跳数),并记录每一跳的延迟,帮助定位网络瓶颈所在。

阅读全文 »