From 3529386d0777acc53794a171c83094f01cdc6e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=87=8C=E6=98=9F?= Date: Wed, 13 Sep 2023 19:59:15 +0800 Subject: [PATCH] =?UTF-8?q?perf[storage];=201.=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=A1=A8=E8=AE=BF=E9=97=AE=E6=8E=A5=E5=8F=A3;=202.?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=B4=A2=E5=BC=95=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=BA=E5=87=BD=E6=95=B0=E5=BC=8F=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E7=9A=84=E6=96=B9=E5=BC=8F;=203.=E6=94=AF=E6=8C=81rec?= =?UTF-8?q?ord=E7=B1=BB=E7=9A=84=E9=85=8D=E7=BD=AEBean;=204.=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E4=BD=BF=E7=94=A8=E4=B8=8D=E5=8F=AF=E5=8F=98=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=93=E6=9E=84=E9=98=B2=E6=AD=A2=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E4=B8=AD=E4=B8=8D=E5=B0=8F=E5=BF=83=E4=BF=AE=E6=94=B9=E5=B8=A6?= =?UTF-8?q?=E6=9D=A5=E7=9A=84=E9=97=AE=E9=A2=98.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/zfoo/protocol/util/ClassUtils.java | 68 +++++++++++++ .../zfoo/protocol/util/ReflectionUtils.java | 23 +++++ storage/pom.xml | 8 ++ .../java/com/zfoo/storage/StorageContext.java | 19 ++++ .../interpreter/ResourceInterpreter.java | 46 +++++++-- .../zfoo/storage/manager/IStorageManager.java | 2 +- .../com/zfoo/storage/manager/StorageInt.java | 3 +- .../com/zfoo/storage/manager/StorageLong.java | 3 +- .../zfoo/storage/manager/StorageManager.java | 20 +++- .../zfoo/storage/manager/StorageObject.java | 83 +++++++++++++--- .../java/com/zfoo/storage/model/IStorage.java | 12 +-- .../com/zfoo/storage/util/LambdaUtils.java | 55 +++++++++++ .../storage/util/SetAccessibleAction.java | 22 +++++ .../util/support/IdeaProxyLambdaMeta.java | 40 ++++++++ .../zfoo/storage/util/support/LambdaMeta.java | 24 +++++ .../util/support/ReflectLambdaMeta.java | 30 ++++++ .../util/support/SerializableFunction.java | 13 +++ .../util/support/SerializedLambda.java | 61 ++++++++++++ .../util/support/ShadowLambdaMeta.java | 30 ++++++ .../com/zfoo/storage/ApplicationTest.java | 21 ++++- .../com/zfoo/storage/StudentResource.java | 31 ++++++ .../storage/export/ExportBinaryTesting.java | 89 ++++++++++++++---- .../storage/resource/StudentResource.java | 2 +- storage/src/test/resources/application.xml | 2 +- .../test/resources/excel/StudentResource.xlsx | Bin 9304 -> 9228 bytes 25 files changed, 649 insertions(+), 58 deletions(-) create mode 100644 storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/SetAccessibleAction.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/support/IdeaProxyLambdaMeta.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/support/LambdaMeta.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/support/ReflectLambdaMeta.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/support/SerializableFunction.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/support/SerializedLambda.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/support/ShadowLambdaMeta.java create mode 100644 storage/src/test/java/com/zfoo/storage/StudentResource.java diff --git a/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java index 74487f59..175f5f8c 100644 --- a/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java +++ b/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java @@ -15,6 +15,7 @@ package com.zfoo.protocol.util; import com.zfoo.protocol.collection.ArrayUtils; import com.zfoo.protocol.exception.RunException; +import java.beans.Introspector; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -24,6 +25,7 @@ import java.net.*; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.regex.Pattern; /** * @author godotg @@ -40,6 +42,21 @@ public abstract class ClassUtils { public final static String JAR_PROTOCOL = "jar"; public final static String JAR_URL_SEPARATOR = "!/"; + private static final Pattern GET_PATTERN = Pattern.compile("^get[A-Z].*"); + private static final Pattern IS_PATTERN = Pattern.compile("^is[A-Z].*"); + + private static ClassLoader systemClassLoader; + + static { + try { + systemClassLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException ignored) { + // AccessControlException on Google App Engine + } + } + + private ClassUtils() { + } public static Class forName(String className) { @@ -488,4 +505,55 @@ public abstract class ClassUtils { || Boolean.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz); } + + /** + * @param name + * @param classLoader + * @return + * @since 3.4.3 + */ + public static Class toClassConfident(String name, ClassLoader classLoader) { + try { + return loadClass(name, getClassLoaders(classLoader)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static Class loadClass(String className, ClassLoader[] classLoaders) throws ClassNotFoundException { + for (ClassLoader classLoader : classLoaders) { + if (classLoader != null) { + try { + return Class.forName(className, true, classLoader); + } catch (ClassNotFoundException e) { + // ignore + } + } + } + throw new ClassNotFoundException("Cannot find class: " + className); + } + + private static ClassLoader[] getClassLoaders(ClassLoader classLoader) { + return new ClassLoader[]{ + classLoader, + Thread.currentThread().getContextClassLoader(), + ClassUtils.class.getClassLoader(), + systemClassLoader}; + } + + /** + * 方法名转换为字段名 + * + * @param methodName 方法名 + * @return + */ + public static String getFieldName(String methodName) { + // 对于非标准变量生成的Get方法这里可以直接抛出异常,或者打印异常日志 + if (GET_PATTERN.matcher(methodName).matches()) { + methodName = methodName.substring(3); + } else if (IS_PATTERN.matcher(methodName).matches()) { + methodName = methodName.substring(2); + } + return Introspector.decapitalize(methodName); + } } diff --git a/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java index b8393c5e..c0764cf0 100644 --- a/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java +++ b/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java @@ -374,5 +374,28 @@ public abstract class ReflectionUtils { } } + public static Constructor getConstructor(Class clazz) { + try { + return clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RunException("default constructor:[{}] not exists in class:[{}]", clazz.getCanonicalName()); + } + } + + public static Constructor getConstructor(Class clazz, Class[] params) { + try { + return clazz.getConstructor(params); + } catch (NoSuchMethodException e) { + throw new RunException("constructor:[{}] has no setMethod in class:[{}]", params.length, clazz.getCanonicalName()); + } + } + + public static T newInstance(Constructor constructor, Object[] params) { + try { + return constructor.newInstance(params); + } catch (Exception e) { + throw new RunException("[{}]无法被实例化", constructor); + } + } } diff --git a/storage/pom.xml b/storage/pom.xml index 9964bc07..aecdc99a 100644 --- a/storage/pom.xml +++ b/storage/pom.xml @@ -39,6 +39,8 @@ 5.2.3 1.10.0 + 32.1.2-jre + 17 UTF-8 @@ -118,6 +120,12 @@ ${junit.version} test + + + com.google.guava + guava + ${guava.version} + diff --git a/storage/src/main/java/com/zfoo/storage/StorageContext.java b/storage/src/main/java/com/zfoo/storage/StorageContext.java index 729def08..4295f841 100644 --- a/storage/src/main/java/com/zfoo/storage/StorageContext.java +++ b/storage/src/main/java/com/zfoo/storage/StorageContext.java @@ -15,6 +15,7 @@ package com.zfoo.storage; import com.zfoo.scheduler.util.StopWatch; import com.zfoo.storage.manager.IStorageManager; +import com.zfoo.storage.util.support.SerializableFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; @@ -24,6 +25,8 @@ import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.Ordered; +import java.util.List; + /** * @author godotg * @version 3.0 @@ -50,6 +53,22 @@ public class StorageContext implements ApplicationListener V get(Class clazz, K keyId) { + return instance.storageManager.getStorage(clazz).get(keyId); + } + + public static List getList(Class clazz) { + return instance.storageManager.getStorage(clazz).getList(); + } + + public static List getIndexes(Class clazz, SerializableFunction function, K indexId) { + return instance.storageManager.getStorage(clazz).getIndexes(function, indexId); + } + + public static V getUniqueIndex(Class clazz, SerializableFunction function, K indexId) { + return instance.storageManager.getStorage(clazz).getUniqueIndex(function, indexId); + } + @Override public void onApplicationEvent(ApplicationContextEvent event) { diff --git a/storage/src/main/java/com/zfoo/storage/interpreter/ResourceInterpreter.java b/storage/src/main/java/com/zfoo/storage/interpreter/ResourceInterpreter.java index d4ffbf78..0c979c53 100644 --- a/storage/src/main/java/com/zfoo/storage/interpreter/ResourceInterpreter.java +++ b/storage/src/main/java/com/zfoo/storage/interpreter/ResourceInterpreter.java @@ -25,6 +25,7 @@ import org.springframework.core.convert.TypeDescriptor; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; @@ -69,20 +70,45 @@ public class ResourceInterpreter { var cellFieldMap = getCellFieldMap(resource, clazz); var fieldInfos = getFieldInfos(cellFieldMap, clazz); - var iterator = resource.getRows().iterator(); - // 从ROW_SERVER这行开始读取数据 - while (iterator.hasNext()) { - var columns = iterator.next(); - var instance = ReflectionUtils.newInstance(clazz); + if (clazz.isRecord()) { + Class[] constructParams = fieldInfos.stream().map(p -> p.field.getType()).toList().toArray(new Class[]{}); + Constructor constructor = ReflectionUtils.getConstructor(clazz, constructParams); - for (var fieldInfo : fieldInfos) { - var content = columns.get(fieldInfo.index); - if (StringUtils.isNotEmpty(content) || fieldInfo.field.getType() == String.class) { - inject(instance, fieldInfo.field, content); + var iterator = resource.getRows().iterator(); + // 从ROW_SERVER这行开始读取数据 + while (iterator.hasNext()) { + int index = 0; + var columns = iterator.next(); + var params = new Object[fieldInfos.size()]; + + for (var fieldInfo : fieldInfos) { + var content = columns.get(fieldInfo.index); + if (StringUtils.isNotEmpty(content) || fieldInfo.field.getType() == String.class) { + var targetType = new TypeDescriptor(fieldInfo.field); + var value = conversionServiceFactoryBean.getObject().convert(content, TYPE_DESCRIPTOR, targetType); + params[index++] = value; + } } + var instance = ReflectionUtils.newInstance(constructor, params); + result.add(instance); + } + } else { + var iterator = resource.getRows().iterator(); + // 从ROW_SERVER这行开始读取数据 + while (iterator.hasNext()) { + var columns = iterator.next(); + var instance = ReflectionUtils.newInstance(clazz); + + for (var fieldInfo : fieldInfos) { + var content = columns.get(fieldInfo.index); + if (StringUtils.isNotEmpty(content) || fieldInfo.field.getType() == String.class) { + inject(instance, fieldInfo.field, content); + } + } + result.add(instance); } - result.add(instance); } + return result; } diff --git a/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java b/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java index d46fa4c5..3fb0539a 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java +++ b/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java @@ -39,7 +39,7 @@ public interface IStorageManager { */ void initAfter(); - IStorage getStorage(Class clazz); + > T getStorage(Class clazz); Map, IStorage> storageMap(); diff --git a/storage/src/main/java/com/zfoo/storage/manager/StorageInt.java b/storage/src/main/java/com/zfoo/storage/manager/StorageInt.java index 39a36c38..22d5a7fb 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/StorageInt.java +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageInt.java @@ -18,6 +18,7 @@ import io.netty.util.collection.IntObjectHashMap; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.stream.Collectors; /** * @author godotg @@ -86,7 +87,7 @@ public class StorageInt extends StorageObject { @Override public Collection getAll() { - return dataMap.values(); + return dataMap.values().stream().collect(Collectors.toUnmodifiableList()); } @Override diff --git a/storage/src/main/java/com/zfoo/storage/manager/StorageLong.java b/storage/src/main/java/com/zfoo/storage/manager/StorageLong.java index caf2d361..77627478 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/StorageLong.java +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageLong.java @@ -18,6 +18,7 @@ import io.netty.util.collection.LongObjectHashMap; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.stream.Collectors; /** * @author godotg @@ -86,7 +87,7 @@ public class StorageLong extends StorageObject { @Override public Collection getAll() { - return dataMap.values(); + return dataMap.values().stream().collect(Collectors.toUnmodifiableList()); } @Override diff --git a/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java b/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java index a83b1380..bdabcf81 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java @@ -26,6 +26,7 @@ import com.zfoo.storage.config.StorageConfig; import com.zfoo.storage.interpreter.data.StorageEnum; import com.zfoo.storage.model.IStorage; import com.zfoo.storage.model.StorageDefinition; +import com.zfoo.storage.util.support.SerializableFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ConfigurableApplicationContext; @@ -191,8 +192,23 @@ public class StorageManager implements IStorageManager { } } + public List getList(Class clazz) { + IStorage storage = getStorage(clazz); + return (List) storage.getAll(); + } + + public List getIndexes(Class clazz, SerializableFunction function, K indexId) { + var storage = getStorage(clazz); + return storage.getIndexes(function, indexId); + } + + public T get(Class clazz, UQ uniqueId) { + IStorage storage = getStorage(clazz); + return storage.get(uniqueId); + } + @Override - public IStorage getStorage(Class clazz) { + public > T getStorage(Class clazz) { var storage = storageMap.get(clazz); if (storage == null) { throw new RunException("There is no [{}] defined Storage and unable to get it", clazz.getCanonicalName()); @@ -201,7 +217,7 @@ public class StorageManager implements IStorageManager { // Storage没有使用,为了节省内存提前释放了它;只有使用ResInjection注解的Storage才能被动态获取或者关闭配置recycle属性 logger.warn("Storage [{}] is not used, it was freed to save memory; use @ResInjection or turn off recycle configuration", clazz.getCanonicalName()); } - return storage; + return (T) storage; } @Override 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 3bd0683c..f91db923 100644 --- a/storage/src/main/java/com/zfoo/storage/manager/StorageObject.java +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageObject.java @@ -12,8 +12,10 @@ package com.zfoo.storage.manager; +import com.google.common.collect.ImmutableMap; import com.zfoo.protocol.collection.CollectionUtils; import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.ClassUtils; import com.zfoo.protocol.util.IOUtils; import com.zfoo.protocol.util.ReflectionUtils; import com.zfoo.protocol.util.StringUtils; @@ -21,27 +23,34 @@ 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.support.SerializableFunction; import org.springframework.lang.Nullable; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * @author godotg * @version 3.0 */ public class StorageObject implements IStorage { - - private Map dataMap = new HashMap<>(); + private ImmutableMap dataMap; // 非唯一索引 - protected Map>> indexMap = new HashMap<>(); + protected ImmutableMap>> indexMap; // 唯一索引 - protected Map> uniqueIndexMap = new HashMap<>(); + protected ImmutableMap> uniqueIndexMap; protected Class clazz; protected IdDef idDef; - protected Map indexDefMap; - // 当前配置表是否在当前项目中使用,没有被使用的会清楚data数据,以达到节省内存的目的 + protected ImmutableMap indexDefMap; + // 当前配置表是否在当前项目中使用,没有被使用的会清除data数据,以达到节省内存的目的 protected boolean recycle = true; @@ -51,11 +60,9 @@ public class StorageObject implements IStorage { storage.clazz = resourceClazz; var idDef = IdDef.valueOf(resourceClazz); storage.idDef = idDef; - storage.indexDefMap = IndexDef.createResourceIndexes(resourceClazz); + storage.indexDefMap = ImmutableMap.copyOf(IndexDef.createResourceIndexes(resourceClazz)); var list = ResourceInterpreter.read(inputStream, resourceClazz, suffix); - for (var object : list) { - storage.put(object); - } + storage.append(list); var idType = idDef.getField().getType(); if (idType == int.class || idType == Integer.class) { return new StorageInt<>(storage); @@ -136,6 +143,12 @@ public class StorageObject implements IStorage { return dataMap.values(); } + @Override + public List getList() { + Collection all = getAll(); + return all.stream().collect(Collectors.toUnmodifiableList()); + } + @Override public Map getData() { return Collections.unmodifiableMap(dataMap); @@ -147,7 +160,8 @@ public class StorageObject implements IStorage { } @Override - public List getIndex(String indexName, Object key) { + public List getIndexes(SerializableFunction indexFunction, K key) { + String indexName = ClassUtils.getFieldName(LambdaUtils.extract(indexFunction).getImplMethodName()); 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(key); @@ -159,11 +173,12 @@ public class StorageObject implements IStorage { @Nullable @Override - public V getUniqueIndex(String uniqueIndexName, Object key) { + public V getUniqueIndex(SerializableFunction uniqueIndexFunction, K key) { + String uniqueIndexName = ClassUtils.getFieldName(LambdaUtils.extract(uniqueIndexFunction).getImplMethodName()); 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(key); - return value; + return (V) value; } @Override @@ -171,7 +186,45 @@ public class StorageObject implements IStorage { return dataMap.size(); } - public V put(Object value) { + private void append(List values) { + ImmutableMap.Builder dataMapBuilder = ImmutableMap.builder(); + Map> uniqueIndexMap = new HashMap<>(32); + Map>> indexMap = new HashMap<>(32); + for (var value : values) { + var key = (K) ReflectionUtils.getField(idDef.getField(), value); + + if (key == null) { + throw new RuntimeException("There is an item with an unconfigured id in the static resource"); + } + + // 添加资源 + var v = (V) value; + dataMapBuilder.put(key, v); + + // 添加索引 + for (var def : indexDefMap.values()) { + // 使用field的名称作为索引的名称 + var indexKey = def.getField().getName(); + var indexValue = ReflectionUtils.getField(def.getField(), v); + if (def.isUnique()) {// 唯一索引 + var index = uniqueIndexMap.computeIfAbsent(indexKey, k -> new HashMap<>(8)); + if (index.put(indexValue, v) != null) { + throw new RuntimeException(StringUtils.format("Duplicate unique index [index:{}][value:{}] of static resource [class:{}]", indexKey, indexValue, clazz.getName())); + } + } else {// 不是唯一索引 + var index = indexMap.computeIfAbsent(indexKey, k -> new HashMap<>(12)); + var list = index.computeIfAbsent(indexValue, k -> new ArrayList<>()); + list.add(v); + } + } + } + this.uniqueIndexMap = ImmutableMap.copyOf(uniqueIndexMap); + this.indexMap = ImmutableMap.copyOf(indexMap); + this.dataMap = dataMapBuilder.build(); + } + + @Deprecated + protected V put(Object value) { @SuppressWarnings("unchecked") var key = (K) ReflectionUtils.getField(idDef.getField(), value); diff --git a/storage/src/main/java/com/zfoo/storage/model/IStorage.java b/storage/src/main/java/com/zfoo/storage/model/IStorage.java index 8e79989b..c7a6c28e 100644 --- a/storage/src/main/java/com/zfoo/storage/model/IStorage.java +++ b/storage/src/main/java/com/zfoo/storage/model/IStorage.java @@ -12,6 +12,7 @@ package com.zfoo.storage.model; +import com.zfoo.storage.util.support.SerializableFunction; import org.springframework.lang.Nullable; import java.util.Collection; @@ -43,18 +44,17 @@ public interface IStorage { Collection getAll(); + List getList(); + Map getData(); IdDef getIdDef(); - List getIndex(String indexName, Object key); + @Nullable + List getIndexes(SerializableFunction function, K key); @Nullable - V getUniqueIndex(String uniqueIndexName, Object key); + V getUniqueIndex(SerializableFunction function, K key); int size(); - - V put(Object value); - - } diff --git a/storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java b/storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java new file mode 100644 index 00000000..8076da2b --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/LambdaUtils.java @@ -0,0 +1,55 @@ +package com.zfoo.storage.util; + +import com.zfoo.storage.util.support.IdeaProxyLambdaMeta; +import com.zfoo.storage.util.support.LambdaMeta; +import com.zfoo.storage.util.support.ReflectLambdaMeta; +import com.zfoo.storage.util.support.SerializableFunction; +import com.zfoo.storage.util.support.SerializedLambda; +import com.zfoo.storage.util.support.ShadowLambdaMeta; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.security.AccessController; + +/** + * @author veione + * @version 1.0 + * @date 2023/9/12 + */ +public final class LambdaUtils { + + + /** + * 该缓存可能会在任意不定的时间被清除 + * + * @param func 需要解析的 lambda 对象 + * @param 类型,被调用的 Function 对象的目标类型 + * @return 返回解析后的结果 + */ + public static LambdaMeta extract(SerializableFunction func) { + // 1. IDEA 调试模式下 lambda 表达式是一个代理 + if (func instanceof Proxy) { + return new IdeaProxyLambdaMeta((Proxy) func); + } + // 2. 反射读取 + try { + Method method = func.getClass().getDeclaredMethod("writeReplace"); + return new ReflectLambdaMeta((SerializedLambda) setAccessible(method).invoke(func), func.getClass().getClassLoader()); + } catch (Throwable e) { + // 3. 反射失败使用序列化的方式读取 + return new ShadowLambdaMeta(SerializedLambda.extract(func)); + } + } + + /** + * 设置可访问对象的可访问权限为 true + * + * @param object 可访问的对象 + * @param 类型 + * @return 返回设置后的对象 + */ + public static T setAccessible(T object) { + return AccessController.doPrivileged(new SetAccessibleAction<>(object)); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/util/SetAccessibleAction.java b/storage/src/main/java/com/zfoo/storage/util/SetAccessibleAction.java new file mode 100644 index 00000000..c90fcc68 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/SetAccessibleAction.java @@ -0,0 +1,22 @@ +package com.zfoo.storage.util; + +import java.lang.reflect.AccessibleObject; +import java.security.PrivilegedAction; + +/** + * Create by hcl at 2021/5/14 + */ +public class SetAccessibleAction implements PrivilegedAction { + private final T obj; + + public SetAccessibleAction(T obj) { + this.obj = obj; + } + + @Override + public T run() { + obj.setAccessible(true); + return obj; + } + +} diff --git a/storage/src/main/java/com/zfoo/storage/util/support/IdeaProxyLambdaMeta.java b/storage/src/main/java/com/zfoo/storage/util/support/IdeaProxyLambdaMeta.java new file mode 100644 index 00000000..34ab4de4 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/support/IdeaProxyLambdaMeta.java @@ -0,0 +1,40 @@ +package com.zfoo.storage.util.support; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandleProxies; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Executable; +import java.lang.reflect.Proxy; + +/** + * 在 IDEA 的 Evaluate 中执行的 Lambda 表达式元数据需要使用该类处理元数据 + *

+ * Create by hcl at 2021/5/17 + */ +public class IdeaProxyLambdaMeta implements LambdaMeta { + private final Class clazz; + private final String name; + + public IdeaProxyLambdaMeta(Proxy func) { + MethodHandle dmh = MethodHandleProxies.wrapperInstanceTarget(func); + Executable executable = MethodHandles.reflectAs(Executable.class, dmh); + clazz = executable.getDeclaringClass(); + name = executable.getName(); + } + + @Override + public String getImplMethodName() { + return name; + } + + @Override + public Class getInstantiatedClass() { + return clazz; + } + + @Override + public String toString() { + return clazz.getSimpleName() + "::" + name; + } + +} diff --git a/storage/src/main/java/com/zfoo/storage/util/support/LambdaMeta.java b/storage/src/main/java/com/zfoo/storage/util/support/LambdaMeta.java new file mode 100644 index 00000000..d7a8910f --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/support/LambdaMeta.java @@ -0,0 +1,24 @@ +package com.zfoo.storage.util.support; + +/** + * Lambda 信息 + *

+ * Created by hcl at 2021/5/14 + */ +public interface LambdaMeta { + + /** + * 获取 lambda 表达式实现方法的名称 + * + * @return lambda 表达式对应的实现方法名称 + */ + String getImplMethodName(); + + /** + * 实例化该方法的类 + * + * @return 返回对应的类名称 + */ + Class getInstantiatedClass(); + +} diff --git a/storage/src/main/java/com/zfoo/storage/util/support/ReflectLambdaMeta.java b/storage/src/main/java/com/zfoo/storage/util/support/ReflectLambdaMeta.java new file mode 100644 index 00000000..3ae8e60c --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/support/ReflectLambdaMeta.java @@ -0,0 +1,30 @@ +package com.zfoo.storage.util.support; + +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.StringUtils; + +/** + * Created by hcl at 2021/5/14 + */ +public class ReflectLambdaMeta implements LambdaMeta { + private final SerializedLambda lambda; + + private final ClassLoader classLoader; + + public ReflectLambdaMeta(SerializedLambda lambda, ClassLoader classLoader) { + this.lambda = lambda; + this.classLoader = classLoader; + } + + @Override + public String getImplMethodName() { + return lambda.getImplMethodName(); + } + + @Override + public Class getInstantiatedClass() { + String instantiatedMethodType = lambda.getInstantiatedMethodType(); + String instantiatedType = instantiatedMethodType.substring(2, instantiatedMethodType.indexOf(StringUtils.SEMICOLON)).replace(StringUtils.SLASH, StringUtils.PERIOD); + return ClassUtils.toClassConfident(instantiatedType, this.classLoader); + } +} \ No newline at end of file diff --git a/storage/src/main/java/com/zfoo/storage/util/support/SerializableFunction.java b/storage/src/main/java/com/zfoo/storage/util/support/SerializableFunction.java new file mode 100644 index 00000000..eac5f769 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/support/SerializableFunction.java @@ -0,0 +1,13 @@ +package com.zfoo.storage.util.support; + +import java.io.Serializable; +import java.util.function.Function; + +/** + * 支持序列化的 Function + * + * @author veione + */ +@FunctionalInterface +public interface SerializableFunction extends Function, Serializable { +} diff --git a/storage/src/main/java/com/zfoo/storage/util/support/SerializedLambda.java b/storage/src/main/java/com/zfoo/storage/util/support/SerializedLambda.java new file mode 100644 index 00000000..b64f150d --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/support/SerializedLambda.java @@ -0,0 +1,61 @@ +package com.zfoo.storage.util.support; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.Serializable; + +/** + * 当前类是 {@link java.lang.invoke.SerializedLambda } 的一个镜像 + *

+ * Create by hcl at 2020/7/17 + */ +public class SerializedLambda implements Serializable { + private static final long serialVersionUID = 8025925345765570181L; + + private Class capturingClass; + private String functionalInterfaceClass; + private String functionalInterfaceMethodName; + private String functionalInterfaceMethodSignature; + private String implClass; + private String implMethodName; + private String implMethodSignature; + private int implMethodKind; + private String instantiatedMethodType; + private Object[] capturedArgs; + + public static SerializedLambda extract(Serializable serializable) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(serializable); + oos.flush(); + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())) { + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + Class clazz = super.resolveClass(desc); + return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz; + } + + }) { + return (SerializedLambda) ois.readObject(); + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public String getInstantiatedMethodType() { + return instantiatedMethodType; + } + + public Class getCapturingClass() { + return capturingClass; + } + + public String getImplMethodName() { + return implMethodName; + } +} diff --git a/storage/src/main/java/com/zfoo/storage/util/support/ShadowLambdaMeta.java b/storage/src/main/java/com/zfoo/storage/util/support/ShadowLambdaMeta.java new file mode 100644 index 00000000..7538c000 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/support/ShadowLambdaMeta.java @@ -0,0 +1,30 @@ +package com.zfoo.storage.util.support; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.protocol.util.ClassUtils; + +/** + * 基于 {@link SerializedLambda} 创建的元信息 + *

+ * Create by hcl at 2021/7/7 + */ +public class ShadowLambdaMeta implements LambdaMeta { + private final SerializedLambda lambda; + + public ShadowLambdaMeta(SerializedLambda lambda) { + this.lambda = lambda; + } + + @Override + public String getImplMethodName() { + return lambda.getImplMethodName(); + } + + @Override + public Class getInstantiatedClass() { + String instantiatedMethodType = lambda.getInstantiatedMethodType(); + String instantiatedType = instantiatedMethodType.substring(2, instantiatedMethodType.indexOf(StringUtils.SEMICOLON)).replace(StringUtils.SLASH, StringUtils.PERIOD); + return ClassUtils.toClassConfident(instantiatedType, lambda.getCapturingClass().getClassLoader()); + } + +} diff --git a/storage/src/test/java/com/zfoo/storage/ApplicationTest.java b/storage/src/test/java/com/zfoo/storage/ApplicationTest.java index 1f76a630..a54badc8 100644 --- a/storage/src/test/java/com/zfoo/storage/ApplicationTest.java +++ b/storage/src/test/java/com/zfoo/storage/ApplicationTest.java @@ -16,12 +16,16 @@ package com.zfoo.storage; import com.zfoo.protocol.util.AssertionUtils; import com.zfoo.protocol.util.JsonUtils; import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.manager.StorageManager; +import com.zfoo.storage.model.IStorage; +import com.zfoo.storage.resource.StudentCsvResource; import com.zfoo.storage.resource.StudentResource; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; +import java.util.List; import java.util.Map; /** @@ -44,6 +48,19 @@ public class ApplicationTest { // Excel的映射内容需要在被Spring管理的bean的方法上加上@ResInjection注解,即可自动注入Excel对应的对象 // 参考StudentManager中的标准用法 + + //获取所有数据 + List list = StorageContext.getList(StudentResource.class); + //根据主键获取数据 + StudentResource studentResource = StorageContext.get(StudentResource.class, 1001); + //获取名字索引列表 + List nameIndexList = StorageContext.getIndexes(StudentResource.class, StudentResource::getName, "james1"); + //获取年龄索引列表 + List ageIndexList = StorageContext.getIndexes(StudentResource.class, StudentResource::getAge, 10); + + //获取Storage + IStorage storage = context.getBean(StorageManager.class).getStorage(StudentResource.class); + var studentManager = context.getBean(StudentManager.class); var studentResources = studentManager.studentResources; var studentCsvResources = studentManager.studentCsvResources; @@ -60,11 +77,11 @@ public class ApplicationTest { System.out.println(StringUtils.MULTIPLE_HYPHENS); // 通过索引找对应的行 - var valuesByIndex = studentResources.getIndex("name", "james0"); + var valuesByIndex = studentResources.getIndexes(StudentResource::getName, "james0"); logger.info(JsonUtils.object2String(valuesByIndex)); // 通过索引找对应的行 - var csvValuesByIndex = studentCsvResources.getIndex("name", "james0"); + var csvValuesByIndex = studentCsvResources.getIndexes(StudentCsvResource::getName, "james0"); logger.info(JsonUtils.object2String(csvValuesByIndex)); // Excel的映射内容需要在被Spring管理的bean的方法上加上@ResInjection注解,即可自动注入Excel对应的对象 diff --git a/storage/src/test/java/com/zfoo/storage/StudentResource.java b/storage/src/test/java/com/zfoo/storage/StudentResource.java new file mode 100644 index 00000000..ddde5f62 --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/StudentResource.java @@ -0,0 +1,31 @@ +package com.zfoo.storage; + +import com.zfoo.storage.anno.AliasFieldName; +import com.zfoo.storage.anno.Id; +import com.zfoo.storage.anno.Index; +import com.zfoo.storage.anno.Storage; +import com.zfoo.storage.resource.User; + +import java.util.List; + +/** + * @author veione + * @version 1.0.0 + */ +@Storage +public record StudentResource( + @Id + int id, + @Index(unique = true) + String idCard, + @Index + String name, + @AliasFieldName("年龄") + int age, + float score, + String[] courses, + User[] users, + List userList, + User user +) { +} 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 a4ed9757..4da1d203 100644 --- a/storage/src/test/java/com/zfoo/storage/export/ExportBinaryTesting.java +++ b/storage/src/test/java/com/zfoo/storage/export/ExportBinaryTesting.java @@ -12,19 +12,22 @@ package com.zfoo.storage.export; -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.ClassUtils; import com.zfoo.protocol.util.FileUtils; import com.zfoo.protocol.util.JsonUtils; import com.zfoo.storage.anno.AliasFieldName; import com.zfoo.storage.anno.Id; +import com.zfoo.storage.anno.Index; import com.zfoo.storage.anno.Storage; import com.zfoo.storage.config.StorageConfig; +import com.zfoo.storage.manager.StorageInt; import com.zfoo.storage.manager.StorageManager; -import com.zfoo.storage.manager.StorageObject; import com.zfoo.storage.util.ExportUtils; +import com.zfoo.storage.util.LambdaUtils; +import com.zfoo.storage.util.support.SerializableFunction; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledHeapByteBuf; import org.junit.Ignore; @@ -33,6 +36,7 @@ import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.File; import java.util.HashSet; +import java.util.List; import java.util.Map; /** @@ -54,18 +58,22 @@ public class ExportBinaryTesting { } @Storage - public static class StudentResource { + public record StudentResource( + @Id + int id, + @Index(unique = true) + String idCard, + @Index + String name, + @Index + @AliasFieldName("年龄") + int age, + float score, + String[] courses, + User[] users, + User user + ) { - @Id - public int id; - - public String name; - @AliasFieldName("年龄") - public int age; - public float score; - public String[] courses; - public User[] users; - public User user; } @@ -88,23 +96,68 @@ public class ExportBinaryTesting { storageManager.initBefore(); storageManager.initAfter(); - // 生成协议 + // 生成协议 TODO 协议 var protocols = new HashSet>(); protocols.add(ResourceData.class); protocols.addAll(storageManager.storageMap().keySet()); var operation = new GenerateOperation(); operation.setProtocolPath("D:\\github\\godot-bird\\test\\storage\\protocol"); operation.getGenerateLanguages().add(CodeLanguage.GdScript); - ProtocolManager.initProtocolAuto(protocols, operation); + //ProtocolManager.initProtocolAuto(protocols, operation); // 生成数据 var resourceData = ExportUtils.autoWrapData(ResourceData.class, storageManager.storageMap()); var buffer = new UnpooledHeapByteBuf(ByteBufAllocator.DEFAULT, 100, Integer.MAX_VALUE); - ProtocolManager.write(buffer, resourceData); + //ProtocolManager.write(buffer, resourceData); var bytes = ByteBufUtils.readAllBytes(buffer); FileUtils.writeInputStreamToFile(new File("D:/github/godot-bird/binary_data.cfg"), new ByteArrayInputStream(bytes)); - @SuppressWarnings("unchecked") - var storage = (StorageObject) storageManager.getStorage(StudentResource.class); + + String methodName = LambdaUtils.extract(StudentResource::age).getImplMethodName(); + String fieldName = ClassUtils.getFieldName(methodName); + System.out.println(methodName); + System.out.println(fieldName); + + //获取storage对象 + var storage = storageManager.getStorage(StudentResource.class); + //获取唯一索引的对象 + StudentResource uniqueIndex = storage.getUniqueIndex(StudentResource::idCard, "110101200007281903"); + System.out.println(JsonUtils.object2String(uniqueIndex)); + //获取年龄索引对象列表 + List ageIndexList = storage.getIndexes(StudentResource::age, 10); + for (StudentResource resource : ageIndexList) { + System.out.println(JsonUtils.object2String(resource)); + } + //获取名称索引对象列表 + List nameIndexList = storage.getIndexes(StudentResource::name, "james1"); + for (StudentResource resource : nameIndexList) { + System.out.println(JsonUtils.object2String(resource)); + } + //获取所有列表对象 + List list = storage.getList(); + for (StudentResource resource : list) { + System.out.println(JsonUtils.object2String(resource)); + } + //获取泛型的storage对象 + StorageInt genericStorage = storageManager.getStorage(StudentResource.class); + //获取配置表的所有数据 + List all = storageManager.getList(StudentResource.class); + for (StudentResource resource : all) { + System.out.println(JsonUtils.object2String(resource)); + } + //获取配置表索引的数据 + List ageIndexes = storageManager.getIndexes(StudentResource.class, StudentResource::age, 10); + for (StudentResource resource : ageIndexes) { + System.out.println(JsonUtils.object2String(resource)); + } + //获取配置表索引的数据 + List nameIndexes = storageManager.getIndexes(StudentResource.class, StudentResource::name, "james1"); + for (StudentResource resource : nameIndexes) { + System.out.println(JsonUtils.object2String(resource)); + } + //获取主键ID的唯一数据 + StudentResource idResource = storageManager.get(StudentResource.class, 1001); + System.out.println(JsonUtils.object2String(idResource)); + for (StudentResource resource : storage.getAll()) { System.out.println(JsonUtils.object2String(resource)); } diff --git a/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java b/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java index 73e8856d..2d209278 100644 --- a/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java +++ b/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java @@ -35,7 +35,7 @@ public class StudentResource { */ @Index private String name; - + @Index @AliasFieldName("年龄") private int age; private float score; diff --git a/storage/src/test/resources/application.xml b/storage/src/test/resources/application.xml index 0353107a..8d411298 100644 --- a/storage/src/test/resources/application.xml +++ b/storage/src/test/resources/application.xml @@ -48,7 +48,7 @@ - + diff --git a/storage/src/test/resources/excel/StudentResource.xlsx b/storage/src/test/resources/excel/StudentResource.xlsx index eed0851e27aa35da10c15287d7f423e9df60388c..8ac64b66abec6b89d56df7e2e3d37eaec5f85ab6 100644 GIT binary patch delta 5249 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 aA#R3{38Je(MEqAzd%a1nv)N_)d;1?c*0j0+ delta 5297 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