Quarkus环境下如何无反射获取ActiveMQ Classic客户端统计指标对接Micrometer?
我们有一个基于Quarkus(未使用smallrye-messaging)、Java 17构建的应用,使用org.apache.activemq:activemq-client:6.1.3连接ActiveMQ Classic broker。需要将客户端收集的生产者和消费者指标(如发送/接收字节数)暴露给Micrometer,再同步至Grafana。
目前能在调试器中查看各类统计数据,但无法通过JMS 2.0实现类(如实现JMSProducer的ActiveMQProducer)直接查询这些统计信息,只能通过反射实现指标采集(代码如下),但该实现复杂且易因底层代码变更失效,希望找到无需反射的方式或现成工具库替代:
public static void instrumentProducer(JMSProducer producer) { if (!(producer instanceof ActiveMQProducer amqProducer)) { throw new IllegalArgumentException("Expected an instance of ActiveMQProducer, but got: " + producer.getClass()); } /* Is there a way to do this without reflection? */ var field = Arrays.stream(ActiveMQProducer.class.getDeclaredFields()) .filter(f -> ActiveMQMessageProducer.class.isAssignableFrom(f.getType())) .findFirst() .orElseThrow(() -> new NoSuchElementException("Could not find ActiveMQMessageProducer field to scrape metrics from")); try { field.trySetAccessible(); var messageProducer = (ActiveMQMessageProducer) field.get(amqProducer); messageProducer.getStats().setEnabled(true); var messageCount = messageProducer.getProducerStats().getMessageCount(); // Ideally this should be a counter, but I'll figure that out. Gauge.builder("activemq.producer.message.count", messageCount::getCount) .baseUnit(messageCount.getUnit()) .description(messageCount.getDescription()) .register(globalRegistry); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to instrument JMSProducer", e); } }
1. 直接获取ActiveMQMessageProducer(无需反射)
ActiveMQ的ActiveMQProducer是JMS 2.0 JMSProducer的包装类,你可以跳过JMS标准API,直接通过ActiveMQSession创建或获取ActiveMQMessageProducer,这样就能直接访问统计对象:
// 假设已持有ActiveMQSession实例 ActiveMQMessageProducer messageProducer = (ActiveMQMessageProducer) session.createProducer(destination); // 启用统计收集 messageProducer.getStats().setEnabled(true); // 直接注册Micrometer指标 Gauge.builder("activemq.producer.message.count", messageProducer.getProducerStats().getMessageCount()::getCount) .baseUnit("messages") .description("Total number of messages sent by this producer") .register(globalRegistry);
如果是从JMSContext获取的JMSProducer,可以先将JMSContext强转为ActiveMQJMSContext,再拿到内部的ActiveMQSession:
ActiveMQJMSContext amqContext = (ActiveMQJMSContext) jmsContext; ActiveMQSession amqSession = amqContext.getSession(); // 后续步骤同上,直接操作ActiveMQMessageProducer
2. 利用ActiveMQ连接级全局统计
ActiveMQ Classic客户端的ActiveMQConnection支持全局统计开关,开启后会自动收集所有生产者、消费者的统计数据,无需逐个处理实例:
ActiveMQConnection connection = (ActiveMQConnection) jmsConnection; connection.setStatsEnabled(true); // 注册全局生产者消息数指标 Gauge.builder("activemq.global.producer.message.count", connection.getProducerStats()::getMessageCount) .baseUnit("messages") .description("Total number of messages sent by all producers") .register(globalRegistry);
3. 封装自定义Micrometer MeterBinder
基于Micrometer的MeterBinder接口封装ActiveMQ统计逻辑,统一管理指标注册,提升代码可维护性:
public class ActiveMQProducerMeterBinder implements MeterBinder { private final ActiveMQMessageProducer producer; public ActiveMQProducerMeterBinder(ActiveMQMessageProducer producer) { this.producer = producer; producer.getStats().setEnabled(true); } @Override public void bindTo(MeterRegistry registry) { ProducerStats stats = producer.getProducerStats(); // 注册消息数指标 Gauge.builder("activemq.producer.message.count", stats.getMessageCount()::getCount) .baseUnit("messages") .description("Total messages sent by producer") .register(registry); // 注册字节数指标 Gauge.builder("activemq.producer.byte.count", stats.getByteCount()::getCount) .baseUnit("bytes") .description("Total bytes sent by producer") .register(registry); } } // 使用示例 new ActiveMQProducerMeterBinder(messageProducer).bindTo(globalRegistry);
核心思路总结
- 优先使用ActiveMQ原生API(而非JMS标准API)获取客户端实例,直接访问统计方法
- 确保启用统计开关(
setStatsEnabled(true)),保证数据被正常收集 - 基于Micrometer标准接口封装指标逻辑,避免重复代码,降低维护成本
内容的提问来源于stack exchange,提问作者Tomboyo

