You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/09/06 16:52:30 UTC

(cayenne) 06/07: CAY-3012 Improve reflection performance

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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit ae18b685279664f1e5174cdb5818f48971412995
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:30:08 2026 -0400

    CAY-3012 Improve reflection performance
---
 .../apache/cayenne/reflect/AbstractCallback.java   | 41 ++++++++++++++++++++++
 .../apache/cayenne/reflect/CallbackOnEntity.java   | 24 ++++++-------
 .../apache/cayenne/reflect/CallbackOnListener.java | 30 +++++++---------
 .../apache/cayenne/map/MockCallingBackEntity.java  |  8 +++++
 .../cayenne/reflect/CallbackOnEntityTest.java      | 20 +++++++++++
 5 files changed, 92 insertions(+), 31 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java b/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java
index 4703293bb..840225b0f 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java
@@ -18,6 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne.reflect;
 
+import org.apache.cayenne.CayenneRuntimeException;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+
 /**
  * Defines a callback operation.
  * 
@@ -25,5 +32,39 @@ package org.apache.cayenne.reflect;
  */
 abstract class AbstractCallback {
 
+    /**
+     * The uniform shape every callback handle is adapted to, so that it can be called with "invokeExact".
+     */
+    static final MethodType CALLBACK_TYPE = MethodType.methodType(void.class, Object.class);
+
     abstract void performCallback(Object entity);
+
+    /**
+     * Turns a validated (and, if needed, made accessible) callback method into a method handle.
+     */
+    static MethodHandle unreflect(Method method) {
+        try {
+            return MethodHandles.lookup().unreflect(method);
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException("Inaccessible callback method: " + method, e);
+        }
+    }
+
+    /**
+     * Rethrows an exception raised by a callback. Unchecked exceptions and errors propagate as they are, checked
+     * exceptions are wrapped.
+     */
+    static RuntimeException callbackFailure(String callbackName, Throwable th) {
+        if (th instanceof RuntimeException re) {
+            return re;
+        }
+        if (th instanceof Error error) {
+            throw error;
+        }
+        return new CayenneRuntimeException("Error invoking callback method " + callbackName, th);
+    }
+
+    static String callbackName(Method method) {
+        return method.getDeclaringClass().getName() + "." + method.getName();
+    }
 }
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java
index ecf5f82b7..44b4f2945 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java
@@ -18,10 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.reflect;
 
+import java.lang.invoke.MethodHandle;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
-import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.util.Util;
 
 /**
@@ -33,7 +33,8 @@ import org.apache.cayenne.util.Util;
  */
 class CallbackOnEntity extends AbstractCallback {
 
-    private final Method callbackMethod;
+    private final String callbackName;
+    private final MethodHandle callbackHandle;
 
     CallbackOnEntity(Class<?> objectClass, String methodName) throws IllegalArgumentException {
         this(findMethod(objectClass, methodName));
@@ -47,28 +48,25 @@ class CallbackOnEntity extends AbstractCallback {
             throw new IllegalArgumentException("Class " + method.getDeclaringClass().getName()
                     + " has no valid callback method '" + method.getName() + "'");
         }
-        this.callbackMethod = method;
-        if (!Util.isAccessible(callbackMethod)) {
-            callbackMethod.setAccessible(true);
+        if (!Util.isAccessible(method)) {
+            method.setAccessible(true);
         }
+        this.callbackName = callbackName(method);
+        this.callbackHandle = unreflect(method).asType(CALLBACK_TYPE);
     }
 
     @Override
     public void performCallback(Object entity) {
         try {
-            callbackMethod.invoke(entity, (Object[]) null);
-        } catch (Exception e) {
-            throw new CayenneRuntimeException("Error invoking entity callback method "
-                    + callbackMethod.getName(), e);
+            callbackHandle.invokeExact(entity);
+        } catch (Throwable th) {
+            throw callbackFailure(callbackName, th);
         }
     }
 
     @Override
     public String toString() {
-        return "callback-entity: "
-                + callbackMethod.getDeclaringClass().getName()
-                + "."
-                + callbackMethod.getName();
+        return "callback-entity: " + callbackName;
     }
 
     static private boolean validateMethod(Method method) {
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
index e23e2f9a0..fa8c1a1d0 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
@@ -18,10 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.reflect;
 
+import java.lang.invoke.MethodHandle;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
-import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.util.Util;
 
 /**
@@ -33,8 +33,8 @@ import org.apache.cayenne.util.Util;
  */
 class CallbackOnListener extends AbstractCallback {
 
-    private Method callbackMethod;
-    private Object listener;
+    private final String callbackName;
+    private final MethodHandle callbackHandle;
 
     CallbackOnListener(Object listener, String methodName)
             throws IllegalArgumentException {
@@ -48,8 +48,9 @@ class CallbackOnListener extends AbstractCallback {
             throw new IllegalArgumentException("Null listener");
         }
 
-        this.callbackMethod = findMethod(listener.getClass(), methodName, entityType);
-        this.listener = listener;
+        Method method = findMethod(listener.getClass(), methodName, entityType);
+        this.callbackName = callbackName(method);
+        this.callbackHandle = unreflect(method).bindTo(listener).asType(CALLBACK_TYPE);
     }
 
     CallbackOnListener(Object listener, Method method, Class<?> entityType)
@@ -64,29 +65,22 @@ class CallbackOnListener extends AbstractCallback {
                     + method.getName());
         }
 
-        this.callbackMethod = method;
-        this.listener = listener;
+        this.callbackName = callbackName(method);
+        this.callbackHandle = unreflect(method).bindTo(listener).asType(CALLBACK_TYPE);
     }
 
     @Override
     public void performCallback(Object entity) {
         try {
-            callbackMethod.invoke(listener, entity);
-        }
-        catch (Exception e) {
-            throw new CayenneRuntimeException(
-                    "Error invoking entity listener callback method "
-                            + callbackMethod.getName(),
-                    e);
+            callbackHandle.invokeExact(entity);
+        } catch (Throwable th) {
+            throw callbackFailure(callbackName, th);
         }
     }
 
     @Override
     public String toString() {
-        return "callback-listener: "
-                + callbackMethod.getDeclaringClass().getName()
-                + "."
-                + callbackMethod.getName();
+        return "callback-listener: " + callbackName;
     }
 
     private boolean verifyMethod(Method method, Class<?> entityType) {
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java b/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java
index 36b0bb038..a4332afd4 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java
@@ -44,4 +44,12 @@ public class MockCallingBackEntity {
     void defaultCallback() {
         defaultCallbackInvoked = true;
     }
+
+    public void uncheckedThrowingCallback() {
+        throw new IllegalStateException("unchecked");
+    }
+
+    public void checkedThrowingCallback() throws Exception {
+        throw new Exception("checked");
+    }
 }
diff --git a/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java b/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java
index 861983bf0..6feeffc2e 100644
--- a/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java
@@ -18,9 +18,11 @@
  ****************************************************************/
 package org.apache.cayenne.reflect;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.map.MockCallingBackEntity;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -83,6 +85,24 @@ public class CallbackOnEntityTest {
         assertTrue(e.defaultCallbackInvoked);
     }
 
+    @Test
+    public void uncheckedExceptionPropagatesAsIs() {
+        CallbackOnEntity callback = new CallbackOnEntity(MockCallingBackEntity.class, "uncheckedThrowingCallback");
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> callback.performCallback(new MockCallingBackEntity()));
+        assertEquals("unchecked", e.getMessage());
+    }
+
+    @Test
+    public void checkedExceptionIsWrapped() {
+        CallbackOnEntity callback = new CallbackOnEntity(MockCallingBackEntity.class, "checkedThrowingCallback");
+
+        CayenneRuntimeException e = assertThrows(CayenneRuntimeException.class,
+                () -> callback.performCallback(new MockCallingBackEntity()));
+        assertEquals("checked", e.getCause().getMessage());
+    }
+
     @Test
     public void staticCallbackMethod() {
         assertThrows(IllegalArgumentException.class,