diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassDescUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassDescUtil.java new file mode 100644 index 000000000..9d378ac08 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassDescUtil.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.classloader; + +import org.dromara.hutool.core.exceptions.UtilException; +import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.map.BiMap; +import org.dromara.hutool.core.reflect.ClassUtil; +import org.dromara.hutool.core.text.StrTrimer; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.CharUtil; + +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.HashMap; + +/** + * 类描述工具类
+ * 来自:org.apache.dubbo.common.utils.ReflectUtils + * + * @author Dubbo + * @since 6.0.0 + */ +public class ClassDescUtil { + /** + * void(V). + */ + public static final char JVM_VOID = 'V'; + + /** + * boolean(Z). + */ + public static final char JVM_BOOLEAN = 'Z'; + + /** + * byte(B). + */ + public static final char JVM_BYTE = 'B'; + + /** + * char(C). + */ + public static final char JVM_CHAR = 'C'; + + /** + * double(D). + */ + public static final char JVM_DOUBLE = 'D'; + + /** + * float(F). + */ + public static final char JVM_FLOAT = 'F'; + + /** + * int(I). + */ + public static final char JVM_INT = 'I'; + + /** + * long(J). + */ + public static final char JVM_LONG = 'J'; + + /** + * short(S). + */ + public static final char JVM_SHORT = 'S'; + + /** + * 原始类型名和其class对应表,例如:int.class 《=》 int + */ + private static final BiMap, Character> PRIMITIVE_CLASS_DESC_MAP = new BiMap<>(new HashMap<>(9, 1)); + private static final BiMap, String> PRIMITIVE_CLASS_NAME_MAP = new BiMap<>(new HashMap<>(9, 1)); + + static { + PRIMITIVE_CLASS_DESC_MAP.put(void.class, JVM_VOID); + PRIMITIVE_CLASS_NAME_MAP.put(void.class, "void"); + PRIMITIVE_CLASS_DESC_MAP.put(boolean.class, JVM_BOOLEAN); + PRIMITIVE_CLASS_NAME_MAP.put(boolean.class, "boolean"); + PRIMITIVE_CLASS_DESC_MAP.put(byte.class, JVM_BYTE); + PRIMITIVE_CLASS_NAME_MAP.put(byte.class, "byte"); + PRIMITIVE_CLASS_DESC_MAP.put(char.class, JVM_CHAR); + PRIMITIVE_CLASS_NAME_MAP.put(char.class, "char"); + PRIMITIVE_CLASS_DESC_MAP.put(double.class, JVM_DOUBLE); + PRIMITIVE_CLASS_NAME_MAP.put(double.class, "double"); + PRIMITIVE_CLASS_DESC_MAP.put(float.class, JVM_FLOAT); + PRIMITIVE_CLASS_NAME_MAP.put(float.class, "float"); + PRIMITIVE_CLASS_DESC_MAP.put(int.class, JVM_INT); + PRIMITIVE_CLASS_NAME_MAP.put(int.class, "int"); + PRIMITIVE_CLASS_DESC_MAP.put(long.class, JVM_LONG); + PRIMITIVE_CLASS_NAME_MAP.put(long.class, "long"); + PRIMITIVE_CLASS_DESC_MAP.put(short.class, JVM_SHORT); + PRIMITIVE_CLASS_NAME_MAP.put(short.class, "short"); + } + + /** + * Class描述转Class + *
{@code
+	 * "[Z" => boolean[].class
+	 * "[[Ljava/util/Map;" => java.util.Map[][].class
+	 * }
+ * + * @param desc 类描述 + * @return Class + * @throws UtilException 类没有找到 + */ + public static Class descToClass(final String desc) throws UtilException { + return descToClass(desc, true, null); + } + + /** + * Class描述转Class + *
{@code
+	 * "[Z" => boolean[].class
+	 * "[[Ljava/util/Map;" => java.util.Map[][].class
+	 * }
+ * + * @param desc 类描述 + * @param isInitialized 是否初始化类 + * @param cl {@link ClassLoader} + * @return Class + * @throws UtilException 类没有找到 + */ + public static Class descToClass(String desc, final boolean isInitialized, final ClassLoader cl) throws UtilException { + final char firstChar = desc.charAt(0); + final Class clazz = PRIMITIVE_CLASS_DESC_MAP.getKey(firstChar); + if (null != clazz) { + return clazz; + } + switch (firstChar) { + case 'L': + // "Ljava/lang/Object;" ==> "java.lang.Object" + desc = desc.substring(1, desc.length() - 1).replace('/', '.'); + break; + case '[': + // "[[Ljava/lang/Object;" ==> "[[Ljava.lang.Object;" + desc = desc.replace(CharUtil.SLASH, CharUtil.DOT); + break; + default: + throw new UtilException("Class not found for : " + desc); + } + + return ClassUtil.forName(desc, isInitialized, cl); + } + + /** + * get class desc. + * boolean[].class => "[Z" + * Object.class => "Ljava/lang/Object;" + * + * @param c class. + * @return desc. + */ + public static String getDesc(Class c) { + final StringBuilder ret = new StringBuilder(); + + while (c.isArray()) { + ret.append('['); + c = c.getComponentType(); + } + + if (c.isPrimitive()) { + final Character desc = PRIMITIVE_CLASS_DESC_MAP.get(c); + if (null != desc) { + ret.append(desc.charValue()); + } + } else { + ret.append('L'); + ret.append(c.getName().replace('.', '/')); + ret.append(';'); + } + return ret.toString(); + } + + /** + * 获取方法或构造描述
+ * 方法: + *
{@code
+	 * int do(int arg1) => "do(I)I"
+	 * void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V"
+	 * }
+ * 构造: + *
+	 * "()V", "(Ljava/lang/String;I)V"
+	 * 
+ * + * @param methodOrConstructor 方法或构造 + * @return 描述 + */ + public static String getDesc(final Executable methodOrConstructor) { + final StringBuilder ret = new StringBuilder(); + if (methodOrConstructor instanceof Method) { + ret.append(methodOrConstructor.getName()); + } + ret.append('('); + + // 参数 + final Class[] parameterTypes = methodOrConstructor.getParameterTypes(); + for (final Class parameterType : parameterTypes) { + ret.append(getDesc(parameterType)); + } + + // 返回类型或构造标记 + ret.append(')'); + if (methodOrConstructor instanceof Method) { + ret.append(getDesc(((Method) methodOrConstructor).getReturnType())); + } else { + ret.append('V'); + } + + return ret.toString(); + } + + /** + * 获取code base + * + * @param clazz 类 + * @return code base + */ + public static String getCodeBase(final Class clazz) { + if (clazz == null) { + return null; + } + final ProtectionDomain domain = clazz.getProtectionDomain(); + if (domain == null) { + return null; + } + final CodeSource source = domain.getCodeSource(); + if (source == null) { + return null; + } + final URL location = source.getLocation(); + if (location == null) { + return null; + } + return location.getFile(); + } + + /** + * name to class. + * "boolean" => boolean.class + * "java.util.Map[][]" => java.util.Map[][].class + * + * @param name name. + * @param isInitialized 是否初始化类 + * @param cl ClassLoader instance. + * @return Class instance. + */ + public static Class nameToClass(String name, final boolean isInitialized, final ClassLoader cl) { + Assert.notNull(name, "Name must not be null"); + // 去除尾部多余的"."和"/" + name = StrUtil.trim(name, StrTrimer.TrimMode.SUFFIX, (c) -> + CharUtil.SLASH == c || CharUtil.DOT == c); + + int c = 0; + final int index = name.indexOf('['); + if (index > 0) { + c = (name.length() - index) / 2; + name = name.substring(0, index); + } + + if (c > 0) { + final StringBuilder sb = new StringBuilder(); + while (c-- > 0) { + sb.append('['); + } + + final Class clazz = PRIMITIVE_CLASS_NAME_MAP.getKey(name); + if (null != clazz) { + // 原始类型数组,根据name获取其描述 + sb.append(PRIMITIVE_CLASS_DESC_MAP.get(clazz).charValue()); + } else { + // 对象数组 + // "java.lang.Object" ==> "Ljava.lang.Object;" + sb.append('L').append(name).append(';'); + } + name = sb.toString(); + } else { + final Class clazz = PRIMITIVE_CLASS_NAME_MAP.getKey(name); + if (null != clazz) { + return clazz; + } + } + + return ClassUtil.forName(name.replace(CharUtil.SLASH, CharUtil.DOT), isInitialized, cl); + } + + /** + * 类名称转描述 + * + *
{@code
+	 * java.util.Map[][] => "[[Ljava/util/Map;"
+	 * }
+ * + * @param name 名称 + * @return 描述 + */ + public static String nameToDesc(String name) { + final StringBuilder sb = new StringBuilder(); + int c = 0; + final int index = name.indexOf('['); + if (index > 0) { + c = (name.length() - index) / 2; + name = name.substring(0, index); + } + while (c-- > 0) { + sb.append('['); + } + + final Class clazz = PRIMITIVE_CLASS_NAME_MAP.getKey(name); + if (null != clazz) { + // 原始类型数组,根据name获取其描述 + sb.append(PRIMITIVE_CLASS_DESC_MAP.get(clazz).charValue()); + } else { + // 对象数组 + // "java.lang.Object" ==> "Ljava.lang.Object;" + sb.append('L').append(name.replace(CharUtil.DOT, CharUtil.SLASH)).append(';'); + } + + return sb.toString(); + } + + /** + * 类描述转名称 + * + *
{@code
+	 * "[[I" => "int[][]"
+	 * }
+ * + * @param desc 描述 + * @return 名称 + */ + public static String descToName(final String desc) { + final StringBuilder sb = new StringBuilder(); + int c = desc.lastIndexOf('[') + 1; + if (desc.length() == c + 1) { + final char descChar = desc.charAt(c); + final Class clazz = PRIMITIVE_CLASS_DESC_MAP.getKey(descChar); + if (null != clazz) { + sb.append(PRIMITIVE_CLASS_NAME_MAP.get(clazz)); + } else { + throw new UtilException("Unsupported primitive desc: {}", desc); + } + } else { + sb.append(desc.substring(c + 1, desc.length() - 1).replace(CharUtil.SLASH, CharUtil.DOT)); + } + while (c-- > 0) { + sb.append("[]"); + } + return sb.toString(); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassLoaderUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassLoaderUtil.java index 228a4d8b1..302598b3e 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassLoaderUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/classloader/ClassLoaderUtil.java @@ -12,22 +12,11 @@ package org.dromara.hutool.core.classloader; -import org.dromara.hutool.core.convert.BasicType; import org.dromara.hutool.core.exceptions.UtilException; -import org.dromara.hutool.core.lang.Assert; -import org.dromara.hutool.core.map.SafeConcurrentHashMap; -import org.dromara.hutool.core.text.CharPool; -import org.dromara.hutool.core.text.StrTrimer; -import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.CharUtil; import java.io.File; -import java.lang.reflect.Array; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; /** * {@link ClassLoader}工具类
@@ -39,48 +28,26 @@ import java.util.Map; public class ClassLoaderUtil { /** - * 数组类的结尾符: "[]" + * 获取{@link ClassLoader}
+ * 获取顺序如下:
+ * + *
+	 * 1、获取当前线程的ContextClassLoader
+	 * 2、获取当前类对应的ClassLoader
+	 * 3、获取系统ClassLoader({@link ClassLoader#getSystemClassLoader()})
+	 * 
+ * + * @return 类加载器 */ - private static final String ARRAY_SUFFIX = "[]"; - /** - * 内部数组类名前缀: "[" - */ - private static final String INTERNAL_ARRAY_PREFIX = "["; - /** - * 内部非原始类型类名前缀: "[L" - */ - private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L"; - /** - * 包名分界符: '.' - */ - private static final char PACKAGE_SEPARATOR = CharUtil.DOT; - /** - * 内部类分界符: '$' - */ - private static final char INNER_CLASS_SEPARATOR = '$'; - - /** - * 原始类型名和其class对应表,例如:int =》 int.class - */ - private static final Map> PRIMITIVE_TYPE_NAME_MAP = new SafeConcurrentHashMap<>(32); - - static { - final List> primitiveTypes = new ArrayList<>(32); - // 加入原始类型 - primitiveTypes.addAll(BasicType.getPrimitiveSet()); - // 加入原始类型数组类型 - primitiveTypes.add(boolean[].class); - primitiveTypes.add(byte[].class); - primitiveTypes.add(char[].class); - primitiveTypes.add(double[].class); - primitiveTypes.add(float[].class); - primitiveTypes.add(int[].class); - primitiveTypes.add(long[].class); - primitiveTypes.add(short[].class); - primitiveTypes.add(void.class); - for (final Class primitiveType : primitiveTypes) { - PRIMITIVE_TYPE_NAME_MAP.put(primitiveType.getName(), primitiveType); + public static ClassLoader getClassLoader() { + ClassLoader classLoader = getContextClassLoader(); + if (classLoader == null) { + classLoader = ClassLoaderUtil.class.getClassLoader(); + if (null == classLoader) { + classLoader = getSystemClassLoader(); + } } + return classLoader; } /** @@ -116,32 +83,17 @@ public class ClassLoaderUtil { } } - /** - * 获取{@link ClassLoader}
- * 获取顺序如下:
+ * 创建新的{@link JarClassLoader},并使用此Classloader加载目录下的class文件和jar文件 * - *
-	 * 1、获取当前线程的ContextClassLoader
-	 * 2、获取当前类对应的ClassLoader
-	 * 3、获取系统ClassLoader({@link ClassLoader#getSystemClassLoader()})
-	 * 
- * - * @return 类加载器 + * @param jarOrDir jar文件或者包含jar和class文件的目录 + * @return {@link JarClassLoader} + * @since 4.4.2 */ - public static ClassLoader getClassLoader() { - ClassLoader classLoader = getContextClassLoader(); - if (classLoader == null) { - classLoader = ClassLoaderUtil.class.getClassLoader(); - if (null == classLoader) { - classLoader = getSystemClassLoader(); - } - } - return classLoader; + public static JarClassLoader getJarClassLoader(final File jarOrDir) { + return JarClassLoader.load(jarOrDir); } - // ----------------------------------------------------------------------------------- loadClass - /** * 加载类,通过传入类的字符串,返回其对应的类名,使用默认ClassLoader并初始化类(调用static模块内容和初始化static属性)
* 扩展{@link Class#forName(String, boolean, ClassLoader)}方法,支持以下几类类名的加载: @@ -201,48 +153,8 @@ public class ClassLoaderUtil { * @throws UtilException 包装{@link ClassNotFoundException},没有类名对应的类时抛出此异常 */ @SuppressWarnings("unchecked") - public static Class loadClass(String name, final boolean isInitialized, ClassLoader classLoader) throws UtilException { - Assert.notNull(name, "Name must not be null"); - - // 自动将包名中的"/"替换为"." - name = name.replace(CharPool.SLASH, CharPool.DOT); - if (null == classLoader) { - classLoader = getClassLoader(); - } - - Class clazz = loadPrimitiveClass(name); - if (clazz == null) { - clazz = doLoadClass(name, isInitialized, classLoader); - } - return (Class) clazz; - } - - /** - * 加载原始类型的类。包括原始类型、原始类型数组和void - * - * @param name 原始类型名,比如 int - * @return 原始类型类 - */ - public static Class loadPrimitiveClass(String name) { - Class result = null; - if (StrUtil.isNotBlank(name)) { - name = name.trim(); - if (name.length() <= 8) { - result = PRIMITIVE_TYPE_NAME_MAP.get(name); - } - } - return result; - } - - /** - * 创建新的{@link JarClassLoader},并使用此Classloader加载目录下的class文件和jar文件 - * - * @param jarOrDir jar文件或者包含jar和class文件的目录 - * @return {@link JarClassLoader} - * @since 4.4.2 - */ - public static JarClassLoader getJarClassLoader(final File jarOrDir) { - return JarClassLoader.load(jarOrDir); + public static Class loadClass(final String name, final boolean isInitialized, final ClassLoader classLoader) throws UtilException { + return (Class) ClassDescUtil.nameToClass(name, isInitialized, classLoader); } /** @@ -292,95 +204,4 @@ public class ClassLoaderUtil { return false; } } - - // ----------------------------------------------------------------------------------- Private method start - - /** - * 加载非原始类类,无缓存 - * - * @param name 类名 - * @param isInitialized 是否初始化 - * @param classLoader {@link ClassLoader},必须非空 - * @return 类 - */ - private static Class doLoadClass(String name, final boolean isInitialized, final ClassLoader classLoader) { - // 去除尾部多余的"." - name = StrUtil.trim(name, StrTrimer.TrimMode.SUFFIX, (c) -> CharUtil.DOT == c); - Class clazz; - if (name.endsWith(ARRAY_SUFFIX)) { - // 对象数组"java.lang.String[]"风格 - final String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length()); - final Class elementClass = loadClass(elementClassName, isInitialized, classLoader); - clazz = Array.newInstance(elementClass, 0).getClass(); - } else if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) { - // "[Ljava.lang.String;" 风格 - final String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1); - final Class elementClass = loadClass(elementName, isInitialized, classLoader); - clazz = Array.newInstance(elementClass, 0).getClass(); - } else if (name.startsWith(INTERNAL_ARRAY_PREFIX)) { - // "[[I" 或 "[[Ljava.lang.String;" 风格 - final String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length()); - final Class elementClass = loadClass(elementName, isInitialized, classLoader); - clazz = Array.newInstance(elementClass, 0).getClass(); - } else { - // 加载普通类 - try { - clazz = Class.forName(name, isInitialized, classLoader); - } catch (final ClassNotFoundException ex) { - // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State - clazz = tryLoadInnerClass(name, classLoader, isInitialized); - if (null == clazz) { - throw new UtilException(ex); - } - } - } - return clazz; - } - - /** - * 尝试转换并加载内部类,例如java.lang.Thread.State =》java.lang.Thread$State - * - * @param name 类名 - * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader - * @param isInitialized 是否初始化类(调用static模块内容和初始化static属性) - * @return 类名对应的类 - * @since 4.1.20 - */ - private static Class tryLoadInnerClass(String name, final ClassLoader classLoader, final boolean isInitialized) { - // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State - int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR); - Class clazz = null; - while (lastDotIndex > 0) {// 类与内部类的分隔符不能在第一位,因此>0 - if (!Character.isUpperCase(name.charAt(lastDotIndex + 1))) { - // 类名必须大写,非大写的类名跳过 - break; - } - name = name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1); - clazz = forName(name, isInitialized, classLoader); - if (null != clazz) { - break; - } - - lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR); - } - return clazz; - } - - /** - * 加载指定名称的类 - * - * @param name 类名 - * @param initialize 是否初始化 - * @param loader {@link ClassLoader} - * @return 指定名称对应的类,如果不存在类,返回{@code null} - */ - private static Class forName(final String name, final boolean initialize, final ClassLoader loader) { - try { - return Class.forName(name, initialize, loader); - } catch (final ClassNotFoundException ex2) { - // 尝试获取内部类失败时,忽略之。 - return null; - } - } - // ----------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/resource/ResourceUtil.java index e157a5a32..e1d217adc 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/resource/ResourceUtil.java @@ -16,6 +16,7 @@ import org.dromara.hutool.core.collection.iter.EnumerationIter; import org.dromara.hutool.core.collection.iter.IterUtil; import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.classloader.ClassLoaderUtil; import org.dromara.hutool.core.text.StrUtil; @@ -30,6 +31,7 @@ import java.net.URL; import java.nio.charset.Charset; import java.util.Enumeration; import java.util.List; +import java.util.Properties; import java.util.function.Predicate; /** @@ -290,4 +292,49 @@ public class ResourceUtil { } return resources; } + + /** + * 加载配置文件内容到{@link Properties}中
+ * 需要注意的是,如果资源文件的扩展名是.xml,会调用{@link Properties#loadFromXML(InputStream)} 读取。 + * + * @param properties {@link Properties}文件 + * @param resource 资源 + * @param charset 编码,对XML无效 + */ + public static void loadTo(final Properties properties, final Resource resource, final Charset charset) { + Assert.notNull(properties); + Assert.notNull(resource); + final String filename = resource.getName(); + if (filename != null && StrUtil.endWithIgnoreCase(filename, ".xml")) { + // XML + try (final InputStream in = resource.getStream()) { + properties.loadFromXML(in); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } else { + // .properties + try (final BufferedReader reader = resource.getReader( + ObjUtil.defaultIfNull(charset, CharsetUtil.UTF_8))) { + properties.load(reader); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + } + + /** + * 加载指定名称的所有配置文件内容到{@link Properties}中 + * + * @param properties {@link Properties}文件 + * @param resourceName 资源名,可以是相对classpath的路径,也可以是绝对路径 + * @param classLoader {@link ClassLoader},{@code null}表示使用默认的当前上下文ClassLoader + * @param charset 编码,对XML无效 + */ + public static void loadAllTo(final Properties properties, final String resourceName, + final ClassLoader classLoader, final Charset charset) { + for (final Resource resource : getResources(resourceName, classLoader)) { + loadTo(properties, resource, charset); + } + } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java index 0151d3f7f..2be0a8d6a 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java @@ -49,6 +49,15 @@ import java.util.function.Predicate; */ public class ClassUtil { + /** + * 包名分界符: '.' + */ + private static final char PACKAGE_SEPARATOR = CharUtil.DOT; + /** + * 内部类分界符: '$' + */ + private static final char INNER_CLASS_SEPARATOR = '$'; + /** * {@code null}安全的获取对象类型 * @@ -726,6 +735,64 @@ public class ClassUtil { return new ArrayList<>(interfacesFound); } + /** + * 加载指定名称的类 + * + * @param name 类名 + * @param isInitialized 是否初始化 + * @param loader {@link ClassLoader},{@code null}表示默认 + * @return 指定名称对应的类,如果不存在类,返回{@code null + * @link Class#forName(String, boolean, ClassLoader)} + */ + public static Class forName(final String name, final boolean isInitialized, ClassLoader loader) { + if(null == loader){ + loader = ClassLoaderUtil.getClassLoader(); + } + + // 加载普通类 + try { + return Class.forName(name, isInitialized, loader); + } catch (final ClassNotFoundException ex) { + // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State + final Class clazz = forNameInnerClass(name, isInitialized, loader); + if (null == clazz) { + throw new UtilException(ex); + } + return clazz; + } + } + + /** + * 尝试转换并加载内部类,例如java.lang.Thread.State =》java.lang.Thread$State + * + * @param name 类名 + * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader + * @param isInitialized 是否初始化类(调用static模块内容和初始化static属性) + * @return 类名对应的类,未找到返回{@code null} + */ + private static Class forNameInnerClass(String name, final boolean isInitialized, final ClassLoader classLoader) { + // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State + int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR); + Class clazz = null; + while (lastDotIndex > 0) {// 类与内部类的分隔符不能在第一位,因此>0 + if (!Character.isUpperCase(name.charAt(lastDotIndex + 1))) { + // 类名必须大写,非大写的类名跳过 + break; + } + name = name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1); + try { + clazz = Class.forName(name, isInitialized, classLoader); + break; + } catch (final ClassNotFoundException ignore) { + //ignore + } + + // 继续向前替换.为$ + lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR); + } + return clazz; + } + /** * 获取指定类的的接口列表 * diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/spi/KVServiceLoader.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/KVServiceLoader.java new file mode 100644 index 000000000..3beddce15 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/KVServiceLoader.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.spi; + +import org.dromara.hutool.core.classloader.ClassLoaderUtil; +import org.dromara.hutool.core.io.resource.ResourceUtil; +import org.dromara.hutool.core.text.StrUtil; + +import java.nio.charset.Charset; +import java.util.Properties; + +public class KVServiceLoader implements ServiceLoader{ + + private final String pathPrefix; + private final Class serviceClass; + private final ClassLoader classLoader; + private final Charset charset; + + private Properties serviceProperties; + + public KVServiceLoader(final String pathPrefix, final Class serviceClass, + final ClassLoader classLoader, final Charset charset) { + this.pathPrefix = pathPrefix; + this.serviceClass = serviceClass; + this.classLoader = classLoader; + this.charset = charset; + + load(); + } + + /** + * 加载或重新加载全部服务 + * @return this + */ + public KVServiceLoader load(){ + final Properties properties = new Properties(); + ResourceUtil.loadAllTo( + properties, + pathPrefix + serviceClass.getName(), + classLoader, + charset); + this.serviceProperties = properties; + return this; + } + + public Class getServiceClass(final String serviceName){ + final String serviceClassName = this.serviceProperties.getProperty(serviceName); + if(StrUtil.isNotBlank(serviceClassName)){ + return ClassLoaderUtil.loadClass(serviceClassName); + } + + return null; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/spi/ServiceLoader.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/ServiceLoader.java new file mode 100644 index 000000000..f6b5994e2 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/ServiceLoader.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.spi; + +/** + * SPI服务加载接口
+ * 用户实现此接口用于制定不同的服务加载方式 + * + * @param 服务对象类型 + * @author looly + */ +public interface ServiceLoader { +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/ServiceLoaderUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/ServiceLoaderUtil.java similarity index 97% rename from hutool-core/src/main/java/org/dromara/hutool/core/util/ServiceLoaderUtil.java rename to hutool-core/src/main/java/org/dromara/hutool/core/spi/ServiceLoaderUtil.java index bdc44c20e..7d349c573 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/util/ServiceLoaderUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/ServiceLoaderUtil.java @@ -10,10 +10,11 @@ * See the Mulan PSL v2 for more details. */ -package org.dromara.hutool.core.util; +package org.dromara.hutool.core.spi; import org.dromara.hutool.core.classloader.ClassLoaderUtil; import org.dromara.hutool.core.collection.ListUtil; +import org.dromara.hutool.core.util.ObjUtil; import java.util.Iterator; import java.util.List; diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/spi/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/package-info.java new file mode 100644 index 000000000..bc41e33de --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +/** + * 服务提供接口SPI(Service Provider interface)机制相关封装 + */ +package org.dromara.hutool.core.spi; diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java index cfb7bb121..f6f29faec 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java @@ -608,7 +608,8 @@ public class BeanUtilTest { o.setAge(123); o.setOpenid("asd"); - @SuppressWarnings("unchecked") final CopyOptions copyOptions = CopyOptions.of().setIgnoreProperties(Person::getAge,Person::getOpenid); + @SuppressWarnings("unchecked") + final CopyOptions copyOptions = CopyOptions.of().setIgnoreProperties(Person::getAge,Person::getOpenid); final Person n = new Person(); BeanUtil.copyProperties(o, n, copyOptions); diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/classloader/ClassDescTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/classloader/ClassDescTest.java new file mode 100644 index 000000000..ac5d37927 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/classloader/ClassDescTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.classloader; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * 来自:org.apache.dubbo.common.utils.ReflectUtilsTest + */ +public class ClassDescTest { + + @Test + void testDesc2Class() { + assertEquals(void.class, ClassDescUtil.descToClass("V")); + assertEquals(boolean.class, ClassDescUtil.descToClass("Z")); + assertEquals(boolean[].class, ClassDescUtil.descToClass("[Z")); + assertEquals(byte.class, ClassDescUtil.descToClass("B")); + assertEquals(char.class, ClassDescUtil.descToClass("C")); + assertEquals(double.class, ClassDescUtil.descToClass("D")); + assertEquals(float.class, ClassDescUtil.descToClass("F")); + assertEquals(int.class, ClassDescUtil.descToClass("I")); + assertEquals(long.class, ClassDescUtil.descToClass("J")); + assertEquals(short.class, ClassDescUtil.descToClass("S")); + assertEquals(String.class, ClassDescUtil.descToClass("Ljava.lang.String;")); + assertEquals(int[][].class, ClassDescUtil.descToClass(ClassDescUtil.getDesc(int[][].class))); + assertEquals(ClassDescTest[].class, ClassDescUtil.descToClass(ClassDescUtil.getDesc(ClassDescTest[].class))); + } + + @Test + void getDescTest() { + // getDesc + assertEquals("Z", ClassDescUtil.getDesc(boolean.class)); + assertEquals("[[[I", ClassDescUtil.getDesc(int[][][].class)); + assertEquals("[[Ljava/lang/Object;", ClassDescUtil.getDesc(Object[][].class)); + } + + @Test + void nameToClassTest() { + final Class aClass = ClassDescUtil.nameToClass("java.lang.Object[]", true, null); + assertEquals(Object[].class, aClass); + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/provider/GlobalProviderFactory.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/provider/GlobalProviderFactory.java index 7b3e20cea..61dec78cb 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/provider/GlobalProviderFactory.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/provider/GlobalProviderFactory.java @@ -12,7 +12,7 @@ package org.dromara.hutool.crypto.provider; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.crypto.SecureUtil; import java.security.Provider; diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/aop/proxy/ProxyFactory.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/aop/proxy/ProxyFactory.java index ad96c719d..96c067a6c 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/aop/proxy/ProxyFactory.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/aop/proxy/ProxyFactory.java @@ -13,7 +13,7 @@ package org.dromara.hutool.extra.aop.proxy; import org.dromara.hutool.core.reflect.ConstructorUtil; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.extra.aop.aspects.Aspect; import java.io.Serializable; diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/expression/engine/ExpressionFactory.java index fb953b641..70701de44 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/expression/engine/ExpressionFactory.java @@ -13,7 +13,7 @@ package org.dromara.hutool.extra.expression.engine; import org.dromara.hutool.core.lang.Singleton; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.log.StaticLog; diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/pinyin/engine/PinyinFactory.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/pinyin/engine/PinyinFactory.java index 5d34b8928..86f5b5068 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/pinyin/engine/PinyinFactory.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/pinyin/engine/PinyinFactory.java @@ -13,7 +13,7 @@ package org.dromara.hutool.extra.pinyin.engine; import org.dromara.hutool.core.lang.Singleton; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.log.StaticLog; diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/template/engine/TemplateFactory.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/template/engine/TemplateFactory.java index 56296242c..a3fd579c0 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/template/engine/TemplateFactory.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/template/engine/TemplateFactory.java @@ -15,7 +15,7 @@ package org.dromara.hutool.extra.template.engine; import org.dromara.hutool.core.lang.Singleton; import org.dromara.hutool.core.reflect.ConstructorUtil; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.extra.template.TemplateConfig; import org.dromara.hutool.extra.template.TemplateEngine; import org.dromara.hutool.extra.template.TemplateException; diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/tokenizer/engine/TokenizerFactory.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/tokenizer/engine/TokenizerFactory.java index 435a9ee79..5a748ccf1 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/tokenizer/engine/TokenizerFactory.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/tokenizer/engine/TokenizerFactory.java @@ -13,7 +13,7 @@ package org.dromara.hutool.extra.tokenizer.engine; import org.dromara.hutool.core.lang.Singleton; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.extra.tokenizer.TokenizerEngine; import org.dromara.hutool.extra.tokenizer.TokenizerException; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java index 65d487581..5fb5c188a 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java @@ -14,7 +14,7 @@ package org.dromara.hutool.http.client.engine; import org.dromara.hutool.core.lang.Singleton; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.http.HttpException; import org.dromara.hutool.http.client.ClientConfig; import org.dromara.hutool.http.client.ClientEngine; diff --git a/hutool-log/src/main/java/org/dromara/hutool/log/LogFactory.java b/hutool-log/src/main/java/org/dromara/hutool/log/LogFactory.java index dcf2dd996..db75ce6bf 100644 --- a/hutool-log/src/main/java/org/dromara/hutool/log/LogFactory.java +++ b/hutool-log/src/main/java/org/dromara/hutool/log/LogFactory.java @@ -15,7 +15,7 @@ package org.dromara.hutool.log; import org.dromara.hutool.core.io.resource.ResourceUtil; import org.dromara.hutool.core.lang.caller.CallerUtil; import org.dromara.hutool.core.map.SafeConcurrentHashMap; -import org.dromara.hutool.core.util.ServiceLoaderUtil; +import org.dromara.hutool.core.spi.ServiceLoaderUtil; import org.dromara.hutool.log.engine.console.ConsoleLogFactory; import org.dromara.hutool.log.engine.jdk.JdkLogFactory; diff --git a/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/Props.java b/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/Props.java index 23cff2e80..23c7673ac 100644 --- a/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/Props.java +++ b/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/Props.java @@ -201,7 +201,7 @@ public final class Props extends Properties implements TypeGetter public void load(final Resource resource) { Assert.notNull(resource, "Props resource must be not null!"); this.resource = resource; - PropsLoaderUtil.loadTo(this, resource, this.charset); + ResourceUtil.loadTo(this, resource, this.charset); } /** diff --git a/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/PropsLoaderUtil.java b/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/PropsLoaderUtil.java deleted file mode 100644 index 3d2cc7a7e..000000000 --- a/hutool-setting/src/main/java/org/dromara/hutool/setting/dialect/PropsLoaderUtil.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.setting.dialect; - -import org.dromara.hutool.core.io.IORuntimeException; -import org.dromara.hutool.core.io.resource.Resource; -import org.dromara.hutool.core.io.resource.ResourceUtil; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Properties; - -/** - * {@link Properties} 资源内容加载工具 - * - * @author looly - * @since 6.0.0 - */ -public class PropsLoaderUtil { - - /** - * 加载配置文件内容到{@link Properties}中
- * 需要注意的是,如果资源文件的扩展名是.xml,会调用{@link Properties#loadFromXML(InputStream)} 读取。 - * - * @param properties {@link Properties}文件 - * @param resource 资源 - * @param charset 编码,对XML无效 - */ - public static void loadTo(final Properties properties, final Resource resource, final Charset charset) { - final String filename = resource.getName(); - if (filename != null && filename.endsWith(".xml")) { - // XML - try (final InputStream in = resource.getStream()) { - properties.loadFromXML(in); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } else { - // .properties - try (final BufferedReader reader = resource.getReader(charset)) { - properties.load(reader); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - } - - /** - * 加载指定名称的所有配置文件内容到{@link Properties}中 - * - * @param properties {@link Properties}文件 - * @param resourceName 资源名,可以是相对classpath的路径,也可以是绝对路径 - * @param classLoader {@link ClassLoader},{@code null}表示使用默认的当前上下文ClassLoader - * @param charset 编码,对XML无效 - */ - public static void loadAllTo(final Properties properties, final String resourceName, final ClassLoader classLoader, final Charset charset) { - for (final Resource resource : ResourceUtil.getResources(resourceName, classLoader)) { - loadTo(properties, resource, charset); - } - } -}