You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 16:09:02