This commit is contained in:
Looly
2023-04-16 00:54:24 +08:00
parent f504a176e4
commit 3b53230a38
15 changed files with 221 additions and 30 deletions

View File

@@ -34,7 +34,7 @@ import java.util.ServiceLoader;
* @author looly
* @since 5.1.6
*/
public class ServiceLoaderUtil {
public class JdkServiceLoaderUtil {
/**
* 加载第一个可用服务如果用户定义了多个接口实现类只获取第一个不报错的服务

View File

@@ -20,8 +20,7 @@ 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;
import java.util.*;
/**
* 键值对服务加载器,使用{@link Properties}加载并存储服务
@@ -75,6 +74,7 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
// endregion
private Properties serviceProperties;
// key: serviceName, value: service instance
private final SimpleCache<String, S> serviceCache;
/**
@@ -136,6 +136,24 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
return this.serviceCache.get(serviceName, () -> createService(serviceName));
}
@Override
public Iterator<S> iterator() {
return new Iterator<S>() {
private final Iterator<String> nameIter =
serviceProperties.stringPropertyNames().iterator();
@Override
public boolean hasNext() {
return nameIter.hasNext();
}
@Override
public S next() {
return getService(nameIter.next());
}
};
}
// region ----- private methods
/**
* 创建服务,无缓存
*
@@ -161,4 +179,5 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
return ClassLoaderUtil.loadClass(serviceClassName);
}
// endregion
}

View File

@@ -13,21 +13,33 @@
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.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.reflect.ConstructorUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.AccessUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* 列表类型的服务加载器用于替换JDK提供的{@link java.util.ServiceLoader}
* 列表类型的服务加载器用于替换JDK提供的{@link java.util.ServiceLoader}<br>
* 相比JDK增加了
* <ul>
* <li>可选服务存储位置默认位于META-INF/services/)。</li>
* <li>可自定义编码。</li>
* <li>可自定义加载指定的服务实例。</li>
* <li>可自定义加载指定的服务类,由用户决定如何实例化(如传入自定义构造参数等)。</li>
* <li>提供更加灵活的服务加载机制,当选择加载指定服务时,其它服务无需加载。</li>
* </ul>
*
* @param <S> 服务类型
* @author looly
@@ -78,6 +90,7 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
// endregion
private final List<String> serviceNames;
// key: className, value: service instance
private final SimpleCache<String, S> serviceCache;
/**
@@ -114,6 +127,48 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
return this.serviceNames.size();
}
/**
* 获取指定服务的实现类
*
* @param index 服务名称
* @return 服务名称对应的实现类
*/
public Class<S> getServiceClass(final int index) {
return AccessUtil.doPrivileged(() -> getServiceClassUnsafe(index), this.acc);
}
/**
* 获取指定序号对应的服务,使用缓存,多次调用只返回相同的服务对象
*
* @param index 服务名称
* @return 服务对象
*/
public S getService(final int index) {
final String serviceClassName = this.serviceNames.get(index);
if(null == serviceClassName){
return null;
}
return getServiceByName(serviceClassName);
}
@Override
public Iterator<S> iterator() {
return new Iterator<S>() {
private final Iterator<String> nameIter = serviceNames.iterator();
@Override
public boolean hasNext() {
return nameIter.hasNext();
}
@Override
public S next() {
return getServiceByName(nameIter.next());
}
};
}
// region ----- private methods
/**
* 解析一个资源一个资源对应一个service文件
*
@@ -165,12 +220,21 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
return lineNo + 1;
}
/**
* 检查行
*
* @param resource 资源
* @param lineNo 行号
* @param line 行内容
*/
private void checkLine(final Resource resource, final int lineNo, final String line) {
if (StrUtil.containsBlank(line)) {
// 类中不允许空白符
fail(resource, lineNo, "Illegal configuration-file syntax");
}
int cp = line.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp)) {
// 非Java合法标识符
fail(resource, lineNo, "Illegal provider-class name: " + line);
}
final int n = line.length();
@@ -182,8 +246,52 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
}
}
private void fail(final Resource resource, final int line, final String msg) {
throw new SPIException(this.serviceClass + ":" + resource.getUrl() + ":" + line + ": " + msg);
/**
* 抛出异常
*
* @param resource 资源
* @param lineNo 行号
* @param msg 消息
*/
private void fail(final Resource resource, final int lineNo, final String msg) {
throw new SPIException(this.serviceClass + ":" + resource.getUrl() + ":" + lineNo + ": " + msg);
}
/**
* 获取指定class名对应的服务使用缓存多次调用只返回相同的服务对象
*
* @param serviceClassName 服务名称
* @return 服务对象
*/
private S getServiceByName(final String serviceClassName) {
return this.serviceCache.get(serviceClassName, () -> createService(serviceClassName));
}
/**
* 创建服务,无缓存
*
* @param serviceClassName 服务类名称
* @return 服务对象
*/
private S createService(final String serviceClassName) {
return AccessUtil.doPrivileged(() ->
ConstructorUtil.newInstance(ClassLoaderUtil.loadClass(serviceClassName)),
this.acc);
}
/**
* 获取指定服务的实现类
*
* @param index 服务索引号
* @return 服务名称对应的实现类
*/
private Class<S> getServiceClassUnsafe(final int index) {
final String serviceClassName = this.serviceNames.get(index);
if (StrUtil.isBlank(serviceClassName)) {
return null;
}
return ClassLoaderUtil.loadClass(serviceClassName);
}
// endregion
}

View File

@@ -16,10 +16,10 @@ package org.dromara.hutool.core.spi;
* SPI服务加载接口<br>
* 用户实现此接口用于制定不同的服务加载方式
*
* @param <T> 服务对象类型
* @param <S> 服务对象类型
* @author looly
*/
public interface ServiceLoader<T> {
public interface ServiceLoader<S> extends Iterable<S>{
/**
* 加载服务

View File

@@ -0,0 +1,66 @@
/*
* 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 java.util.Iterator;
/**
* 服务提供接口SPIService Provider interface相关工具类
*
* @author looly
* @since 6.0.0
*/
public class SpiUtil {
/**
* 加载第一个可用服务,如果用户定义了多个接口实现类,只获取第一个不报错的服务
*
* @param <T> 接口类型
* @param clazz 服务接口
* @return 第一个服务接口实现对象,无实现返回{@code null}
*/
public static <T> T loadFirstAvailable(final Class<T> clazz) {
final Iterator<T> iterator = loadList(clazz).iterator();
while (iterator.hasNext()) {
try {
return iterator.next();
} catch (final Throwable ignore) {
// ignore
}
}
return null;
}
/**
* 加载服务
*
* @param <T> 接口类型
* @param clazz 服务接口
* @return 服务接口实现列表
*/
public static <T> ServiceLoader<T> loadList(final Class<T> clazz) {
return loadList(clazz, null);
}
/**
* 加载服务
*
* @param <T> 接口类型
* @param clazz 服务接口
* @param loader {@link ClassLoader}
* @return 服务接口实现列表
*/
public static <T> ServiceLoader<T> loadList(final Class<T> clazz, final ClassLoader loader) {
return ListServiceLoader.of(clazz, loader);
}
}