ref[storage]: refactor lambda of storage

This commit is contained in:
godotg
2023-10-21 20:46:14 +08:00
parent 592523a3bd
commit a6fe28d518
7 changed files with 135 additions and 178 deletions
@@ -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;
}
}
@@ -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<K, V> implements IStorage<K, V> {
// all storage data
private Map<K, V> dataMap;
// 非唯一索引
protected Map<String, Map<Object, List<V>>> indexMap = new HashMap<>();
@@ -41,9 +44,11 @@ public class StorageObject<K, V> implements IStorage<K, V> {
protected Class<?> clazz;
protected IdDef idDef;
protected Map<String, IndexDef> indexDefMap;
// 当前配置表是否在当前项目中使用,没有被使用的会清除data数据,以达到节省内存的目的
// EN: unused configuration tables will clear data to save memory.
// CN: 没有被使用的配置表会清除data数据,以达到节省内存的目的
protected boolean recycle = true;
private ConcurrentReferenceHashMap<Func1<V, ?>, 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<K, V> implements IStorage<K, V> {
@Override
public <INDEX> List<V> getIndexes(Func1<V, INDEX> 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<K, V> implements IStorage<K, V> {
@Nullable
@Override
public <INDEX> V getUniqueIndex(Func1<V, INDEX> 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<K, V> implements IStorage<K, V> {
return dataMap.size();
}
private <INDEX> String getMethodToField(Func1<V, INDEX> 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;
}
}
@@ -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<Class<?>, 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<? extends Serializable> 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 <T> Lambda类型
* @param func 函数(无参方法)
* @return 函数名称
*/
public static <T> String getMethodName(Func1<T, ?> func) {
return CACHE.computeIfAbsent(func.getClass(), it -> extract(func).getImplMethodName());
}
/**
* 获取lambda表达式函数(字段)名称
*
* @param <T> Lambda类型
* @param func 函数
* @return 字段名称
*/
public static <T> String getFieldName(Func1<T, ?> func) {
return FieldUtils.methodToProperty(getMethodName(func));
}
}
@@ -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<StudentResource, Integer> 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);
//获取唯一索引的对象
@@ -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");
}
}
@@ -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<StudentResource, Object> nameFunc = StudentResource::getName;
System.out.println(LambdaUtils.getMethodName(nameFunc));
System.out.println(LambdaUtils.getMethodName(nameFunc));
}
}
@@ -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<StudentResource, String> func2 = StudentResource::name;
// Method method = func2.getClass().getDeclaredMethod("writeReplace");
// object = method.invoke(func2);
// System.out.println(object);
}
@Test
public void testFunctionCache() {
Func1<StudentResource, Object> nameFunc1 = StudentResource::getName;
Func1<StudentResource, Object> 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);