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

@ -2,7 +2,7 @@
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
</p>
<p align="center">
<strong>🍬A set of tools that keep Java sweet.</strong>
<strong>🍬Make Java Sweet Again.</strong>
</p>
<p align="center">
👉 <a href="https://hutool.cn">https://hutool.cn/</a> 👈

View File

@ -2,7 +2,7 @@
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
</p>
<p align="center">
<strong>🍬A set of tools that keep Java sweet.</strong>
<strong>🍬Make Java Sweet Again.</strong>
</p>
<p align="center">
👉 <a href="https://hutool.cn">https://hutool.cn/</a> 👈

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);
}
}

View File

@ -12,7 +12,7 @@
package org.dromara.hutool.crypto.provider;
import org.dromara.hutool.core.spi.ServiceLoaderUtil;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.crypto.SecureUtil;
import java.security.Provider;
@ -54,7 +54,7 @@ public class GlobalProviderFactory {
* @return {@link Provider} or {@code null}
*/
private static Provider _createProvider() {
final ProviderFactory factory = ServiceLoaderUtil.loadFirstAvailable(ProviderFactory.class);
final ProviderFactory factory = SpiUtil.loadFirstAvailable(ProviderFactory.class);
if (null == factory) {
return null;
}

View File

@ -13,7 +13,7 @@
package org.dromara.hutool.extra.aop.proxy;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.spi.ServiceLoaderUtil;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.extra.aop.aspects.Aspect;
import java.io.Serializable;
@ -56,7 +56,7 @@ public interface ProxyFactory extends Serializable {
* @return 代理工厂
*/
static ProxyFactory of() {
return ServiceLoaderUtil.loadFirstAvailable(ProxyFactory.class);
return SpiUtil.loadFirstAvailable(ProxyFactory.class);
}
/**

View File

@ -13,12 +13,11 @@
package org.dromara.hutool.extra.expression.engine;
import org.dromara.hutool.core.lang.Singleton;
import org.dromara.hutool.core.spi.ServiceLoaderUtil;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.log.StaticLog;
import org.dromara.hutool.extra.expression.ExpressionEngine;
import org.dromara.hutool.extra.expression.ExpressionException;
import org.dromara.hutool.log.StaticLog;
/**
* 表达式语言引擎工厂类用于根据用户引入的表达式jar自动创建对应的引擎对象
@ -56,7 +55,7 @@ public class ExpressionFactory {
* @return {@link ExpressionEngine}
*/
private static ExpressionEngine doCreate() {
final ExpressionEngine engine = ServiceLoaderUtil.loadFirstAvailable(ExpressionEngine.class);
final ExpressionEngine engine = SpiUtil.loadFirstAvailable(ExpressionEngine.class);
if(null != engine){
return engine;
}

View File

@ -13,12 +13,11 @@
package org.dromara.hutool.extra.pinyin.engine;
import org.dromara.hutool.core.lang.Singleton;
import org.dromara.hutool.core.spi.ServiceLoaderUtil;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.log.StaticLog;
import org.dromara.hutool.extra.pinyin.PinyinEngine;
import org.dromara.hutool.extra.pinyin.PinyinException;
import org.dromara.hutool.log.StaticLog;
/**
* 简单拼音引擎工厂用于根据用户引入的拼音库jar自动创建对应的拼音引擎对象
@ -55,7 +54,7 @@ public class PinyinFactory {
* @return {@link PinyinEngine}
*/
private static PinyinEngine doCreate() {
final PinyinEngine engine = ServiceLoaderUtil.loadFirstAvailable(PinyinEngine.class);
final PinyinEngine engine = SpiUtil.loadFirstAvailable(PinyinEngine.class);
if(null != engine){
return engine;
}

View File

@ -14,8 +14,8 @@ 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.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil;
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;
@ -75,7 +75,7 @@ public class TemplateFactory {
if(null != customEngineClass){
engine = ConstructorUtil.newInstance(customEngineClass);
}else{
engine = ServiceLoaderUtil.loadFirstAvailable(TemplateEngine.class);
engine = SpiUtil.loadFirstAvailable(TemplateEngine.class);
}
if(null != engine){
return engine.init(config);

View File

@ -13,7 +13,7 @@
package org.dromara.hutool.extra.tokenizer.engine;
import org.dromara.hutool.core.lang.Singleton;
import org.dromara.hutool.core.spi.ServiceLoaderUtil;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.extra.tokenizer.TokenizerEngine;
import org.dromara.hutool.extra.tokenizer.TokenizerException;
@ -55,7 +55,7 @@ public class TokenizerFactory {
* @return {@link TokenizerEngine}
*/
private static TokenizerEngine doCreate() {
final TokenizerEngine engine = ServiceLoaderUtil.loadFirstAvailable(TokenizerEngine.class);
final TokenizerEngine engine = SpiUtil.loadFirstAvailable(TokenizerEngine.class);
if(null != engine){
return engine;
}

View File

@ -13,8 +13,8 @@
package org.dromara.hutool.http.client.engine;
import org.dromara.hutool.core.lang.Singleton;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil;
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;
@ -68,7 +68,7 @@ public class ClientEngineFactory {
* @return {@code EngineFactory}
*/
private static ClientEngine doCreate() {
final ClientEngine engine = ServiceLoaderUtil.loadFirstAvailable(ClientEngine.class);
final ClientEngine engine = SpiUtil.loadFirstAvailable(ClientEngine.class);
if (null != engine) {
return engine;
}

View File

@ -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.spi.ServiceLoaderUtil;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.log.engine.console.ConsoleLogFactory;
import org.dromara.hutool.log.engine.jdk.JdkLogFactory;
@ -182,7 +182,7 @@ public abstract class LogFactory {
* @return 日志实现类
*/
private static LogFactory doCreate() {
final LogFactory factory = ServiceLoaderUtil.loadFirstAvailable(LogFactory.class);
final LogFactory factory = SpiUtil.loadFirstAvailable(LogFactory.class);
if (null != factory) {
return factory;
}