From a6fe28d5189ed06c4a70cf7f8b38d792722c31bf Mon Sep 17 00:00:00 2001 From: godotg Date: Sat, 21 Oct 2023 20:46:14 +0800 Subject: [PATCH] ref[storage]: refactor lambda of storage --- .../com/zfoo/protocol/util/FieldUtils.java | 64 ++++++++++---- .../zfoo/storage/manager/StorageObject.java | 86 +++++++++++++++++-- .../com/zfoo/storage/util/LambdaUtils.java | 68 --------------- .../storage/export/ExportBinaryTesting.java | 10 --- .../com/zfoo/storage/util/FieldUtilsTest.java | 39 --------- .../storage/util/LambdaFunctionCacheTest.java | 30 ------- .../zfoo/storage/util/LambdaFunctionTest.java | 16 +++- 7 files changed, 135 insertions(+), 178 deletions(-) delete mode 100644 storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java delete mode 100644 storage/src/test/java/com/zfoo/storage/util/FieldUtilsTest.java delete mode 100644 storage/src/test/java/com/zfoo/storage/util/LambdaFunctionCacheTest.java diff --git a/protocol/src/main/java/com/zfoo/protocol/util/FieldUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/FieldUtils.java index d726bb94..25ad2ac9 100644 --- a/protocol/src/main/java/com/zfoo/protocol/util/FieldUtils.java +++ b/protocol/src/main/java/com/zfoo/protocol/util/FieldUtils.java @@ -3,6 +3,7 @@ package com.zfoo.protocol.util; import com.zfoo.protocol.exception.RunException; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Locale; /** @@ -69,29 +70,54 @@ public abstract class FieldUtils { } } - public static String methodToProperty(String name) { - if (name.startsWith("is")) { - name = name.substring(2); - } else if (name.startsWith("get") || name.startsWith("set")) { - name = name.substring(3); + public static String getMethodToField(Class clazz, String methodName) { + try { + // 查看clazz时候真的有methodName方法 + clazz.getDeclaredMethod(methodName); + } catch (NoSuchMethodException e) { + throw new RunException("clazz:[{}] has no getMethod:[{}]", clazz.getSimpleName(), methodName); } - if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) { - name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1); + var fieldName = methodName; + if (clazz.isRecord()) { + try { + clazz.getDeclaredField(fieldName); + return fieldName; + } catch (NoSuchFieldException e) { + throw new RunException("record clazz:[{}] has no field:[{}]", clazz.getSimpleName(), fieldName); + } } - return name; + // get method + fieldName = StringUtils.substringAfterFirst(methodName, "get"); + try { + clazz.getDeclaredField(fieldName); + return fieldName; + } catch (NoSuchFieldException e) { + } + + fieldName = StringUtils.uncapitalize(fieldName); + try { + clazz.getDeclaredField(fieldName); + return fieldName; + } catch (NoSuchFieldException e) { + } + + // is method + fieldName = StringUtils.substringAfterFirst(methodName, "is"); + try { + clazz.getDeclaredField(fieldName); + return fieldName; + } catch (NoSuchFieldException e) { + } + + fieldName = StringUtils.uncapitalize(fieldName); + try { + clazz.getDeclaredField(fieldName); + return fieldName; + } catch (NoSuchFieldException e) { + throw new RunException("clazz:[{}] has no field for getMethod:[{}]", clazz.getSimpleName(), methodName); + } } - public static boolean isProperty(String name) { - return isGetter(name) || isSetter(name); - } - - public static boolean isGetter(String name) { - return name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2; - } - - public static boolean isSetter(String name) { - return name.startsWith("set") && name.length() > 3; - } } diff --git a/storage/src/main/java/com/zfoo/storage/manager/StorageObject.java b/storage/src/main/java/com/zfoo/storage/manager/StorageObject.java index b58805e4..ba13bf94 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/StorageObject.java +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageObject.java @@ -13,25 +13,28 @@ package com.zfoo.storage.manager; import com.zfoo.protocol.collection.CollectionUtils; -import com.zfoo.protocol.util.AssertionUtils; -import com.zfoo.protocol.util.IOUtils; -import com.zfoo.protocol.util.ReflectionUtils; -import com.zfoo.protocol.util.StringUtils; +import com.zfoo.protocol.util.*; import com.zfoo.storage.interpreter.ResourceInterpreter; import com.zfoo.storage.model.IStorage; import com.zfoo.storage.model.IdDef; import com.zfoo.storage.model.IndexDef; -import com.zfoo.storage.util.LambdaUtils; import com.zfoo.storage.util.function.Func1; +import com.zfoo.storage.util.lambda.*; import org.springframework.lang.Nullable; +import org.springframework.util.ConcurrentReferenceHashMap; import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.*; /** * @author godotg */ public class StorageObject implements IStorage { + // all storage data private Map dataMap; // 非唯一索引 protected Map>> indexMap = new HashMap<>(); @@ -41,9 +44,11 @@ public class StorageObject implements IStorage { protected Class clazz; protected IdDef idDef; protected Map indexDefMap; - // 当前配置表是否在当前项目中使用,没有被使用的会清除data数据,以达到节省内存的目的 + // EN: unused configuration tables will clear data to save memory. + // CN: 没有被使用的配置表会清除data数据,以达到节省内存的目的 protected boolean recycle = true; + private ConcurrentReferenceHashMap, String> funcCaches = new ConcurrentReferenceHashMap<>(); public static StorageObject parse(InputStream inputStream, Class resourceClazz, String suffix) { var idDef = IdDef.valueOf(resourceClazz); @@ -162,7 +167,7 @@ public class StorageObject implements IStorage { @Override public List getIndexes(Func1 func, INDEX index) { - String indexName = LambdaUtils.getFieldName(func); + String indexName = getMethodToField(func); var indexValues = indexMap.get(indexName); AssertionUtils.notNull(indexValues, "The index of [indexName:{}] does not exist in the static resource [resource:{}]", indexName, clazz.getSimpleName()); var values = indexValues.get(index); @@ -175,7 +180,7 @@ public class StorageObject implements IStorage { @Nullable @Override public V getUniqueIndex(Func1 func, INDEX index) { - String uniqueIndexName = LambdaUtils.getFieldName(func); + String uniqueIndexName = getMethodToField(func); var indexValueMap = uniqueIndexMap.get(uniqueIndexName); AssertionUtils.notNull(indexValueMap, "There is no a unique index for [uniqueIndexName:{}] in the static resource [resource:{}]", uniqueIndexName, clazz.getSimpleName()); var value = indexValueMap.get(index); @@ -212,4 +217,69 @@ public class StorageObject implements IStorage { return dataMap.size(); } + private String getMethodToField(Func1 func) { + var indexName = funcCaches.get(func); + if (indexName != null) { + return indexName; + } + + // 1. IDEA 调试模式下 lambda 表达式是一个代理 + if (func instanceof Proxy) { + try { + var lambda = new IdeaProxyLambdaMeta((Proxy) func); + indexName = FieldUtils.getMethodToField(clazz, lambda.getImplMethodName()); + } catch (Exception e) { + } + } + + // 2. 反射读取 + if (indexName == null) { + try { + var method = func.getClass().getDeclaredMethod("writeReplace"); + ReflectionUtils.makeAccessible(method); + var lambda = new ReflectLambdaMeta((java.lang.invoke.SerializedLambda) method.invoke(func)); + indexName = FieldUtils.getMethodToField(clazz, lambda.getImplMethodName()); + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + } + } + + // 3. 反射失败使用序列化的方式读取 + if (indexName == null) { + try { + var lambda = new ShadowLambdaMeta(SerializedLambda.extract(func)); + indexName = FieldUtils.getMethodToField(clazz, lambda.getImplMethodName()); + } catch (Exception e) { + } + } + + // 4. 通过将func带入到dataMap中求解,适合GraalVM环境中 + if (indexName == null) { + try { + var fields = clazz.getDeclaredFields(); + Arrays.stream(fields).forEach(ReflectionUtils::makeAccessible); + for (var value : dataMap.values()) { + var r = func.call(value); + var valueFields = Arrays.stream(fields) + .map(it -> ReflectionUtils.getField(it, value)) + .filter(it -> it.equals(r) && it.getClass() == r.getClass()) + .toList(); + // 如果只有一个能匹配到func的返回值则就是这个方法 + if (valueFields.size() == 1) { + for (var field : fields) { + if (!ReflectionUtils.getField(field, value).equals(r)) { + continue; + } + indexName = field.getName(); + break; + } + break; + } + } + } catch (Exception e) { + } + } + + funcCaches.put(func, indexName); + return indexName; + } } diff --git a/storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java b/storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java deleted file mode 100644 index d8f396c1..00000000 --- a/storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.zfoo.storage.util; - -import com.zfoo.protocol.util.FieldUtils; -import com.zfoo.protocol.util.ReflectionUtils; -import com.zfoo.storage.util.function.Func1; -import com.zfoo.storage.util.lambda.*; -import org.springframework.util.ConcurrentReferenceHashMap; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -/** - * @author veione - * @version 1.0 - */ -public abstract class LambdaUtils { - private static final ConcurrentReferenceHashMap, String> CACHE = new ConcurrentReferenceHashMap<>(64, 0.75F, 16, ConcurrentReferenceHashMap.ReferenceType.SOFT); - - /** - * 该缓存可能会在任意不定的时间被清除 - * - * @param func 需要解析的 lambda 对象 - * @return 返回解析后的结果 - */ - public static LambdaMeta extract(Serializable func) { - // 1. IDEA 调试模式下 lambda 表达式是一个代理 - if (func instanceof Proxy) { - return new IdeaProxyLambdaMeta((Proxy) func); - } - - // 2. 反射读取 - try { - Class clazz = func.getClass(); - Method method = clazz.getDeclaredMethod("writeReplace"); - ReflectionUtils.makeAccessible(method); - return new ReflectLambdaMeta((java.lang.invoke.SerializedLambda) method.invoke(func)); - } catch (Throwable e) { - } - - // 3. 反射失败使用序列化的方式读取 - return new ShadowLambdaMeta(SerializedLambda.extract(func)); - } - - - /** - * 获取lambda表达式函数(方法)名称 - * - * @param Lambda类型 - * @param func 函数(无参方法) - * @return 函数名称 - */ - public static String getMethodName(Func1 func) { - return CACHE.computeIfAbsent(func.getClass(), it -> extract(func).getImplMethodName()); - } - - /** - * 获取lambda表达式函数(字段)名称 - * - * @param Lambda类型 - * @param func 函数 - * @return 字段名称 - */ - public static String getFieldName(Func1 func) { - return FieldUtils.methodToProperty(getMethodName(func)); - } - -} diff --git a/storage/src/test/java/com/zfoo/storage/export/ExportBinaryTesting.java b/storage/src/test/java/com/zfoo/storage/export/ExportBinaryTesting.java index 6cc579dc..69b300d7 100644 --- a/storage/src/test/java/com/zfoo/storage/export/ExportBinaryTesting.java +++ b/storage/src/test/java/com/zfoo/storage/export/ExportBinaryTesting.java @@ -16,7 +16,6 @@ import com.zfoo.protocol.ProtocolManager; import com.zfoo.protocol.buffer.ByteBufUtils; import com.zfoo.protocol.generate.GenerateOperation; import com.zfoo.protocol.serializer.CodeLanguage; -import com.zfoo.protocol.util.FieldUtils; import com.zfoo.protocol.util.FileUtils; import com.zfoo.protocol.util.JsonUtils; import com.zfoo.storage.anno.AliasFieldName; @@ -27,8 +26,6 @@ import com.zfoo.storage.config.StorageConfig; import com.zfoo.storage.manager.StorageInt; import com.zfoo.storage.manager.StorageManager; import com.zfoo.storage.util.ExportUtils; -import com.zfoo.storage.util.LambdaUtils; -import com.zfoo.storage.util.function.Func1; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledHeapByteBuf; import org.junit.Ignore; @@ -112,13 +109,6 @@ public class ExportBinaryTesting { var bytes = ByteBufUtils.readAllBytes(buffer); FileUtils.writeInputStreamToFile(new File("D:/github/godot-bird/binary_data.cfg"), new ByteArrayInputStream(bytes)); - Func1 ageFunc = StudentResource::age; - String methodName = LambdaUtils.extract(ageFunc).getImplMethodName(); - String fieldName = FieldUtils.methodToProperty(methodName); - System.out.println(methodName); - System.out.println(fieldName); - - //获取storage对象 var storage = storageManager.getStorage(StudentResource.class); //获取唯一索引的对象 diff --git a/storage/src/test/java/com/zfoo/storage/util/FieldUtilsTest.java b/storage/src/test/java/com/zfoo/storage/util/FieldUtilsTest.java deleted file mode 100644 index 15cd8ba4..00000000 --- a/storage/src/test/java/com/zfoo/storage/util/FieldUtilsTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2020 The zfoo Authors - * Licensed 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 com.zfoo.storage.util; - -import com.zfoo.protocol.util.FieldUtils; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * @author veione - */ -public class FieldUtilsTest { - - @Test - public void testPropertyName() { - //前三种主要用于普通的POJO,第四种用于是record类型,方法名就是属性名 - String getName = FieldUtils.methodToProperty("getName"); - assertEquals(getName, "name"); - String setName = FieldUtils.methodToProperty("setName"); - assertEquals(setName, "name"); - String isName = FieldUtils.methodToProperty("isName"); - assertEquals(isName, "name"); - String name = FieldUtils.methodToProperty("name"); - assertEquals(name, "name"); - name = FieldUtils.methodToProperty("Name"); - assertEquals(name, "name"); - } -} diff --git a/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionCacheTest.java b/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionCacheTest.java deleted file mode 100644 index 1f0f88de..00000000 --- a/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionCacheTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2020 The zfoo Authors - * Licensed 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 com.zfoo.storage.util; - -import com.zfoo.storage.resource.StudentResource; -import com.zfoo.storage.util.function.Func1; -import org.junit.Test; - -/** - * @author veione - */ -public class LambdaFunctionCacheTest { - - @Test - public void testFunctionCache() { - Func1 nameFunc = StudentResource::getName; - System.out.println(LambdaUtils.getMethodName(nameFunc)); - System.out.println(LambdaUtils.getMethodName(nameFunc)); - } -} diff --git a/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionTest.java b/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionTest.java index 46598803..f4169394 100644 --- a/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionTest.java +++ b/storage/src/test/java/com/zfoo/storage/util/LambdaFunctionTest.java @@ -12,9 +12,10 @@ package com.zfoo.storage.util; +import com.zfoo.storage.resource.StudentResource; import com.zfoo.storage.resource.TeacherResource; import com.zfoo.storage.util.function.Func1; -import com.zfoo.storage.util.lambda.LambdaMeta; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; @@ -24,6 +25,8 @@ import java.lang.reflect.Method; * @author veione */ public class LambdaFunctionTest { + + // https://blog.csdn.net/iteye_19045/article/details/119299015 @Test public void testFuncSerialization() throws Exception { @@ -35,15 +38,20 @@ public class LambdaFunctionTest { Method writeReplace = clazz.getDeclaredMethod("writeReplace"); System.out.println(writeReplace); - LambdaMeta meta = LambdaUtils.extract(func); - System.out.println(meta); - // Function func2 = StudentResource::name; // Method method = func2.getClass().getDeclaredMethod("writeReplace"); // object = method.invoke(func2); // System.out.println(object); } + + @Test + public void testFunctionCache() { + Func1 nameFunc1 = StudentResource::getName; + Func1 nameFunc2 = StudentResource::getName; + Assert.assertNotEquals(nameFunc1, nameFunc2); + } + private static Object getObject(Serializable func) throws Exception { Method method = func.getClass().getDeclaredMethod("writeReplace"); Object object = method.invoke(func);