mirror of
https://github.com/LamGC/ContentGrabbingJi.git
synced 2025-04-29 22:27:33 +00:00
[Add] Core 添加内部 CacheStoreBuilder;
[Add] CacheStoreBuilder 添加 Builder 以统一使用所有 Factory 创建 CacheStore; [Add] FactoryInfo Factory 注解的信息实体类; [Add] NoSuchFactoryException 当无可用 Factory 时的自定义异常; [Add] CacheStoreBuilderTest 尚不完善的单元测试类, 仅测试有效情况, 因 SPI 机制原因, 不方便编写多个情况的测试用例;
This commit is contained in:
parent
631c11b659
commit
3e591e8c26
227
ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java
vendored
Normal file
227
ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java
vendored
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache;
|
||||||
|
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringConverter;
|
||||||
|
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CacheStore 构造器.
|
||||||
|
*
|
||||||
|
* <p>这只是个门面类, 最终调用 {@link CacheStoreFactory} 的具体实现类获取 {@link CacheStore} 对象.
|
||||||
|
* <p> CacheStoreBuilder 确保了返回不为 null,
|
||||||
|
* 即使所有 Factory 无法返回合适的 CacheStore 实例, 也只会抛出 {@link NoSuchFactoryException} 异常.
|
||||||
|
* @see CacheStoreFactory
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class CacheStoreBuilder {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilder.class);
|
||||||
|
private final static List<CacheStoreFactory> FACTORY_LIST = new LinkedList<>();
|
||||||
|
private final static Map<CacheStoreFactory, FactoryInfo> FACTORY_INFO_MAP = new Hashtable<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 SPI 机制加载所有缓存组件.
|
||||||
|
*
|
||||||
|
* <p>第一次执行时加载, 由 {@link #getFactory(Function)} 调用.
|
||||||
|
* <p>由于 ServiceLoader 线程不安全, 所以通过 synchronized 保证其安全性.
|
||||||
|
* 不通过 static 块进行初始化的原因是因为担心发生异常导致无法继续执行
|
||||||
|
* (除非必要, 否则不要使用 static 执行可能会发生异常的代码.).
|
||||||
|
*/
|
||||||
|
private synchronized static void loadFactory() {
|
||||||
|
if (FACTORY_LIST.size() != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ServiceLoader<CacheStoreFactory> FACTORY_LOADER = ServiceLoader.load(CacheStoreFactory.class);
|
||||||
|
try {
|
||||||
|
for (CacheStoreFactory factory : FACTORY_LOADER) {
|
||||||
|
FactoryInfo info;
|
||||||
|
try {
|
||||||
|
info = new FactoryInfo(factory.getClass());
|
||||||
|
FACTORY_INFO_MAP.put(factory, info);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("Factory {} 加载失败: {}", factory.getClass().getName(), e.getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FACTORY_LIST.add(factory);
|
||||||
|
log.info("Factory {} 已加载(优先级: {}, 实现类: {}).",
|
||||||
|
info.getFactoryName(),
|
||||||
|
info.getFactoryPriority(),
|
||||||
|
factory.getClass().getName());
|
||||||
|
}
|
||||||
|
FACTORY_LIST.sort(new PriorityComparator());
|
||||||
|
} catch (Error error) {
|
||||||
|
// 防止发生 Error 又不输出到日志导致玄学问题难以排查.
|
||||||
|
log.error("加载 CacheStoreFactory 时发生严重错误.", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级排序器.
|
||||||
|
*/
|
||||||
|
private final static class PriorityComparator implements Comparator<CacheStoreFactory> {
|
||||||
|
@Override
|
||||||
|
public int compare(CacheStoreFactory o1, CacheStoreFactory o2) {
|
||||||
|
FactoryInfo info1 = Objects.requireNonNull(FACTORY_INFO_MAP.get(o1));
|
||||||
|
FactoryInfo info2 = Objects.requireNonNull(FACTORY_INFO_MAP.get(o2));
|
||||||
|
return info2.getFactoryPriority() - info1.getFactoryPriority();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个当前可用的高优先级 Factory 对象.
|
||||||
|
* @return 返回可用的高优先级 Factory 对象.
|
||||||
|
*/
|
||||||
|
private static <R extends CacheStore<?>> R getFactory(Function<CacheStoreFactory, R> function) throws NoSuchFactoryException {
|
||||||
|
if (FACTORY_LIST.size() == 0) {
|
||||||
|
loadFactory();
|
||||||
|
}
|
||||||
|
Iterator<CacheStoreFactory> iterator = FACTORY_LIST.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
CacheStoreFactory factory = iterator.next();
|
||||||
|
FactoryInfo info = FACTORY_INFO_MAP.get(factory);
|
||||||
|
try {
|
||||||
|
if (factory.canGetCacheStore()) {
|
||||||
|
log.debug("CacheStoreFactory {} 可用(优先级: {}).", info.getFactoryName(), info.getFactoryPriority());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("CacheStoreFactory " + info.getFactoryName() +
|
||||||
|
" (" + factory.getClass().getName() + ") 检查可用性时发生异常", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
R result = function.apply(factory);
|
||||||
|
log.trace("已通过 Factory '{}' 获取 CacheStore '{}'. (Factory实现类: {}).",
|
||||||
|
info.getFactoryName(),
|
||||||
|
result.getClass().getName(),
|
||||||
|
factory.getClass().getName());
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!iterator.hasNext()) {
|
||||||
|
throw new NoSuchFactoryException(new GetCacheStoreException("CacheStoreFactory " + info.getFactoryName() +
|
||||||
|
" (" + factory.getClass().getName() + ") 创建 CacheStore 时发生异常.", e));
|
||||||
|
} else {
|
||||||
|
if (e instanceof GetCacheStoreException) {
|
||||||
|
log.warn("CacheStoreFactory '{} ({})' 无法提供相应 CacheStore. 原因: {}",
|
||||||
|
info.getFactoryName(), factory.getClass().getName(), e.getMessage());
|
||||||
|
} else {
|
||||||
|
log.warn("CacheStoreFactory '" + info.getFactoryName() +
|
||||||
|
" (" + factory.getClass().getName() + ")' 创建 CacheStore 时发生异常.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NoSuchFactoryException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单项缓存存储容器.
|
||||||
|
* @param identify 缓存容器标识.
|
||||||
|
* @param converter 类型转换器.
|
||||||
|
* @param <V> 值类型.
|
||||||
|
* @return 返回新的存储容器, 与其他容器互不干扰.
|
||||||
|
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
|
||||||
|
*/
|
||||||
|
public static <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
|
||||||
|
try {
|
||||||
|
return getFactory(factory -> {
|
||||||
|
SingleCacheStore<V> singleCacheStoreInstance = factory.newSingleCacheStore(identify, converter);
|
||||||
|
if (singleCacheStoreInstance == null) {
|
||||||
|
throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null.");
|
||||||
|
}
|
||||||
|
return singleCacheStoreInstance;
|
||||||
|
});
|
||||||
|
} catch (NoSuchFactoryException e) {
|
||||||
|
throw new GetCacheStoreException("无可用的 Factory.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取列表缓存存储容器.
|
||||||
|
* @param identify 缓存容器标识.
|
||||||
|
* @param converter 类型转换器.
|
||||||
|
* @param <E> 元素类型.
|
||||||
|
* @return 返回新的存储容器, 与其他容器互不干扰.
|
||||||
|
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
|
||||||
|
*/
|
||||||
|
public static <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
|
||||||
|
try {
|
||||||
|
return getFactory(factory -> {
|
||||||
|
ListCacheStore<E> listCacheStoreInstance = factory.newListCacheStore(identify, converter);
|
||||||
|
if (listCacheStoreInstance == null) {
|
||||||
|
throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null.");
|
||||||
|
}
|
||||||
|
return listCacheStoreInstance;
|
||||||
|
});
|
||||||
|
} catch (NoSuchFactoryException e) {
|
||||||
|
throw new GetCacheStoreException("无可用的 Factory.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取集合缓存存储容器.
|
||||||
|
* @param identify 缓存容器标识.
|
||||||
|
* @param converter 类型转换器.
|
||||||
|
* @param <E> 元素类型.
|
||||||
|
* @return 返回新的存储容器, 与其他容器互不干扰.
|
||||||
|
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
|
||||||
|
*/
|
||||||
|
public static <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
|
||||||
|
try {
|
||||||
|
return getFactory(factory -> {
|
||||||
|
SetCacheStore<E> setCacheStoreInstance = factory.newSetCacheStore(identify, converter);
|
||||||
|
if (setCacheStoreInstance == null) {
|
||||||
|
throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null.");
|
||||||
|
}
|
||||||
|
return setCacheStoreInstance;
|
||||||
|
});
|
||||||
|
} catch (NoSuchFactoryException e) {
|
||||||
|
throw new GetCacheStoreException("无可用的 Factory.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取映射表缓存存储容器.
|
||||||
|
* @param identify 缓存容器标识.
|
||||||
|
* @param converter 类型转换器.
|
||||||
|
* @param <V> 值类型.
|
||||||
|
* @return 返回新的存储容器, 与其他容器互不干扰.
|
||||||
|
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
|
||||||
|
*/
|
||||||
|
public static <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
|
||||||
|
try {
|
||||||
|
return getFactory(factory -> {
|
||||||
|
MapCacheStore<V> mapCacheStoreInstance = factory.newMapCacheStore(identify, converter);
|
||||||
|
if (mapCacheStoreInstance == null) {
|
||||||
|
throw new GetCacheStoreException("Factory " + factory.getClass().getName() + " 返回 null.");
|
||||||
|
}
|
||||||
|
return mapCacheStoreInstance;
|
||||||
|
});
|
||||||
|
} catch (NoSuchFactoryException e) {
|
||||||
|
throw new GetCacheStoreException("无可用的 Factory.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java
vendored
Normal file
65
ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CacheStoreFactory 的标识信息.
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class FactoryInfo {
|
||||||
|
|
||||||
|
private final String factoryName;
|
||||||
|
private final int factoryPriority;
|
||||||
|
|
||||||
|
public FactoryInfo(Class<? extends CacheStoreFactory> factoryClass) {
|
||||||
|
Factory factoryAnnotation = factoryClass.getAnnotation(Factory.class);
|
||||||
|
if (factoryAnnotation == null) {
|
||||||
|
throw new IllegalArgumentException("Annotation not found");
|
||||||
|
} else if (Strings.isNullOrEmpty(factoryAnnotation.name())) {
|
||||||
|
throw new IllegalArgumentException("Factory name is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.factoryName = factoryAnnotation.name();
|
||||||
|
int factoryPriority = factoryAnnotation.priority();
|
||||||
|
if (factoryPriority > FactoryPriority.PRIORITY_HIGHEST) {
|
||||||
|
this.factoryPriority = FactoryPriority.PRIORITY_HIGHEST;
|
||||||
|
} else if (factoryPriority < FactoryPriority.PRIORITY_LOWEST) {
|
||||||
|
this.factoryPriority = FactoryPriority.PRIORITY_LOWEST;
|
||||||
|
} else {
|
||||||
|
this.factoryPriority = factoryPriority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Factory 声明的名称.
|
||||||
|
* @return 返回 Factory 名称.
|
||||||
|
*/
|
||||||
|
public String getFactoryName() {
|
||||||
|
return factoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Factory 优先级.
|
||||||
|
* @return 返回 Factory 的优先级.
|
||||||
|
*/
|
||||||
|
public int getFactoryPriority() {
|
||||||
|
return factoryPriority;
|
||||||
|
}
|
||||||
|
}
|
35
ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java
vendored
Normal file
35
ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找不到 Factory 异常.
|
||||||
|
* <p>当尝试获取 {@link CacheStoreFactory} 失败时抛出.
|
||||||
|
* @see CacheStoreFactory
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class NoSuchFactoryException extends Exception {
|
||||||
|
|
||||||
|
public NoSuchFactoryException() {
|
||||||
|
super("Unable to get available factory");
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoSuchFactoryException(Throwable cause) {
|
||||||
|
super("Unable to get available factory", cause);
|
||||||
|
}
|
||||||
|
}
|
42
ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java
vendored
Normal file
42
ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache;
|
||||||
|
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringConverter;
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CacheStoreBuilder
|
||||||
|
*/
|
||||||
|
public class CacheStoreBuilderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCacheStoreTest() {
|
||||||
|
final String identify = "test";
|
||||||
|
final StringConverter<String> converter = new StringToStringConverter();
|
||||||
|
|
||||||
|
Assert.assertNotNull(CacheStoreBuilder.newSingleCacheStore(identify, converter));
|
||||||
|
Assert.assertNotNull(CacheStoreBuilder.newListCacheStore(identify, converter));
|
||||||
|
Assert.assertNotNull(CacheStoreBuilder.newMapCacheStore(identify, converter));
|
||||||
|
Assert.assertNotNull(CacheStoreBuilder.newSetCacheStore(identify, converter));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user