From cfc52d3d35c9e18952a2b580c3dff557814352ce Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Tue, 17 Mar 2026 11:23:36 +0400 Subject: [PATCH] Chart value descriptor class can not be hot-deployed jmix-framework/jmix_5129 --- .../data/item/SimpleDataItem.java | 7 +- .../metamodel/model/utils/MethodsCache.java | 102 ++++++++++++------ .../data/ListPivotTableItems.java | 5 +- 3 files changed, 69 insertions(+), 45 deletions(-) diff --git a/jmix-charts/charts-flowui/src/main/java/io/jmix/chartsflowui/data/item/SimpleDataItem.java b/jmix-charts/charts-flowui/src/main/java/io/jmix/chartsflowui/data/item/SimpleDataItem.java index 2c4c9ab348..9d0a00a306 100644 --- a/jmix-charts/charts-flowui/src/main/java/io/jmix/chartsflowui/data/item/SimpleDataItem.java +++ b/jmix-charts/charts-flowui/src/main/java/io/jmix/chartsflowui/data/item/SimpleDataItem.java @@ -23,16 +23,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * Data item, which contains an instance of any class. */ public class SimpleDataItem implements DataItem { - private static final Map, MethodsCache> methodCacheMap = new ConcurrentHashMap<>(); - protected Object item; public SimpleDataItem(Object item) { @@ -95,8 +91,7 @@ public Object getValue(String path) { } protected MethodsCache getMethodsCache(Object object) { - Class cls = object.getClass(); - return methodCacheMap.computeIfAbsent(cls, k -> MethodsCache.getOrCreate(cls)); + return MethodsCache.getOrCreate(object.getClass()); } @Override diff --git a/jmix-core/core/src/main/java/io/jmix/core/metamodel/model/utils/MethodsCache.java b/jmix-core/core/src/main/java/io/jmix/core/metamodel/model/utils/MethodsCache.java index 7f9cb7ca54..12017688c1 100644 --- a/jmix-core/core/src/main/java/io/jmix/core/metamodel/model/utils/MethodsCache.java +++ b/jmix-core/core/src/main/java/io/jmix/core/metamodel/model/utils/MethodsCache.java @@ -18,16 +18,15 @@ import com.google.common.collect.ImmutableMap; import io.jmix.core.metamodel.annotation.JmixProperty; import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; -import org.springframework.lang.Nullable; import java.lang.invoke.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Function; @@ -48,10 +47,15 @@ public class MethodsCache { .put(boolean.class, Boolean.class) .build(); - private static final Map methodCacheMap = new ConcurrentHashMap<>(); + private static final ClassValue methodCacheMap = new ClassValue<>() { + @Override + protected MethodsCache computeValue(Class type) { + return new MethodsCache(type); + } + }; public static MethodsCache getOrCreate(Class clazz) { - return methodCacheMap.computeIfAbsent(clazz, MethodsCache::new); + return methodCacheMap.get(clazz); } private MethodsCache(Class clazz) { @@ -153,42 +157,70 @@ private Method chooseGetter(Class clazz, String propertyName, Method found, @Nul } private Function createGetter(Class clazz, Method method) { - Function getter; - try { - MethodHandles.Lookup caller = MethodHandles.lookup(); - CallSite site = LambdaMetafactory.metafactory(caller, - "apply", - MethodType.methodType(Function.class), - MethodType.methodType(Object.class, Object.class), - caller.findVirtual(clazz, method.getName(), MethodType.methodType(method.getReturnType())), - MethodType.methodType(method.getReturnType(), clazz)); - MethodHandle factory = site.getTarget(); - getter = (Function) factory.invoke(); - } catch (Throwable t) { - throw new RuntimeException("Can not create getter", t); + // If a class was hot-deployed, then it will be loaded + // by a different class loader. This will make it impossible to create a lambda + // using LambdaMetaFactory for producing the method in Java 17+ + if (getClass().getClassLoader() == clazz.getClassLoader()) { + try { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); + CallSite site = LambdaMetafactory.metafactory(lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), + lookup.unreflect(method), + MethodType.methodType(method.getReturnType(), clazz)); + return (Function) site.getTarget().invoke(); + } catch (Throwable t) { + throw new RuntimeException("Can not create getter", t); + } + } else { + try { + MethodHandle methodHandle = MethodHandles.lookup().unreflect(method); + return obj -> { + try { + return methodHandle.invoke(obj); + } catch (Throwable throwable) { + throw new RuntimeException("Error calling getter", throwable); + } + }; + } catch (IllegalAccessException e) { + throw new RuntimeException("Can not create getter", e); + } } - - return getter; } private BiConsumer createSetter(Class clazz, Method method) { - Class valueType = method.getParameterTypes()[0]; - BiConsumer setter; - try { - MethodHandles.Lookup caller = MethodHandles.lookup(); - CallSite site = LambdaMetafactory.metafactory(caller, - "accept", - MethodType.methodType(BiConsumer.class), - MethodType.methodType(void.class, Object.class, Object.class), - caller.findVirtual(clazz, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()[0])), - MethodType.methodType(void.class, clazz, valueType.isPrimitive() ? primitivesToObjects.get(valueType) : valueType)); - MethodHandle factory = site.getTarget(); - setter = (BiConsumer) factory.invoke(); - } catch (Throwable t) { - throw new RuntimeException("Can not create setter", t); + // If a class was hot-deployed, then it will be loaded + // by a different class loader. This will make it impossible to create a lambda + // using LambdaMetaFactory for producing the method in Java 17+ + if (getClass().getClassLoader() == clazz.getClassLoader()) { + try { + Class valueType = method.getParameterTypes()[0]; + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); + CallSite site = LambdaMetafactory.metafactory(lookup, + "accept", + MethodType.methodType(BiConsumer.class), + MethodType.methodType(void.class, Object.class, Object.class), + lookup.unreflect(method), + MethodType.methodType(void.class, clazz, valueType.isPrimitive() ? primitivesToObjects.get(valueType) : valueType)); + return (BiConsumer) site.getTarget().invoke(); + } catch (Throwable t) { + throw new RuntimeException("Can not create setter", t); + } + } else { + try { + MethodHandle methodHandle = MethodHandles.lookup().unreflect(method); + return (obj, value) -> { + try { + methodHandle.invoke(obj, value); + } catch (Throwable throwable) { + throw new RuntimeException("Error calling setter", throwable); + } + }; + } catch (IllegalAccessException e) { + throw new RuntimeException("Can not create setter", e); + } } - - return setter; } /** diff --git a/jmix-pivottable/pivottable-flowui/src/main/java/io/jmix/pivottableflowui/data/ListPivotTableItems.java b/jmix-pivottable/pivottable-flowui/src/main/java/io/jmix/pivottableflowui/data/ListPivotTableItems.java index 6692605bdd..e6e76dcd5b 100644 --- a/jmix-pivottable/pivottable-flowui/src/main/java/io/jmix/pivottableflowui/data/ListPivotTableItems.java +++ b/jmix-pivottable/pivottable-flowui/src/main/java/io/jmix/pivottableflowui/data/ListPivotTableItems.java @@ -26,7 +26,6 @@ import org.springframework.lang.Nullable; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; /** @@ -35,7 +34,6 @@ public class ListPivotTableItems implements PivotTableItems { private EventBus eventBus; - private static final Map, MethodsCache> methodCacheMap = new ConcurrentHashMap<>(); protected final List items = new ArrayList<>(); protected final String idAttribute; @@ -246,8 +244,7 @@ protected Object deserializeId(String stringValue) { } protected MethodsCache getMethodsCache(Object object) { - Class cls = object.getClass(); - return methodCacheMap.computeIfAbsent(cls, k -> MethodsCache.getOrCreate(cls)); + return MethodsCache.getOrCreate(object.getClass()); } protected void fireChangeEvent(ItemsChangeType operationType, Collection items) {