Spring应用中如何获取调用EmailService的发起方Spring Bean名称
Spring 没有提供直接在被调用 Bean 内部获取调用方 Bean 名称的原生内置方法,但有三种远优于手动解析调用栈的实现方案,推荐优先使用无侵入的 AOP 方案:
方案1:AOP切面实现无侵入统计(首选)
该方案无需修改EmailService的业务代码,统计逻辑与业务逻辑完全解耦:
- 第一步:开启AOP支持,在启动类添加
@EnableAspectJAutoProxy注解 - 第二步:编写统计切面,示例代码如下:
@Aspect @Component public class EmailServiceMonitor { private final ApplicationContext applicationContext; // 线程安全的按天调用计数器,key为调用方Bean名 private final Map<String, AtomicInteger> dailyInvokeCounter = new ConcurrentHashMap<>(); public EmailServiceMonitor(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } // 切点匹配EmailService的send方法 @Pointcut("execution(* your.package.path.EmailService.send(..))") public void emailSendPointcut() {} @Before("emailSendPointcut()") public void beforeSend(JoinPoint joinPoint) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); String callerBeanName = null; for (StackTraceElement element : stackTrace) { String className = element.getClassName(); // 替换为你自身业务的包前缀,过滤掉AOP、反射、代理类 if (className.startsWith("your.business.package") && !className.contains("EmailService") && !className.contains("$$EnhancerBySpringCGLIB") && !className.contains("FastClassBySpringCGLIB")) { try { Class<?> callerClazz = Class.forName(className); // 从Spring上下文获取对应类型的Bean名 callerBeanName = applicationContext.getBeanNamesForType(callerClazz)[0]; break; } catch (ClassNotFoundException ignored) {} } } if (callerBeanName != null) { dailyInvokeCounter.computeIfAbsent(callerBeanName, k -> new AtomicInteger(0)).incrementAndGet(); } } // 对外暴露统计结果,可定时导出或落库 public Map<String, Integer> getDailyStats() { return dailyInvokeCounter.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().get())); } }
该方案仅需遍历调用栈匹配自身业务包,复用Spring原生API获取Bean名,不会受代理类干扰,实现简洁。
方案2:轻量侵入传递调用方标识
如果接受小范围修改业务代码,可以在send方法新增可选的调用方参数,调用方通过BeanNameAware自动注入自身Bean名传入:
// EmailService修改send方法新增调用方参数 public void send(String recipient, String content, @Nullable String caller) { if (caller != null) { // 执行统计逻辑 } // 原有发送邮件逻辑 } // 调用方实现BeanNameAware自动获取自身Bean名 @Service public class PaymentService implements BeanNameAware { private String selfBeanName; private final EmailService emailService; @Override public void setBeanName(String name) { this.selfBeanName = name; } public void payNotify() { emailService.send("user@example.com", "支付成功", selfBeanName); } }
该方案完全不需要解析调用栈,性能最高,适合调用方数量不多的场景。
方案3:Micrometer埋点对接监控体系
如果项目已经集成了Spring Boot Actuator和Micrometer,可以直接通过标签统计调用量,后续可直接对接Prometheus、Grafana生成柱状图,无需自行实现统计存储逻辑:
@Service public class EmailService { private final MeterRegistry meterRegistry; public EmailService(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } public void send(String recipient, String content) { String caller = getCallerBeanName(); // 逻辑参考方案1的栈遍历 // 以caller为标签统计调用次数 meterRegistry.counter("email_service_send_total", "caller", caller).increment(); // 原有发送邮件逻辑 } }
内容的提问来源于stack exchange,提问作者johnlinp
相关产品推荐
相关产品推荐

