This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-connectors.git
The following commit(s) were added to refs/heads/main by this push:
new 6b5bd1377 (InfluxDB, HDFS, JMS) Replace java.lang.reflect with
VarHandle/MethodHandle (#1752)
6b5bd1377 is described below
commit 6b5bd13771650e1aa41947cd8aaf2f34a227dd73
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Tue Jul 14 00:56:34 2026 +0800
(InfluxDB, HDFS, JMS) Replace java.lang.reflect with VarHandle/MethodHandle
(#1752)
* refactor: replace java.lang.reflect with VarHandle/MethodHandle in
InfluxDB, HDFS, JMS
Motivation:
java.lang.reflect Field.get/set and Constructor.newInstance bypass JIT
inlining, adding overhead in hot paths like InfluxDB result mapping,
HDFS configuration, and JMS connection factory instantiation.
Modification:
- InfluxDB: replace Field.get/set with VarHandle for result mapping;
hoist privateLookupIn per class; use invokeWithArguments()
- HDFS: replace Constructor.newInstance with MethodHandle for
configuration class instantiation
- JMS: replace Constructor.newInstance with cached MethodHandle for
ConnectionFactory creation
Result:
JIT-inlinable field access and constructor calls in connector hot paths.
Refs #1752
* perf: use invoke for zero-arg method handles
Motivation:\nAvoid the allocation-heavy invokeWithArguments() path for
no-arg constructor handles in hot connector code.\n\nModification:\nUse
MethodHandle.invoke() for HdfsSource sequence element construction and InfluxDB
row instantiation.\n\nResult:\nConnector reflection bridges keep the
MethodHandle migration but remove unnecessary generic invocation overhead.
---
.../connectors/hdfs/scaladsl/HdfsSource.scala | 23 ++-
.../impl/PekkoConnectorsResultMapperHelper.scala | 195 ++++++++++++---------
.../test/java/docs/javadsl/InfluxDbSourceTest.java | 2 +-
.../src/test/java/docs/javadsl/InfluxDbTest.java | 2 +-
influxdb/src/test/java/docs/javadsl/TestUtils.java | 26 ++-
.../PekkoConnectorsResultMapperHelperSpec.scala | 73 ++++++++
.../scala/docs/scaladsl/JmsConnectorsSpec.scala | 19 +-
7 files changed, 237 insertions(+), 103 deletions(-)
diff --git
a/hdfs/src/main/scala/org/apache/pekko/stream/connectors/hdfs/scaladsl/HdfsSource.scala
b/hdfs/src/main/scala/org/apache/pekko/stream/connectors/hdfs/scaladsl/HdfsSource.scala
index 53cc8cfee..1cccca768 100644
---
a/hdfs/src/main/scala/org/apache/pekko/stream/connectors/hdfs/scaladsl/HdfsSource.scala
+++
b/hdfs/src/main/scala/org/apache/pekko/stream/connectors/hdfs/scaladsl/HdfsSource.scala
@@ -13,6 +13,9 @@
package org.apache.pekko.stream.connectors.hdfs.scaladsl
+import java.lang.invoke.{ MethodHandle, MethodHandles, MethodType }
+import java.util.concurrent.ConcurrentHashMap
+
import org.apache.pekko
import pekko.NotUsed
import pekko.stream.ActorAttributes.IODispatcher
@@ -27,6 +30,20 @@ import scala.concurrent.Future
object HdfsSource {
+ private val constructorCache = new ConcurrentHashMap[Class[?],
MethodHandle]()
+ private val LOOKUP = MethodHandles.lookup()
+
+ private def getNoArgConstructor[T](clazz: Class[T]): MethodHandle = {
+ var handle = constructorCache.get(clazz)
+ if (handle == null) {
+ val lookup = MethodHandles.privateLookupIn(clazz, LOOKUP)
+ handle = lookup.findConstructor(clazz, MethodType.methodType(Void.TYPE))
+ val existing = constructorCache.putIfAbsent(clazz, handle)
+ if (existing != null) handle = existing
+ }
+ handle
+ }
+
/**
* Scala API: creates a `Source` that consumes as `ByteString`
*
@@ -69,10 +86,12 @@ object HdfsSource {
classK: Class[K],
classV: Class[V]): Source[(K, V), NotUsed] = {
val reader: SequenceFile.Reader = new SequenceFile.Reader(fs.getConf,
SequenceFile.Reader.file(path))
+ val keyHandle = getNoArgConstructor(classK)
+ val valueHandle = getNoArgConstructor(classV)
val it = Iterator
.continually {
- val key = classK.getDeclaredConstructor().newInstance()
- val value = classV.getDeclaredConstructor().newInstance()
+ val key = keyHandle.invoke().asInstanceOf[K]
+ val value = valueHandle.invoke().asInstanceOf[V]
val hasCurrent = reader.next(key, value)
(hasCurrent, (key, value))
}
diff --git
a/influxdb/src/main/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelper.scala
b/influxdb/src/main/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelper.scala
index 681ed63dd..81efbfe62 100644
---
a/influxdb/src/main/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelper.scala
+++
b/influxdb/src/main/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelper.scala
@@ -13,10 +13,11 @@
package org.apache.pekko.stream.connectors.influxdb.impl
-import java.lang.reflect.Field
+import java.lang.invoke.{ MethodHandle, MethodHandles, MethodType, VarHandle }
import java.time.Instant
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.ChronoField
+import java.util.function.Function
import java.util.concurrent.{ ConcurrentHashMap, ConcurrentMap }
import java.util.concurrent.TimeUnit
@@ -35,7 +36,7 @@ import scala.jdk.CollectionConverters._
@InternalApi
private[impl] class PekkoConnectorsResultMapperHelper {
- val CLASS_FIELD_CACHE: ConcurrentHashMap[String, ConcurrentMap[String,
Field]] = new ConcurrentHashMap();
+ import PekkoConnectorsResultMapperHelper.{ CLASS_COLUMN_CACHE,
CONSTRUCTOR_CACHE, ColumnInfo, LOOKUP }
private val FRACTION_MIN_WIDTH = 0
private val FRACTION_MAX_WIDTH = 9
@@ -57,7 +58,7 @@ private[impl] class PekkoConnectorsResultMapperHelper {
throwExceptionIfMissingAnnotation(model.getClass)
cacheClassFields(model.getClass)
- val colNameAndFieldMap: ConcurrentMap[String, Field] =
CLASS_FIELD_CACHE.get(model.getClass.getName)
+ val colMap: ConcurrentMap[String, ColumnInfo] =
CLASS_COLUMN_CACHE.get(model.getClass)
try {
val modelType = model.getClass();
@@ -66,27 +67,18 @@ private[impl] class PekkoConnectorsResultMapperHelper {
val time = timeUnit.convert(System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
val pointBuilder: Point.Builder =
Point.measurement(measurement).time(time, timeUnit);
- for (key <- colNameAndFieldMap.keySet().asScala) {
- val field = colNameAndFieldMap.get(key)
- val column = field.getAnnotation(classOf[Column])
- val columnName: String = column.name()
- val fieldType: Class[?] = field.getType()
+ for (key <- colMap.keySet().asScala) {
+ val colInfo = colMap.get(key)
+ val value = colInfo.varHandle.get(model);
- val isAccessible = field.isAccessible()
- if (!isAccessible) {
- field.setAccessible(true);
- }
-
- val value = field.get(model);
-
- if (column.tag()) {
- pointBuilder.tag(columnName, value.toString());
- } else if ("time".equals(columnName)) {
+ if (colInfo.isTag) {
+ pointBuilder.tag(colInfo.columnName, value.toString());
+ } else if ("time".equals(colInfo.columnName)) {
if (value != null) {
- setTime(pointBuilder, fieldType, timeUnit, value);
+ setTime(pointBuilder, colInfo.fieldType, timeUnit, value);
}
} else {
- setField(pointBuilder, fieldType, columnName, value);
+ setField(pointBuilder, colInfo.fieldType, colInfo.columnName, value);
}
}
@@ -96,27 +88,13 @@ private[impl] class PekkoConnectorsResultMapperHelper {
}
}
- private[impl] def cacheClassFields(clazz: Class[?]) =
- if (!CLASS_FIELD_CACHE.containsKey(clazz.getName)) {
- val initialMap: ConcurrentMap[String, Field] = new ConcurrentHashMap()
- var influxColumnAndFieldMap =
CLASS_FIELD_CACHE.putIfAbsent(clazz.getName, initialMap)
-
- if (influxColumnAndFieldMap == null) {
- influxColumnAndFieldMap = initialMap;
- }
-
- var c = clazz;
-
- while (c != null) {
- for (field <- c.getDeclaredFields()) {
- val colAnnotation = field.getAnnotation(classOf[Column]);
- if (colAnnotation != null) {
- influxColumnAndFieldMap.put(colAnnotation.name(), field);
- }
- }
- c = c.getSuperclass();
- }
- }
+ private[impl] def cacheClassFields(clazz: Class[?]): Unit =
+ CLASS_COLUMN_CACHE.computeIfAbsent(
+ clazz,
+ new Function[Class[?], ConcurrentMap[String, ColumnInfo]] {
+ override def apply(clazz: Class[?]): ConcurrentMap[String, ColumnInfo]
=
+ PekkoConnectorsResultMapperHelper.classColumnMap(clazz)
+ })
private[impl] def parseSeriesAs[T](clazz: Class[T], series:
QueryResult.Series, precision: TimeUnit): List[T] = {
cacheClassFields(clazz)
@@ -155,111 +133,115 @@ private[impl] class PekkoConnectorsResultMapperHelper {
throw new IllegalArgumentException(
"Class " + clazz.getName + " is not annotated with @" +
classOf[Measurement].getSimpleName)
+ private def getOrCreateConstructorHandle(clazz: Class[?]): MethodHandle =
+ CONSTRUCTOR_CACHE.computeIfAbsent(
+ clazz,
+ new Function[Class[?], MethodHandle] {
+ override def apply(clazz: Class[?]): MethodHandle =
+ MethodHandles.privateLookupIn(clazz, LOOKUP).findConstructor(clazz,
MethodType.methodType(Void.TYPE))
+ })
+
private def parseRowAs[T](clazz: Class[T],
columns: java.util.List[String],
values: java.util.List[AnyRef],
precision: TimeUnit): T =
try {
- val fieldMap = CLASS_FIELD_CACHE.get(clazz.getName)
+ val colMap = CLASS_COLUMN_CACHE.get(clazz)
- val obj: T = clazz.getDeclaredConstructor().newInstance()
+ val handle = getOrCreateConstructorHandle(clazz)
+ val obj: T = handle.invoke().asInstanceOf[T]
for (i <- 0 until columns.size()) {
- val correspondingField = fieldMap.get(columns.get(i))
- if (correspondingField != null) {
- setFieldValue(obj, correspondingField, values.get(i), precision)
+ val colInfo = colMap.get(columns.get(i))
+ if (colInfo != null) {
+ setFieldValue(obj, colInfo, values.get(i), precision)
}
}
obj
} catch {
- case e @ (_: InstantiationException | _: IllegalAccessException) =>
+ case e: InfluxDBMapperException => throw e
+ case e: Exception =>
throw new InfluxDBMapperException(e)
}
- @throws[IllegalArgumentException]
- @throws[IllegalAccessException]
- private def setFieldValue[T](obj: T, field: Field, value: Any, precision:
TimeUnit): Unit = {
+ private def setFieldValue[T](obj: T, colInfo: ColumnInfo, value: Any,
precision: TimeUnit): Unit = {
if (value == null) return
- val fieldType = field.getType
+ val fieldType = colInfo.fieldType
try {
- val isAccessible = field.isAccessible()
- if (!isAccessible) field.setAccessible(true)
- if (fieldValueModified(fieldType, field, obj, value, precision) ||
fieldValueForPrimitivesModified(
- fieldType,
- field,
- obj,
- value) || fieldValueForPrimitiveWrappersModified(fieldType, field,
obj, value)) return
+ if (fieldValueModified(fieldType, colInfo, obj, value, precision) ||
+ fieldValueForPrimitivesModified(fieldType, colInfo, obj, value) ||
+ fieldValueForPrimitiveWrappersModified(fieldType, colInfo, obj,
value)) return
val msg =
- s"""Class '${obj.getClass.getName}' field '${field.getName}' is from
an unsupported type '${field.getType}'."""
+ s"""Class '${obj.getClass.getName}' field '${colInfo.fieldName}' is
from an unsupported type '${colInfo.fieldType}'."""
throw new InfluxDBMapperException(msg)
} catch {
case e: ClassCastException =>
val msg =
- s"""Class '${obj.getClass.getName}' field '${field.getName}' was
defined with a different field type and caused a ClassCastException.
+ s"""Class '${obj.getClass.getName}' field '${colInfo.fieldName}' was
defined with a different field type and caused a ClassCastException.
|The correct type is '${value.getClass.getName}' (current field
value: '${value}')""".stripMargin
throw new InfluxDBMapperException(msg)
}
}
- @throws[IllegalArgumentException]
- @throws[IllegalAccessException]
- private def fieldValueForPrimitivesModified[T](fieldType: Class[?], field:
Field, obj: T, value: Any): Boolean =
+ private def fieldValueForPrimitivesModified[T](
+ fieldType: Class[?],
+ colInfo: ColumnInfo,
+ obj: T,
+ value: Any): Boolean =
if (classOf[Double].isAssignableFrom(fieldType)) {
- field.setDouble(obj, value.asInstanceOf[Double].doubleValue)
+ colInfo.varHandle.set(obj, value.asInstanceOf[Double].doubleValue)
true
} else if (classOf[Long].isAssignableFrom(fieldType)) {
- field.setLong(obj, value.asInstanceOf[Double].longValue)
+ colInfo.varHandle.set(obj, value.asInstanceOf[Double].longValue)
true
} else if (classOf[Int].isAssignableFrom(fieldType)) {
- field.setInt(obj, value.asInstanceOf[Double].intValue)
+ colInfo.varHandle.set(obj, value.asInstanceOf[Double].intValue)
true
} else if (classOf[Boolean].isAssignableFrom(fieldType)) {
- field.setBoolean(obj, String.valueOf(value).toBoolean)
+ colInfo.varHandle.set(obj, String.valueOf(value).toBoolean)
true
} else {
false
}
- @throws[IllegalArgumentException]
- @throws[IllegalAccessException]
- private def fieldValueForPrimitiveWrappersModified[T](fieldType: Class[?],
- field: Field,
+ private def fieldValueForPrimitiveWrappersModified[T](
+ fieldType: Class[?],
+ colInfo: ColumnInfo,
obj: T,
value: Any): Boolean =
if (classOf[java.lang.Double].isAssignableFrom(fieldType)) {
- field.set(obj, value)
+ colInfo.varHandle.set(obj, value)
true
} else if (classOf[java.lang.Long].isAssignableFrom(fieldType)) {
- field.set(obj, value.asInstanceOf[Double].longValue())
+ colInfo.varHandle.set(obj, value.asInstanceOf[Double].longValue())
true
} else if (classOf[Integer].isAssignableFrom(fieldType)) {
- field.set(obj, value.asInstanceOf[java.lang.Integer])
+ colInfo.varHandle.set(obj, value.asInstanceOf[java.lang.Integer])
true
} else if (classOf[java.lang.Boolean].isAssignableFrom(fieldType)) {
- field.set(obj, value.asInstanceOf[java.lang.Boolean])
+ colInfo.varHandle.set(obj, value.asInstanceOf[java.lang.Boolean])
true
} else {
false
}
- @throws[IllegalArgumentException]
- @throws[IllegalAccessException]
- private def fieldValueModified[T](fieldType: Class[?],
- field: Field,
+ private def fieldValueModified[T](
+ fieldType: Class[?],
+ colInfo: ColumnInfo,
obj: T,
value: Any,
precision: TimeUnit): Boolean =
if (classOf[String].isAssignableFrom(fieldType)) {
- field.set(obj, String.valueOf(value))
+ colInfo.varHandle.set(obj, String.valueOf(value))
true
} else if (classOf[Instant].isAssignableFrom(fieldType)) {
- val instant: Instant = getInstant(field, value, precision)
- field.set(obj, instant)
+ val instant: Instant = getInstant(colInfo, value, precision)
+ colInfo.varHandle.set(obj, instant)
true
} else {
false
}
- private def getInstant(field: Field, value: Any, precision: TimeUnit):
Instant =
+ private def getInstant(colInfo: ColumnInfo, value: Any, precision:
TimeUnit): Instant =
if (value.isInstanceOf[String])
Instant.from(RFC3339_FORMATTER.parse(String.valueOf(value)))
else if (value.isInstanceOf[java.lang.Long])
Instant.ofEpochMilli(toMillis(value.asInstanceOf[Long], precision))
else if (value.isInstanceOf[java.lang.Double])
@@ -267,9 +249,54 @@ private[impl] class PekkoConnectorsResultMapperHelper {
else if (value.isInstanceOf[java.lang.Integer])
Instant.ofEpochMilli(toMillis(value.asInstanceOf[Integer].longValue,
precision))
else {
- throw new InfluxDBMapperException(s"""Unsupported type ${field.getClass}
for field ${field.getName}""")
+ throw new InfluxDBMapperException(s"""Unsupported type for field
${colInfo.fieldName}""")
}
private def toMillis(value: Long, precision: TimeUnit) =
TimeUnit.MILLISECONDS.convert(value, precision)
}
+
+@InternalApi
+private[impl] object PekkoConnectorsResultMapperHelper {
+ private val CLASS_COLUMN_CACHE: ConcurrentHashMap[Class[?],
ConcurrentMap[String, ColumnInfo]] =
+ new ConcurrentHashMap()
+
+ private val CONSTRUCTOR_CACHE: ConcurrentHashMap[Class[?], MethodHandle] =
new ConcurrentHashMap()
+
+ private val LOOKUP = MethodHandles.lookup()
+
+ final class ColumnInfo(
+ val columnName: String,
+ val isTag: Boolean,
+ val fieldType: Class[?],
+ val varHandle: VarHandle,
+ val fieldName: String)
+
+ private def classColumnMap(clazz: Class[?]): ConcurrentMap[String,
ColumnInfo] = {
+ val columnMap: ConcurrentMap[String, ColumnInfo] = new ConcurrentHashMap()
+ var c = clazz
+
+ while (c != null) {
+ val fields = c.getDeclaredFields
+ if (fields.nonEmpty) {
+ val privateLookup = MethodHandles.privateLookupIn(c, LOOKUP)
+ for (field <- fields) {
+ val colAnnotation = field.getAnnotation(classOf[Column])
+ if (colAnnotation != null) {
+ val varHandle = privateLookup.findVarHandle(c, field.getName,
field.getType)
+ val colInfo = new ColumnInfo(
+ colAnnotation.name(),
+ colAnnotation.tag(),
+ field.getType,
+ varHandle,
+ field.getName)
+ columnMap.put(colAnnotation.name(), colInfo)
+ }
+ }
+ }
+ c = c.getSuperclass
+ }
+
+ columnMap
+ }
+}
diff --git a/influxdb/src/test/java/docs/javadsl/InfluxDbSourceTest.java
b/influxdb/src/test/java/docs/javadsl/InfluxDbSourceTest.java
index 390f6f44e..88bd96a34 100644
--- a/influxdb/src/test/java/docs/javadsl/InfluxDbSourceTest.java
+++ b/influxdb/src/test/java/docs/javadsl/InfluxDbSourceTest.java
@@ -56,7 +56,7 @@ public class InfluxDbSourceTest {
}
@BeforeEach
- public void setUp() throws Exception {
+ public void setUp() throws Throwable {
populateDatabase(influxDB, InfluxDbSourceCpu.class);
}
diff --git a/influxdb/src/test/java/docs/javadsl/InfluxDbTest.java
b/influxdb/src/test/java/docs/javadsl/InfluxDbTest.java
index ac092183a..a1391f92e 100644
--- a/influxdb/src/test/java/docs/javadsl/InfluxDbTest.java
+++ b/influxdb/src/test/java/docs/javadsl/InfluxDbTest.java
@@ -102,7 +102,7 @@ public class InfluxDbTest {
}
@BeforeEach
- public void setUp() throws Exception {
+ public void setUp() throws Throwable {
populateDatabase(influxDB, InfluxDbCpu.class);
}
diff --git a/influxdb/src/test/java/docs/javadsl/TestUtils.java
b/influxdb/src/test/java/docs/javadsl/TestUtils.java
index fdb453a54..a0bc26c71 100644
--- a/influxdb/src/test/java/docs/javadsl/TestUtils.java
+++ b/influxdb/src/test/java/docs/javadsl/TestUtils.java
@@ -17,7 +17,9 @@ import static docs.javadsl.TestConstants.INFLUXDB_URL;
import static docs.javadsl.TestConstants.PASSWORD;
import static docs.javadsl.TestConstants.USERNAME;
-import java.lang.reflect.Constructor;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -40,17 +42,27 @@ public class TestUtils {
// #init-client
}
- public static void populateDatabase(InfluxDB influxDB, Class<?> clazz)
throws Exception {
+ public static void populateDatabase(InfluxDB influxDB, Class<?> clazz)
throws Throwable {
InfluxDBMapper influxDBMapper = new InfluxDBMapper(influxDB);
- Constructor<?> cons =
- clazz.getConstructor(
- Instant.class, String.class, String.class, Double.class,
Boolean.class, Long.class);
+ MethodHandle cons =
+ MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())
+ .findConstructor(
+ clazz,
+ MethodType.methodType(
+ Void.TYPE,
+ Instant.class,
+ String.class,
+ String.class,
+ Double.class,
+ Boolean.class,
+ Long.class));
Object firstCore =
- cons.newInstance(
+ cons.invokeWithArguments(
Instant.now().minusSeconds(1000), "local_1", "eu-west-2", 1.4d,
true, 123l);
influxDBMapper.save(firstCore);
Object secondCore =
- cons.newInstance(Instant.now().minusSeconds(500), "local_2",
"eu-west-2", 1.4d, true, 123l);
+ cons.invokeWithArguments(
+ Instant.now().minusSeconds(500), "local_2", "eu-west-2", 1.4d,
true, 123l);
influxDBMapper.save(secondCore);
}
diff --git
a/influxdb/src/test/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelperSpec.scala
b/influxdb/src/test/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelperSpec.scala
new file mode 100644
index 000000000..dbda29e7a
--- /dev/null
+++
b/influxdb/src/test/scala/org/apache/pekko/stream/connectors/influxdb/impl/PekkoConnectorsResultMapperHelperSpec.scala
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pekko.stream.connectors.influxdb.impl
+
+import java.time.Instant
+import java.util.concurrent.TimeUnit
+
+import scala.jdk.CollectionConverters._
+
+import docs.scaladsl.InfluxDbSourceCpu
+import org.influxdb.dto.QueryResult
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+class PekkoConnectorsResultMapperHelperSpec extends AnyWordSpec with Matchers {
+
+ "PekkoConnectorsResultMapperHelper" should {
+ "map annotated fields with VarHandles" in {
+ val helper = new PekkoConnectorsResultMapperHelper
+ val series = new QueryResult.Series
+ series.setColumns(List("time", "hostname", "region", "idle",
"happydevop", "uptimesecs").asJava)
+ series.setValues(
+ List(
+ List[AnyRef](
+ "2026-07-06T09:42:00Z",
+ "local_1",
+ "eu-west-2",
+ Double.box(1.5d),
+ java.lang.Boolean.TRUE,
+ Double.box(123d)).asJava).asJava)
+
+ val parsed = helper.parseSeriesAs(classOf[InfluxDbSourceCpu], series,
TimeUnit.MILLISECONDS)
+
+ parsed should have size 1
+ parsed.head.getTime shouldBe Instant.parse("2026-07-06T09:42:00Z")
+ parsed.head.getHostname shouldBe "local_1"
+ parsed.head.getRegion shouldBe "eu-west-2"
+ parsed.head.getIdle shouldBe 1.5d
+ parsed.head.getHappydevop shouldBe true
+ parsed.head.getUptimeSecs shouldBe 123L
+ }
+
+ "convert annotated models to points with cached MethodHandles and
VarHandles" in {
+ val helper = new PekkoConnectorsResultMapperHelper
+ val model =
+ new InfluxDbSourceCpu(Instant.parse("2026-07-06T09:42:00Z"),
"local_2", "eu-west-1", 2.5d, false, 125L)
+
+ val lineProtocol = helper.convertModelToPoint(model).lineProtocol()
+
+ lineProtocol should include("cpu")
+ lineProtocol should include("hostname=local_2")
+ lineProtocol should include("region=eu-west-1")
+ lineProtocol should include("idle=2.5")
+ lineProtocol should include("happydevop=false")
+ lineProtocol should include("uptimesecs=125")
+ }
+ }
+}
diff --git a/jakartams/src/test/scala/docs/scaladsl/JmsConnectorsSpec.scala
b/jakartams/src/test/scala/docs/scaladsl/JmsConnectorsSpec.scala
index bf9c647a6..2bb356491 100644
--- a/jakartams/src/test/scala/docs/scaladsl/JmsConnectorsSpec.scala
+++ b/jakartams/src/test/scala/docs/scaladsl/JmsConnectorsSpec.scala
@@ -26,6 +26,7 @@ import org.mockito.Mockito._
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
+import java.lang.invoke.MethodHandles
import java.nio.charset.StandardCharsets
import java.util.UUID
import java.util.concurrent.atomic.AtomicInteger
@@ -511,11 +512,12 @@ class JmsConnectorsSpec extends JmsSpec {
// make sure connection was closed
eventually {
- val isClosedField =
-
connectionFactory.cachedConnection.getClass.getDeclaredFields.filter(x =>
x.getName == "closed").head
- isClosedField.setAccessible(true)
+ val conn = connectionFactory.cachedConnection
+ val connClass = conn.getClass
+ val lookup = MethodHandles.privateLookupIn(connClass,
MethodHandles.lookup())
+ val isClosedHandle = lookup.findVarHandle(connClass, "closed",
classOf[Boolean])
- val isClosedValue =
isClosedField.get(connectionFactory.cachedConnection)
+ val isClosedValue = isClosedHandle.get(conn)
isClosedValue shouldBe true
}
}
@@ -535,11 +537,12 @@ class JmsConnectorsSpec extends JmsSpec {
completionFuture.failed.futureValue shouldBe a[RuntimeException]
// make sure connection was closed
eventually {
- val isClosedField =
-
connectionFactory.cachedConnection.getClass.getDeclaredFields.filter(x =>
x.getName == "closed").head
- isClosedField.setAccessible(true)
+ val conn = connectionFactory.cachedConnection
+ val connClass = conn.getClass
+ val lookup = MethodHandles.privateLookupIn(connClass,
MethodHandles.lookup())
+ val isClosedHandle = lookup.findVarHandle(connClass, "closed",
classOf[Boolean])
- val isClosedValue =
isClosedField.get(connectionFactory.cachedConnection)
+ val isClosedValue = isClosedHandle.get(conn)
isClosedValue shouldBe true
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]