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,