This commit is contained in:
Looly 2023-04-16 01:34:41 +08:00
parent 3b53230a38
commit bc9d665036
6 changed files with 132 additions and 16 deletions

View File

@ -41,6 +41,16 @@ import java.util.List;
* <li>提供更加灵活的服务加载机制当选择加载指定服务时其它服务无需加载</li> * <li>提供更加灵活的服务加载机制当选择加载指定服务时其它服务无需加载</li>
* </ul> * </ul>
* *
* <p>
* 服务文件默认位于"META-INF/services/"文件名为服务接口类全名内容类似于
* <pre>
* # 我是注释
* hutool.service.Service1
* hutool.service.Service2
* </pre>
* <p>
* 通过调用{@link #getService(int)}方法传入序号即可获取对应服务
*
* @param <S> 服务类型 * @param <S> 服务类型
* @author looly * @author looly
* @since 6.0.0 * @since 6.0.0
@ -145,7 +155,7 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
*/ */
public S getService(final int index) { public S getService(final int index) {
final String serviceClassName = this.serviceNames.get(index); final String serviceClassName = this.serviceNames.get(index);
if(null == serviceClassName){ if (null == serviceClassName) {
return null; return null;
} }
return getServiceByName(serviceClassName); return getServiceByName(serviceClassName);
@ -155,6 +165,7 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
public Iterator<S> iterator() { public Iterator<S> iterator() {
return new Iterator<S>() { return new Iterator<S>() {
private final Iterator<String> nameIter = serviceNames.iterator(); private final Iterator<String> nameIter = serviceNames.iterator();
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return nameIter.hasNext(); return nameIter.hasNext();
@ -275,7 +286,7 @@ public class ListServiceLoader<S> extends AbsServiceLoader<S> {
*/ */
private S createService(final String serviceClassName) { private S createService(final String serviceClassName) {
return AccessUtil.doPrivileged(() -> return AccessUtil.doPrivileged(() ->
ConstructorUtil.newInstance(ClassLoaderUtil.loadClass(serviceClassName)), ConstructorUtil.newInstance(ClassLoaderUtil.loadClass(serviceClassName)),
this.acc); this.acc);
} }

View File

@ -20,16 +20,29 @@ import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.AccessUtil; import org.dromara.hutool.core.util.AccessUtil;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.*; import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
/** /**
* 键值对服务加载器使用{@link Properties}加载并存储服务 * 键值对服务加载器使用{@link Properties}加载并存储服务<br>
* 服务文件默认位于"META-INF/hutool/"文件名为服务接口类全名
*
* <p>
* 内容类似于
* <pre>
* # 我是注释
* service1 = hutool.service.Service1
* service2 = hutool.service.Service2
* </pre>
* <p>
* 通过调用{@link #getService(String)}方法传入等号前的名称即可获取对应服务
* *
* @param <S> 服务类型 * @param <S> 服务类型
* @author looly * @author looly
* @since 6.0.0 * @since 6.0.0
*/ */
public class KVServiceLoader<S> extends AbsServiceLoader<S> { public class MapServiceLoader<S> extends AbsServiceLoader<S> {
private static final String PREFIX_HUTOOL = "META-INF/hutool/"; private static final String PREFIX_HUTOOL = "META-INF/hutool/";
@ -42,7 +55,7 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
* @param serviceClass 服务名称 * @param serviceClass 服务名称
* @return KVServiceLoader * @return KVServiceLoader
*/ */
public static <S> KVServiceLoader<S> of(final Class<S> serviceClass) { public static <S> MapServiceLoader<S> of(final Class<S> serviceClass) {
return of(serviceClass, null); return of(serviceClass, null);
} }
@ -54,7 +67,7 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
* @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器
* @return KVServiceLoader * @return KVServiceLoader
*/ */
public static <S> KVServiceLoader<S> of(final Class<S> serviceClass, final ClassLoader classLoader) { public static <S> MapServiceLoader<S> of(final Class<S> serviceClass, final ClassLoader classLoader) {
return of(PREFIX_HUTOOL, serviceClass, classLoader); return of(PREFIX_HUTOOL, serviceClass, classLoader);
} }
@ -67,9 +80,9 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
* @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器
* @return KVServiceLoader * @return KVServiceLoader
*/ */
public static <S> KVServiceLoader<S> of(final String pathPrefix, final Class<S> serviceClass, public static <S> MapServiceLoader<S> of(final String pathPrefix, final Class<S> serviceClass,
final ClassLoader classLoader) { final ClassLoader classLoader) {
return new KVServiceLoader<>(pathPrefix, serviceClass, classLoader, null); return new MapServiceLoader<>(pathPrefix, serviceClass, classLoader, null);
} }
// endregion // endregion
@ -85,8 +98,8 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
* @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器 * @param classLoader 自定义类加载器, {@code null}表示使用默认当前的类加载器
* @param charset 编码默认UTF-8 * @param charset 编码默认UTF-8
*/ */
public KVServiceLoader(final String pathPrefix, final Class<S> serviceClass, public MapServiceLoader(final String pathPrefix, final Class<S> serviceClass,
final ClassLoader classLoader, final Charset charset) { final ClassLoader classLoader, final Charset charset) {
super(pathPrefix, serviceClass, classLoader, charset); super(pathPrefix, serviceClass, classLoader, charset);
this.serviceCache = new SimpleCache<>(new HashMap<>()); this.serviceCache = new SimpleCache<>(new HashMap<>());
@ -141,6 +154,7 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
return new Iterator<S>() { return new Iterator<S>() {
private final Iterator<String> nameIter = private final Iterator<String> nameIter =
serviceProperties.stringPropertyNames().iterator(); serviceProperties.stringPropertyNames().iterator();
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return nameIter.hasNext(); return nameIter.hasNext();
@ -154,6 +168,7 @@ public class KVServiceLoader<S> extends AbsServiceLoader<S> {
} }
// region ----- private methods // region ----- private methods
/** /**
* 创建服务无缓存 * 创建服务无缓存
* *

View File

@ -25,12 +25,23 @@ public class SpiUtil {
/** /**
* 加载第一个可用服务如果用户定义了多个接口实现类只获取第一个不报错的服务 * 加载第一个可用服务如果用户定义了多个接口实现类只获取第一个不报错的服务
* *
* @param <T> 接口类型 * @param <S> 服务类型
* @param clazz 服务接口 * @param clazz 服务接口
* @return 第一个服务接口实现对象无实现返回{@code null} * @return 第一个服务接口实现对象无实现返回{@code null}
*/ */
public static <T> T loadFirstAvailable(final Class<T> clazz) { public static <S> S loadFirstAvailable(final Class<S> clazz) {
final Iterator<T> iterator = loadList(clazz).iterator(); return loadFirstAvailable(loadList(clazz));
}
/**
* 加载第一个可用服务如果用户定义了多个接口实现类只获取第一个不报错的服务
*
* @param <S> 服务类型
* @param serviceLoader {@link ServiceLoader}
* @return 第一个服务接口实现对象无实现返回{@code null}
*/
public static <S> S loadFirstAvailable(final ServiceLoader<S> serviceLoader) {
final Iterator<S> iterator = serviceLoader.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
try { try {
return iterator.next(); return iterator.next();

View File

@ -11,6 +11,10 @@
*/ */
/** /**
* 服务提供接口SPIService Provider interface机制相关封装 * 服务提供接口SPIService Provider interface机制相关封装包括
* <ul>
* <li>{@link org.dromara.hutool.core.spi.ListServiceLoader}提供列表形式的服务定义</li>
* <li>{@link org.dromara.hutool.core.spi.MapServiceLoader}提供键值对形式的服务定义</li>
* </ul>
*/ */
package org.dromara.hutool.core.spi; package org.dromara.hutool.core.spi;

View File

@ -0,0 +1,69 @@
/*
* 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.UtilException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class SpiUtilTest {
@Test
void loadFirstAvailableTest() {
final MapServiceLoader<TestSPI1> serviceLoader = MapServiceLoader.of(TestSPI1.class);
final TestSPI1 testSPI1 = SpiUtil.loadFirstAvailable(serviceLoader);
Assertions.assertNotNull(testSPI1);
Assertions.assertEquals("test service 1", testSPI1.doSth());
}
@Test
void getServiceClassTest() {
final MapServiceLoader<TestSPI1> serviceLoader = MapServiceLoader.of(TestSPI1.class);
final Class<TestSPI1> service1 = serviceLoader.getServiceClass("service1");
Assertions.assertEquals(TestService1.class, service1);
}
@Test
void getServiceClassNotExistTest() {
final MapServiceLoader<TestSPI1> serviceLoader = MapServiceLoader.of(TestSPI1.class);
Assertions.assertThrows(UtilException.class, ()->{
serviceLoader.getServiceClass("service2");
});
}
@Test
void getServiceClassEmptyTest() {
final MapServiceLoader<TestSPI1> serviceLoader = MapServiceLoader.of(TestSPI1.class);
final Class<TestSPI1> serviceEmpty = serviceLoader.getServiceClass("serviceEmpty");
Assertions.assertNull(serviceEmpty);
}
@Test
void getServiceNotDefineTest() {
final MapServiceLoader<TestSPI1> serviceLoader = MapServiceLoader.of(TestSPI1.class);
final Class<TestSPI1> service1 = serviceLoader.getServiceClass("serviceNotDefine");
Assertions.assertNull(service1);
}
public interface TestSPI1{
String doSth();
}
public static class TestService1 implements TestSPI1{
@Override
public String doSth() {
return "test service 1";
}
}
}

View File

@ -0,0 +1,6 @@
# 无服务
serviceEmpty =
# 正常服务
service1 = org.dromara.hutool.core.spi.SpiUtilTest$TestService1
# 此服务类未定义
service2 = org.dromara.hutool.core.spi.SpiUtilTest$TestService2