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

Quarkus热重载后InfluxDB客户端反射获取POJO字段失败

Quarkus热重载后InfluxDB客户端反射访问字段失败问题

问题场景

在Quarkus服务中使用InfluxDB Java Client管理指标,自行开发了提供InfluxDB 2开发容器的扩展。首次运行测试完全正常,但触发热重载(保存任意文件)后,发送指标到数据库的测试失败。测试重新运行会触发异常,停止并重启测试后首次运行恢复正常,直到再次触发热重载;移除Metric中的duration字段,报错会转移到job字段。

环境:Java 21、Quarkus 3.15、InfluxDBClient 7.2.0

测试代码及Metric模型

package mypackage.models.measurements;

import static mypackage.config.InfluxDBConstants.DefaultConstants.BUCKET;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;

import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.domain.WritePrecision;

import myextension.quarkus.devservices.influxdb.runtime.InfluxDBConfig;
import mypackage.config.InfluxDBConstants.JobMetricConstants.Fields;
import mypackage.config.InfluxDBConstants.JobMetricConstants.Tags;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;

/**
 * JobMetricTest
 */
@QuarkusTest
@TestInstance(Lifecycle.PER_CLASS)
public class JobMetricTest {

    @Inject
    InfluxDBConfig config;

    public InfluxDBClient getClient() {
        InfluxDBClient client = InfluxDBClientFactory.create(
                config.url(),
                config.token().toCharArray(),
                config.organization().get(),
                config.bucket().get());
        assertTrue(client.ping());
        return client;
    }

    @Test
    public void testInitialization() throws InterruptedException {
        Instant testTime = Instant.now();
        JobMetric metric = new JobMetric("testJob");
        assertTrue(metric.duration == -1);
        TimeUnit.MILLISECONDS.sleep(1);
        metric.finish();
        assertTrue(metric.duration > 0);
        assertTrue(metric.job.equals("testJob"));
        assertTrue(metric.start.getEpochSecond() >= testTime.getEpochSecond());
    }

    @Test
    public void testInfluxReflection() throws InterruptedException {
        InfluxDBClient client = getClient();
        Metric metric = new Metric();

        metric.job = "job";
        metric.hasError = false;
        metric.duration = Double.valueOf(-1);
        metric.start = Instant.now();
        Instant stop = Instant.now();
        Duration d  = Duration.between(metric.start, stop);
        metric.duration = (double) d.getSeconds();
        metric.duration += ((double) d.toMillisPart()) / 1000;
        client.getWriteApiBlocking()
            .writeMeasurement(          // Line 72: Fails here!
                BUCKET,
                config.organization().get(),
                WritePrecision.NS,
                metric
            );
    }
}

@Measurement(name = "metric")
class Metric {
    @Column(timestamp = true)
    public Instant start;

    @Column(tag = true, name = Tags.JOB)
    public String job;

    @Column(name = Fields.HAS_ERROR, tag = false)
    public Boolean hasError;

    @Column(name = Fields.DURATION, tag = false)
    public Double duration;
}

异常信息

2024-11-17 21:42:17,616 ERROR [io.qua.test] (Test runner thread) ==================== TEST REPORT #2 ==================== [Error Occurred After Shutdown]
2024-11-17 21:42:17,616 ERROR [io.qua.test] (Test runner thread) Test JobMetricTest#testInfluxReflextion() failed 
 [Error Occurred After Shutdown]: java.lang.IllegalArgumentException: Can not get java.lang.Double field mypackage.models.measurements.Metric.duration on mypackage.models.measurements.Metric
    at java.base/jdk.internal.reflect.MethodHandleFieldAccessorImpl.newGetIllegalArgumentException(MethodHandleFieldAccessorImpl.java:86)
    at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.get(MethodHandleObjectFieldAccessorImpl.java:61)
    at java.base/java.lang.reflect.Field.get(Field.java:444)
    at com.influxdb.client.internal.MeasurementMapper.getObject(MeasurementMapper.java:130)
    at com.influxdb.client.internal.MeasurementMapper.toPoint(MeasurementMapper.java:68)
    at com.influxdb.client.internal.AbstractWriteClient$BatchWriteDataMeasurement.toLineProtocol(AbstractWriteClient.java:386)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:212)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:212)
    at java.base/java.util.Collections$2.tryAdvance(Collections.java:5074)
    at java.base/java.util.Collections$2.forEachRemaining(Collections.java:5082)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:556)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:546)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:702)
    at com.influxdb.client.internal.AbstractWriteBlockingClient.write(AbstractWriteClient.java:69)
    at com.influxdb.client.internal.WriteApiBlockingImpl.writeMeasurements(WriteApiBlockingImpl.java:253)
    at com.influxdb.client.internal.WriteApiBlockingImpl.writeMeasurement(WriteApiBlockingImpl.java:220)
    at com.influxdb.client.internal.WriteApiBlockingImpl.writeMeasurement(WriteApiBlockingImpl.java:207)
    at mypackage.models.measurements.JobMetricTest.testInfluxReflextion(JobMetricTest.java:72)

解决方案

这是Quarkus热重载的类加载器隔离机制导致的问题:热重载后,新修改的Metric类由新的类加载器加载,而之前手动创建的InfluxDBClient实例绑定的是旧类加载器的元数据,反射时无法识别新类的字段。

修复步骤

  1. 将InfluxDBClient注册为CDI Bean
    不要在测试中每次新建客户端,而是在扩展中把InfluxDBClient注册为Quarkus管理的CDI Bean,确保它和应用类使用同一个类加载器:

    // 在你的InfluxDB扩展的Producer类中添加
    @Produces
    @ApplicationScoped
    public InfluxDBClient produceInfluxDBClient(InfluxDBConfig config) {
        return InfluxDBClientFactory.create(
                config.url(),
                config.token().toCharArray(),
                config.organization().get(),
                config.bucket().get());
    }
    
  2. 修改测试类,直接注入InfluxDBClient
    移除测试中的getClient()方法,直接注入客户端实例:

    @QuarkusTest
    @TestInstance(Lifecycle.PER_CLASS)
    public class JobMetricTest {
    
        @Inject
        InfluxDBConfig config;
        
        @Inject
        InfluxDBClient client; // 直接注入管理好的客户端
    
        @Test
        public void testInitialization() throws InterruptedException {
            // 原代码保持不变
        }
    
        @Test
        public void testInfluxReflection() throws InterruptedException {
            Metric metric = new Metric();
    
            metric.job = "job";
            metric.hasError = false;
            metric.duration = Double.valueOf(-1);
            metric.start = Instant.now();
            Instant stop = Instant.now();
            Duration d  = Duration.between(metric.start, stop);
            metric.duration = (double) d.getSeconds();
            metric.duration += ((double) d.toMillisPart()) / 1000;
            client.getWriteApiBlocking()
                .writeMeasurement(
                    BUCKET,
                    config.organization().get(),
                    WritePrecision.NS,
                    metric
                );
        }
    }
    
  3. 可选:为Metric类添加反射注册注解
    如果热重载后Metric类的反射元数据仍有问题,添加@RegisterForReflection注解告诉Quarkus保留该类的反射信息:

    @Measurement(name = "metric")
    @RegisterForReflection
    class Metric {
        // 原字段保持不变
    }
    

原理

Quarkus热重载会创建新的类加载器加载更新后的类,将InfluxDBClient转为CDI Bean后,它会被Quarkus的类加载器管理,热重载时会重新绑定到新的类元数据,反射访问字段时就能匹配新加载的Metric类。

内容的提问来源于stack exchange,提问作者JWo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:25:55