From 9781d5eb9f3e66c348c536b58edf6bd90624ac04 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Apr 2023 17:31:13 +0800 Subject: [PATCH] fix code --- .../hutool/core/cache/SimpleCache.java | 15 ++ .../hutool/core/io/resource/ResourceUtil.java | 4 +- .../hutool/core/reflect/ConstructorUtil.java | 3 +- .../hutool/core/spi/AbsServiceLoader.java | 52 ++++++ .../hutool/core/spi/KVServiceLoader.java | 128 ++++++++++++-- .../hutool/core/spi/ListServiceLoader.java | 158 ++++++++++++++++++ .../dromara/hutool/core/spi/SPIException.java | 49 ++++++ .../hutool/core/spi/ServiceLoader.java | 12 ++ .../dromara/hutool/core/util/AccessUtil.java | 41 +++++ 9 files changed, 443 insertions(+), 19 deletions(-) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/spi/AbsServiceLoader.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/spi/ListServiceLoader.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/spi/SPIException.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/util/AccessUtil.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/cache/SimpleCache.java b/hutool-core/src/main/java/org/dromara/hutool/core/cache/SimpleCache.java index 123052916..75b31bd0c 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/cache/SimpleCache.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/cache/SimpleCache.java @@ -71,6 +71,21 @@ public class SimpleCache implements Iterable>, Serializabl this.rawMap = initMap; } + /** + * 是否包含键 + * + * @param key 键 + * @return 是否包含 + */ + public boolean containsKey(final K key) { + lock.readLock().lock(); + try { + return rawMap.containsKey(MutableObj.of(key)); + } finally { + lock.readLock().unlock(); + } + } + /** * 从缓存池中查找值 * 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 e1d217adc..32da2fbdf 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 @@ -299,7 +299,7 @@ public class ResourceUtil { * * @param properties {@link Properties}文件 * @param resource 资源 - * @param charset 编码,对XML无效 + * @param charset 编码,对XML无效,默认UTF-8 */ public static void loadTo(final Properties properties, final Resource resource, final Charset charset) { Assert.notNull(properties); @@ -329,7 +329,7 @@ public class ResourceUtil { * @param properties {@link Properties}文件 * @param resourceName 资源名,可以是相对classpath的路径,也可以是绝对路径 * @param classLoader {@link ClassLoader},{@code null}表示使用默认的当前上下文ClassLoader - * @param charset 编码,对XML无效 + * @param charset 编码,对XML无效,默认UTF-8 */ public static void loadAllTo(final Properties properties, final String resourceName, final ClassLoader classLoader, final Charset charset) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ConstructorUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ConstructorUtil.java index 8b5f9680a..2ff002c53 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ConstructorUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ConstructorUtil.java @@ -12,6 +12,7 @@ package org.dromara.hutool.core.reflect; +import org.dromara.hutool.core.classloader.ClassLoaderUtil; import org.dromara.hutool.core.exceptions.UtilException; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.map.WeakConcurrentMap; @@ -103,7 +104,7 @@ public class ConstructorUtil { @SuppressWarnings("unchecked") public static T newInstance(final String clazz) throws UtilException { try { - return (T) Class.forName(clazz).newInstance(); + return (T) ClassLoaderUtil.loadClass(clazz).newInstance(); } catch (final Exception e) { throw new UtilException(e, "Instance class [{}] error!", clazz); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/spi/AbsServiceLoader.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/AbsServiceLoader.java new file mode 100644 index 000000000..c756a78bd --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/AbsServiceLoader.java @@ -0,0 +1,52 @@ +/* + * 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.text.StrUtil; + +import java.nio.charset.Charset; +import java.security.AccessControlContext; +import java.security.AccessController; + +/** + * 抽象服务加载器,提供包括路径前缀、服务类、类加载器、编码、安全相关持有 + * + * @param 服务类型 + * @author looly + * @since 6.0.0 + */ +public abstract class AbsServiceLoader implements ServiceLoader { + + protected final String pathPrefix; + protected final Class serviceClass; + protected final ClassLoader classLoader; + protected final Charset charset; + protected final AccessControlContext acc; + + /** + * 构造 + * + * @param pathPrefix 路径前缀 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @param charset 编码,默认UTF-8 + */ + public AbsServiceLoader(final String pathPrefix, final Class serviceClass, + final ClassLoader classLoader, final Charset charset) { + this.pathPrefix = StrUtil.addSuffixIfNot(pathPrefix, StrUtil.SLASH); + this.serviceClass = serviceClass; + this.classLoader = classLoader; + this.charset = charset; + this.acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; + } +} 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 index 3beddce15..7d92f4aa2 100644 --- 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 @@ -12,37 +12,92 @@ package org.dromara.hutool.core.spi; +import org.dromara.hutool.core.cache.SimpleCache; import org.dromara.hutool.core.classloader.ClassLoaderUtil; import org.dromara.hutool.core.io.resource.ResourceUtil; +import org.dromara.hutool.core.reflect.ConstructorUtil; import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.AccessUtil; import java.nio.charset.Charset; +import java.util.HashMap; import java.util.Properties; -public class KVServiceLoader implements ServiceLoader{ +/** + * 键值对服务加载器,使用{@link Properties}加载并存储服务 + * + * @param 服务类型 + * @author looly + * @since 6.0.0 + */ +public class KVServiceLoader extends AbsServiceLoader { - private final String pathPrefix; - private final Class serviceClass; - private final ClassLoader classLoader; - private final Charset charset; + private static final String PREFIX_HUTOOL = "META-INF/hutool/"; + + // region ----- of + + /** + * 构建KVServiceLoader + * + * @param 服务类型 + * @param serviceClass 服务名称 + * @return KVServiceLoader + */ + public static KVServiceLoader of(final Class serviceClass) { + return of(serviceClass, null); + } + + /** + * 构建KVServiceLoader + * + * @param 服务类型 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @return KVServiceLoader + */ + public static KVServiceLoader of(final Class serviceClass, final ClassLoader classLoader) { + return of(PREFIX_HUTOOL, serviceClass, classLoader); + } + + /** + * 构建KVServiceLoader + * + * @param 服务类型 + * @param pathPrefix 路径前缀 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @return KVServiceLoader + */ + public static KVServiceLoader of(final String pathPrefix, final Class serviceClass, + final ClassLoader classLoader) { + return new KVServiceLoader<>(pathPrefix, serviceClass, classLoader, null); + } + // endregion private Properties serviceProperties; + private final SimpleCache serviceCache; + /** + * 构造 + * + * @param pathPrefix 路径前缀 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @param charset 编码,默认UTF-8 + */ 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; + super(pathPrefix, serviceClass, classLoader, charset); + this.serviceCache = new SimpleCache<>(new HashMap<>()); load(); } /** * 加载或重新加载全部服务 - * @return this */ - public KVServiceLoader load(){ + @Override + public void load() { final Properties properties = new Properties(); ResourceUtil.loadAllTo( properties, @@ -50,15 +105,56 @@ public class KVServiceLoader implements ServiceLoader{ classLoader, charset); this.serviceProperties = properties; - return this; } - public Class getServiceClass(final String serviceName){ + @Override + public int size() { + return this.serviceProperties.size(); + } + + /** + * 获取指定服务的实现类 + * + * @param serviceName 服务名称 + * @return 服务名称对应的实现类 + */ + public Class getServiceClass(final String serviceName) { + return AccessUtil.doPrivileged(() -> getServiceClassUnsafe(serviceName), this.acc); + } + + /** + * 获取指定名称对应的服务,使用缓存,多次调用只返回相同的服务对象 + * + * @param serviceName 服务名称 + * @return 服务对象 + */ + public S getService(final String serviceName) { + return this.serviceCache.get(serviceName, () -> createService(serviceName)); + } + + /** + * 创建服务,无缓存 + * + * @param serviceName 服务名称 + * @return 服务对象 + */ + private S createService(final String serviceName) { + return AccessUtil.doPrivileged(() -> + ConstructorUtil.newInstance(getServiceClassUnsafe(serviceName)), this.acc); + } + + /** + * 获取指定服务的实现类 + * + * @param serviceName 服务名称 + * @return 服务名称对应的实现类 + */ + private Class getServiceClassUnsafe(final String serviceName) { final String serviceClassName = this.serviceProperties.getProperty(serviceName); - if(StrUtil.isNotBlank(serviceClassName)){ - return ClassLoaderUtil.loadClass(serviceClassName); + if (StrUtil.isBlank(serviceClassName)) { + return null; } - return null; + return ClassLoaderUtil.loadClass(serviceClassName); } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/spi/ListServiceLoader.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/ListServiceLoader.java new file mode 100644 index 000000000..3e30d28cf --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/ListServiceLoader.java @@ -0,0 +1,158 @@ +/* + * 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.cache.SimpleCache; +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.io.resource.MultiResource; +import org.dromara.hutool.core.io.resource.Resource; +import org.dromara.hutool.core.io.resource.ResourceUtil; +import org.dromara.hutool.core.util.CharUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * 列表类型的服务加载器,用于替换JDK提供的{@link java.util.ServiceLoader} + * + * @param 服务类型 + * @author looly + * @since 6.0.0 + */ +public class ListServiceLoader extends AbsServiceLoader { + + private static final String PREFIX_SERVICES = "META-INF/services/"; + + // region ----- of + + /** + * 构建KVServiceLoader + * + * @param 服务类型 + * @param serviceClass 服务名称 + * @return KVServiceLoader + */ + public static ListServiceLoader of(final Class serviceClass) { + return of(serviceClass, null); + } + + /** + * 构建KVServiceLoader + * + * @param 服务类型 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @return KVServiceLoader + */ + public static ListServiceLoader of(final Class serviceClass, final ClassLoader classLoader) { + return of(PREFIX_SERVICES, serviceClass, classLoader); + } + + /** + * 构建KVServiceLoader + * + * @param 服务类型 + * @param pathPrefix 路径前缀 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @return KVServiceLoader + */ + public static ListServiceLoader of(final String pathPrefix, final Class serviceClass, + final ClassLoader classLoader) { + return new ListServiceLoader<>(pathPrefix, serviceClass, classLoader, null); + } + // endregion + + private final List serviceNames; + private final SimpleCache serviceCache; + + /** + * 构造 + * + * @param pathPrefix 路径前缀 + * @param serviceClass 服务名称 + * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 + * @param charset 编码,默认UTF-8 + */ + public ListServiceLoader(final String pathPrefix, final Class serviceClass, + final ClassLoader classLoader, final Charset charset) { + super(pathPrefix, serviceClass, classLoader, charset); + this.serviceNames = new ArrayList<>(); + this.serviceCache = new SimpleCache<>(new HashMap<>()); + + load(); + } + + @Override + public void load() { + final MultiResource resources = ResourceUtil.getResources( + pathPrefix + serviceClass.getName(), + this.classLoader); + for (final Resource resource : resources) { + parse(resource); + } + } + + @Override + public int size() { + return this.serviceNames.size(); + } + + private void parse(final Resource resource){ + try(final BufferedReader reader = resource.getReader(this.charset)){ + int lc = 1; + while(lc >= 0){ + lc = parseLine(resource, reader, lc, this.serviceNames); + } + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + private int parseLine(final Resource resource, final BufferedReader r, final int lc, + final List names) + throws IOException { + String ln = r.readLine(); + if (ln == null) { + return -1; + } + final int ci = ln.indexOf('#'); + if (ci >= 0) ln = ln.substring(0, ci); + ln = ln.trim(); + final int n = ln.length(); + if (n != 0) { + if ((ln.indexOf(CharUtil.SPACE) >= 0) || (ln.indexOf(CharUtil.TAB) >= 0)) + fail(resource, lc, "Illegal configuration-file syntax"); + int cp = ln.codePointAt(0); + if (!Character.isJavaIdentifierStart(cp)) + fail(resource, lc, "Illegal provider-class name: " + ln); + for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { + cp = ln.codePointAt(i); + if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) + fail(resource, lc, "Illegal provider-class name: " + ln); + } + if (!serviceCache.containsKey(ln) && !names.contains(ln)) + names.add(ln); + } + return lc + 1; + } + + private void fail(final Resource resource, final int line, final String msg) { + throw new SPIException(this.serviceClass + ":" + resource.getUrl() + ":" + line + ": " + msg); + } + +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/spi/SPIException.java b/hutool-core/src/main/java/org/dromara/hutool/core/spi/SPIException.java new file mode 100644 index 000000000..8bc3c865d --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/spi/SPIException.java @@ -0,0 +1,49 @@ +/* + * 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.exceptions.ExceptionUtil; +import org.dromara.hutool.core.text.StrUtil; + +/** + * SPI异常 + * + * @author looly + */ +public class SPIException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public SPIException(final Throwable cause) { + super(ExceptionUtil.getMessage(cause), cause); + } + + public SPIException(final String message) { + super(message); + } + + public SPIException(final String messageTemplate, final Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public SPIException(final String message, final Throwable cause) { + super(message, cause); + } + + public SPIException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public SPIException(final Throwable cause, final String messageTemplate, final Object... params) { + super(StrUtil.format(messageTemplate, params), cause); + } +} 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 index f6b5994e2..59efebe81 100644 --- 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 @@ -20,4 +20,16 @@ package org.dromara.hutool.core.spi; * @author looly */ public interface ServiceLoader { + + /** + * 加载服务 + */ + void load(); + + /** + * 服务总数 + * + * @return 总数 + */ + int size(); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/AccessUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/AccessUtil.java new file mode 100644 index 000000000..a36f6ec53 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/AccessUtil.java @@ -0,0 +1,41 @@ +/* + * 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.util; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * {@link AccessController}相关封装 + * + * @author looly + * @since 6.0.0 + */ +public class AccessUtil { + + /** + * @param action 执行内容 + * @param context 上下文,当为{@code null}时直接执行内容,而不检查 + * @param 执行结果类型 + * @return 结果 + */ + public static T doPrivileged(final PrivilegedAction action, + final AccessControlContext context) { + if (null == context) { + return action.run(); + } + + return AccessController.doPrivileged(action, context); + } +}