This is an automated email from the ASF dual-hosted git repository.

He-Pin pushed a commit to branch refactor/replace-reflection-with-methodhandles
in repository https://gitbox.apache.org/repos/asf/pekko-projection.git

commit 306f0e02d878b93b3a648235b947e7b4bb9ab9f9
Author: 虎鸣 <[email protected]>
AuthorDate: Mon Jul 6 19:43:40 2026 +0800

    refactor: replace projection reflective access with handles
    
    Motivation:
    Reduce direct reflection in projection tests and gRPC internals while 
preserving existing runtime behavior.
    
    Modification:
    Use MethodHandles for H2 driver class lookup and initialization, protobuf 
parser access, and Scala companion construction paths.
    
    Result:
    Projection dynamic access paths avoid Class.forName and reflective 
invocation in the touched modules.
---
 .../java/jdocs/jdbc/JdbcProjectionDocExample.java  |  8 +++---
 .../scala/docs/jdbc/JdbcProjectionDocExample.scala |  3 ++-
 .../grpc/internal/ConsumerFilterStore.scala        | 29 +++++++++++++++++-----
 .../grpc/internal/ProtoAnySerialization.scala      | 16 +++++++-----
 .../jdbc/JdbcContainerOffsetStoreSpec.scala        |  3 ++-
 .../pekko/projection/jdbc/JdbcProjectionSpec.scala |  3 ++-
 .../pekko/projection/jdbc/JdbcProjectionTest.java  |  6 +++--
 .../projection/jdbc/JdbcOffsetStoreSpec.scala      |  3 ++-
 8 files changed, 50 insertions(+), 21 deletions(-)

diff --git a/examples/src/test/java/jdocs/jdbc/JdbcProjectionDocExample.java 
b/examples/src/test/java/jdocs/jdbc/JdbcProjectionDocExample.java
index f2399d07..05ab195a 100644
--- a/examples/src/test/java/jdocs/jdbc/JdbcProjectionDocExample.java
+++ b/examples/src/test/java/jdocs/jdbc/JdbcProjectionDocExample.java
@@ -34,9 +34,10 @@ import jakarta.persistence.EntityManager;
 
 // #jdbc-session-imports
 import org.apache.pekko.projection.jdbc.JdbcSession;
+import java.lang.invoke.MethodHandles;
+import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Connection;
 
 // #jdbc-session-imports
 
@@ -73,10 +74,11 @@ class JdbcProjectionDocExample {
 
     public PlainJdbcSession() {
       try {
-        Class.forName("org.h2.Driver");
+        MethodHandles.Lookup lookup = MethodHandles.lookup();
+        lookup.ensureInitialized(lookup.findClass("org.h2.Driver"));
         this.connection = 
DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
         connection.setAutoCommit(false);
-      } catch (ClassNotFoundException | SQLException e) {
+      } catch (ReflectiveOperationException | SQLException e) {
         throw new RuntimeException(e);
       }
     }
diff --git a/examples/src/test/scala/docs/jdbc/JdbcProjectionDocExample.scala 
b/examples/src/test/scala/docs/jdbc/JdbcProjectionDocExample.scala
index 87bf7498..96d67744 100644
--- a/examples/src/test/scala/docs/jdbc/JdbcProjectionDocExample.scala
+++ b/examples/src/test/scala/docs/jdbc/JdbcProjectionDocExample.scala
@@ -65,7 +65,8 @@ object JdbcProjectionDocExample {
   class PlainJdbcSession extends JdbcSession {
 
     lazy val conn = {
-      Class.forName("org.h2.Driver")
+      val lookup = java.lang.invoke.MethodHandles.lookup()
+      lookup.ensureInitialized(lookup.findClass("org.h2.Driver"))
       val c = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")
       c.setAutoCommit(false)
       c
diff --git 
a/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ConsumerFilterStore.scala
 
b/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ConsumerFilterStore.scala
index 641069eb..8ce241c3 100644
--- 
a/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ConsumerFilterStore.scala
+++ 
b/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ConsumerFilterStore.scala
@@ -13,6 +13,7 @@
 
 package org.apache.pekko.projection.grpc.internal
 
+import java.lang.invoke.{ MethodHandle, MethodHandles, MethodType }
 import java.util.ConcurrentModificationException
 import java.util.concurrent.ConcurrentHashMap
 
@@ -54,6 +55,15 @@ import org.slf4j.LoggerFactory
 @InternalApi private[pekko] object ConsumerFilterStore {
   sealed trait Command
 
+  private val lookup = MethodHandles.lookup()
+  private val ddataApplyHandles = new ConcurrentHashMap[Class[?], MethodHandle]
+  private val ddataApplyMethodType =
+    MethodType.methodType(
+      classOf[Behavior[?]],
+      classOf[ConsumerFilterSettings],
+      classOf[String],
+      classOf[ActorRef[ConsumerFilterRegistry.FilterUpdated]])
+
   final case class UpdateFilter(criteria: immutable.Seq[FilterCriteria]) 
extends Command
 
   final case class GetFilter(replyTo: ActorRef[ConsumerFilter.CurrentFilter]) 
extends Command
@@ -94,18 +104,25 @@ import org.slf4j.LoggerFactory
       .dynamicAccess
       
.getObjectFor[Any]("org.apache.pekko.projection.grpc.internal.DdataConsumerFilterStore")
 match {
       case Success(companion) =>
-        val applyMethod = companion.getClass.getMethod(
-          "apply",
-          classOf[ConsumerFilterSettings],
-          classOf[String],
-          classOf[ActorRef[ConsumerFilterRegistry.FilterUpdated]])
-        applyMethod.invoke(companion, settings, streamId, 
notifyUpdatesTo).asInstanceOf[Behavior[Command]]
+        ddataApplyHandle(companion.getClass)
+          .invoke(companion, settings, streamId, notifyUpdatesTo)
+          .asInstanceOf[Behavior[Command]]
       case Failure(exc) =>
         LoggerFactory.getLogger(className).error2("Couldn't create instance of 
[{}]", className, exc)
         throw exc
     }
   }
 
+  private def ddataApplyHandle(companionClass: Class[?]): MethodHandle = {
+    val cached = ddataApplyHandles.get(companionClass)
+    if (cached ne null) cached
+    else {
+      val handle = lookup.findVirtual(companionClass, "apply", 
ddataApplyMethodType)
+      val existing = ddataApplyHandles.putIfAbsent(companionClass, handle)
+      if (existing eq null) handle else existing
+    }
+  }
+
 }
 
 /**
diff --git 
a/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ProtoAnySerialization.scala
 
b/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ProtoAnySerialization.scala
index a9a0dc4f..39078e6e 100644
--- 
a/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ProtoAnySerialization.scala
+++ 
b/grpc/src/main/scala/org/apache/pekko/projection/grpc/internal/ProtoAnySerialization.scala
@@ -13,6 +13,8 @@
 
 package org.apache.pekko.projection.grpc.internal
 
+import java.lang.invoke.{ MethodHandle, MethodHandles, MethodType }
+
 import scala.collection.concurrent.TrieMap
 import scala.collection.immutable
 import scala.jdk.CollectionConverters._
@@ -43,6 +45,8 @@ import scalapb.options.Scalapb
   final val PekkoSerializationTypeUrlPrefix = "ser.pekko.io/"
   final val PekkoTypeUrlManifestSeparator = ':'
   private final val ProtoAnyTypeUrl = GoogleTypeUrlPrefix + 
"google.protobuf.Any"
+  private val parserMethodType = MethodType.methodType(classOf[Parser[?]])
+  private val parserHandles = TrieMap.empty[Class[?], MethodHandle]
 
   private val log = LoggerFactory.getLogger(classOf[ProtoAnySerialization])
 
@@ -218,10 +222,7 @@ import scalapb.options.Scalapb
       log.debug("tryResolveJavaPbType attempting to load class {}", className)
 
       val clazz = system.dynamicAccess.getClassFor[Any](className).get
-      val parser = clazz
-        .getMethod("parser")
-        .invoke(null)
-        .asInstanceOf[Parser[com.google.protobuf.Message]]
+      val parser = 
parserHandle(clazz).invoke().asInstanceOf[Parser[com.google.protobuf.Message]]
       Some(new JavaPbResolvedType(parser))
 
     } catch {
@@ -229,13 +230,13 @@ import scalapb.options.Scalapb
         log.debug2("Failed to load class [{}] because: {}", className, 
cnfe.getMessage)
         None
       case nsme: NoSuchElementException =>
-        // Not sure this is exception is thrown. NoSuchMethodException is 
thrown from getMethod("parser").
+        // Not sure this is exception is thrown. NoSuchMethodException is 
thrown from the parser MethodHandle lookup.
         // It was like this in the original Kalix JVM SDK.
         throw SerializationException(
           s"Found com.google.protobuf.Message class $className to deserialize 
protobuf ${typeDescriptor.getFullName} but it didn't have a static parser() 
method on it.",
           nsme)
       case _: NoSuchMethodException =>
-        // NoSuchMethodException may be thrown from getMethod("parser") if the 
ScalaPB class can be loaded,
+        // NoSuchMethodException may be thrown from the MethodHandle lookup if 
the ScalaPB class can be loaded,
         // but the ScalaPB class doesn't have the parser method.
         None
       case iae @ (_: IllegalAccessException | _: IllegalArgumentException) =>
@@ -245,6 +246,9 @@ import scalapb.options.Scalapb
     }
   }
 
+  private def parserHandle(clazz: Class[?]): MethodHandle =
+    parserHandles.getOrElseUpdate(clazz, 
MethodHandles.publicLookup().findStatic(clazz, "parser", parserMethodType))
+
   private def hasExtension[ContainerT <: 
com.google.protobuf.GeneratedMessage.ExtendableMessage[ContainerT], T](
       msg: 
com.google.protobuf.GeneratedMessage.ExtendableMessageOrBuilder[ContainerT],
       ext: com.google.protobuf.GeneratedMessage.GeneratedExtension[ContainerT, 
T]
diff --git 
a/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcContainerOffsetStoreSpec.scala
 
b/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcContainerOffsetStoreSpec.scala
index 6fea2116..73e2a946 100644
--- 
a/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcContainerOffsetStoreSpec.scala
+++ 
b/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcContainerOffsetStoreSpec.scala
@@ -55,7 +55,8 @@ object JdbcContainerOffsetStoreSpec {
       val container = _container.get
 
       new PureJdbcSession(() => {
-        Class.forName(container.getDriverClassName)
+        val lookup = java.lang.invoke.MethodHandles.lookup()
+        
lookup.ensureInitialized(lookup.findClass(container.getDriverClassName))
         val conn =
           DriverManager.getConnection(container.getJdbcUrl, 
container.getUsername, container.getPassword)
         conn.setAutoCommit(false)
diff --git 
a/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcProjectionSpec.scala
 
b/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcProjectionSpec.scala
index 45bd0914..6561b6ac 100644
--- 
a/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcProjectionSpec.scala
+++ 
b/jdbc-int-test/src/test/scala/org/apache/pekko/projection/jdbc/JdbcProjectionSpec.scala
@@ -87,7 +87,8 @@ object JdbcProjectionSpec {
   class PureJdbcSession extends JdbcSession {
 
     lazy val conn = {
-      Class.forName("org.h2.Driver")
+      val lookup = java.lang.invoke.MethodHandles.lookup()
+      lookup.ensureInitialized(lookup.findClass("org.h2.Driver"))
       val c = 
DriverManager.getConnection("jdbc:h2:mem:jdbc-projection-test;DB_CLOSE_DELAY=-1")
       c.setAutoCommit(false)
       c
diff --git 
a/jdbc/src/test/java/org/apache/pekko/projection/jdbc/JdbcProjectionTest.java 
b/jdbc/src/test/java/org/apache/pekko/projection/jdbc/JdbcProjectionTest.java
index 3d10617e..8dfb565c 100644
--- 
a/jdbc/src/test/java/org/apache/pekko/projection/jdbc/JdbcProjectionTest.java
+++ 
b/jdbc/src/test/java/org/apache/pekko/projection/jdbc/JdbcProjectionTest.java
@@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
+import java.lang.invoke.MethodHandles;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
@@ -87,11 +88,12 @@ public class JdbcProjectionTest {
 
     public PureJdbcSession() {
       try {
-        Class.forName("org.h2.Driver");
+        MethodHandles.Lookup lookup = MethodHandles.lookup();
+        lookup.ensureInitialized(lookup.findClass("org.h2.Driver"));
         Connection c = 
DriverManager.getConnection("jdbc:h2:mem:test-java;DB_CLOSE_DELAY=-1");
         c.setAutoCommit(false);
         this.connection = c;
-      } catch (ClassNotFoundException | SQLException e) {
+      } catch (ReflectiveOperationException | SQLException e) {
         throw new RuntimeException(e);
       }
     }
diff --git 
a/jdbc/src/test/scala/org/apache/pekko/projection/jdbc/JdbcOffsetStoreSpec.scala
 
b/jdbc/src/test/scala/org/apache/pekko/projection/jdbc/JdbcOffsetStoreSpec.scala
index c23691f1..1d3c9666 100644
--- 
a/jdbc/src/test/scala/org/apache/pekko/projection/jdbc/JdbcOffsetStoreSpec.scala
+++ 
b/jdbc/src/test/scala/org/apache/pekko/projection/jdbc/JdbcOffsetStoreSpec.scala
@@ -101,7 +101,8 @@ object JdbcOffsetStoreSpec {
 
     def jdbcSessionFactory(): PureJdbcSession =
       new PureJdbcSession(() => {
-        Class.forName("org.h2.Driver")
+        val lookup = java.lang.invoke.MethodHandles.lookup()
+        lookup.ensureInitialized(lookup.findClass("org.h2.Driver"))
         val conn = 
DriverManager.getConnection("jdbc:h2:mem:offset-store-test-jdbc;DB_CLOSE_DELAY=-1")
         conn.setAutoCommit(false)
         conn


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to