[Add][Change] Common 更改 CacheStoreBuilder 为实例模式, 为 CacheStoreFactory 添加 initial 方法;

[Add] CacheStoreFactory 添加 'initial(File)' 方法供缓存组件进行初始化;
[Change] CacheStoreBuilder 将 CacheStoreBuilder 从静态类更改为实例类, 增加对 CacheStoreFactory 的初始化;
[Change] CacheStoreBuilderTest 适配 CacheStoreBuilder 的更改;
This commit is contained in:
LamGC 2020-11-05 00:53:32 +08:00
parent 235c7452b8
commit b7d712da21
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
3 changed files with 97 additions and 19 deletions

View File

@ -20,6 +20,8 @@ package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
/**
* 缓存存储容器构造工厂.
*
@ -28,6 +30,15 @@ import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
*/
public interface CacheStoreFactory {
/**
* Factory 初始化方法.
* <p> Factory 被加载且验证正确后, 将会在第一次获取 CacheStore 前被调用一次(也可能在验证完成后执行).
* <p> 该方法仅调用一次, 之后将不再调用.
* @param dataDirectory 数据存储目录, 可在该目录内存储配置, 或持久化缓存.
* @throws Exception 上层管理类允许捕获任何抛出的异常, 如果 Factory 抛出异常, 将视为失效, 不被使用.
*/
void initial(File dataDirectory) throws Exception;
/**
* 获取一个新的 CacheStore 对象.
* @param identify 缓存标识.

View File

@ -17,11 +17,14 @@
package net.lamgc.cgj.bot.cache;
import com.google.common.base.Throwables;
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.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
@ -37,18 +40,36 @@ import java.util.function.Function;
public class CacheStoreBuilder {
private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilder.class);
private final static List<CacheStoreFactory> FACTORY_LIST = new ArrayList<>();
private final static Map<CacheStoreFactory, FactoryInfo> FACTORY_INFO_MAP = new Hashtable<>();
private final List<CacheStoreFactory> FACTORY_LIST = new ArrayList<>();
private final Map<CacheStoreFactory, FactoryInfo> FACTORY_INFO_MAP = new Hashtable<>();
private final File dataDirectory;
public static CacheStoreBuilder getInstance(File cacheDataDirectory) throws IOException {
if (!cacheDataDirectory.exists() && !cacheDataDirectory.mkdirs()) {
throw new IOException("Data directory creation failed: " + cacheDataDirectory.getAbsolutePath());
} else if (!cacheDataDirectory.isDirectory()) {
throw new IOException("The specified data store path is not a directory: " +
cacheDataDirectory.getAbsolutePath());
} else if (!cacheDataDirectory.canRead() || !cacheDataDirectory.canWrite()) {
throw new IOException("The specified data store directory cannot be read or written.");
}
return new CacheStoreBuilder(cacheDataDirectory);
}
private CacheStoreBuilder(File dataDirectory) {
this.dataDirectory = dataDirectory;
}
/**
* 使用 SPI 机制加载所有缓存组件.
*
* <p>第一次执行时加载, {@link #getFactory(CacheStoreSource, Function)} 调用.
* <p>由于 ServiceLoader 线程不安全, 所以通过 synchronized 保证其安全性.
* 不通过 static 块进行初始化的原因是因为担心发生异常导致无法继续执行
* (除非必要, 否则不要使用 static 执行可能会发生异常的代码.).
* 不通过 块进行初始化的原因是因为担心发生异常导致无法继续执行
* (除非必要, 否则不要使用 执行可能会发生异常的代码.).
*/
private synchronized static void loadFactory() {
private synchronized void loadFactory() {
if (FACTORY_LIST.size() != 0) {
return;
}
@ -67,6 +88,12 @@ public class CacheStoreBuilder {
log.warn("Factory {} 加载失败: {}", factory.getClass().getName(), e.getMessage());
continue;
}
if (!initialFactory(factory, info)) {
log.warn("Factory {} 初始化失败.", info.getFactoryName());
continue;
}
FACTORY_LIST.add(factory);
log.info("Factory {} 已加载(优先级: {}, 实现类: {}).",
info.getFactoryName(),
@ -81,10 +108,26 @@ public class CacheStoreBuilder {
}
}
private boolean initialFactory(CacheStoreFactory factory, FactoryInfo info) {
File factoryDataDirectory = new File(dataDirectory, info.getFactoryName());
if (!factoryDataDirectory.exists() && !factoryDataDirectory.mkdirs()) {
log.warn("Factory {} 数据存储目录创建失败, 可能会影响后续操作. (Path: {})",
info.getFactoryName(),
factoryDataDirectory.getAbsolutePath());
}
try {
factory.initial(factoryDataDirectory);
return true;
} catch (Exception e) {
log.error("Factory {} 初始化失败.\n{}", info.getFactoryName(), Throwables.getStackTraceAsString(e));
return false;
}
}
/**
* 优先级排序器.
*/
private final static class PriorityComparator implements Comparator<CacheStoreFactory> {
private final class PriorityComparator implements Comparator<CacheStoreFactory> {
@Override
public int compare(CacheStoreFactory o1, CacheStoreFactory o2) {
FactoryInfo info1 = Objects.requireNonNull(FACTORY_INFO_MAP.get(o1));
@ -97,7 +140,7 @@ public class CacheStoreBuilder {
* 获取一个当前可用的高优先级 Factory 对象.
* @return 返回可用的高优先级 Factory 对象.
*/
private static <R extends CacheStore<?>> R getFactory(CacheStoreSource storeSource,
private <R extends CacheStore<?>> R getFactory(CacheStoreSource storeSource,
Function<CacheStoreFactory, R> function)
throws NoSuchFactoryException {
if (FACTORY_LIST.size() == 0) {
@ -156,7 +199,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return newSingleCacheStore(null, identify, converter);
}
@ -169,7 +212,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <V> SingleCacheStore<V> newSingleCacheStore(CacheStoreSource storeSource, String identify,
public <V> SingleCacheStore<V> newSingleCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<V> converter) {
try {
return getFactory(storeSource, factory -> {
@ -192,7 +235,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
return newListCacheStore(null, identify, converter);
}
@ -205,7 +248,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <E> ListCacheStore<E> newListCacheStore(CacheStoreSource storeSource, String identify,
public <E> ListCacheStore<E> newListCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<E> converter) {
try {
return getFactory(storeSource, factory -> {
@ -228,7 +271,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
return newSetCacheStore(null, identify, converter);
}
@ -240,7 +283,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <E> SetCacheStore<E> newSetCacheStore(CacheStoreSource storeSource, String identify,
public <E> SetCacheStore<E> newSetCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<E> converter) {
try {
return getFactory(storeSource, factory -> {
@ -263,7 +306,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return newMapCacheStore(null, identify, converter);
}
@ -276,7 +319,7 @@ public class CacheStoreBuilder {
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public static <V> MapCacheStore<V> newMapCacheStore(CacheStoreSource storeSource, String identify,
public <V> MapCacheStore<V> newMapCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<V> converter) {
try {
return getFactory(storeSource, factory -> {

View File

@ -17,6 +17,7 @@
package net.lamgc.cgj.bot.cache;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import net.lamgc.cgj.bot.cache.local.CopyOnWriteArrayListCacheStore;
@ -24,31 +25,54 @@ import net.lamgc.cgj.bot.cache.local.HashSetCacheStore;
import net.lamgc.cgj.bot.cache.redis.RedisMapCacheStore;
import net.lamgc.cgj.bot.cache.redis.RedisSingleCacheStore;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
/**
* @see CacheStoreBuilder
*/
public class CacheStoreBuilderTest {
private final static TemporaryFolder tempDirectory = TemporaryFolder.builder().build();
@BeforeClass
public static void beforeAction() {
try {
tempDirectory.create();
} catch (IOException e) {
Assert.fail(Throwables.getStackTraceAsString(e));
}
}
@Test
public void getCacheStoreTest() {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder;
try {
cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
} catch (IOException e) {
Assert.fail(Throwables.getStackTraceAsString(e));
return;
}
SingleCacheStore<String> singleCacheStore = CacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RedisSingleCacheStore.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = CacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(CopyOnWriteArrayListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = CacheStoreBuilder.newMapCacheStore(CacheStoreSource.REMOTE, identify, converter);
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(RedisMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = CacheStoreBuilder.newSetCacheStore(identify, converter);
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(HashSetCacheStore.class, setCacheStore.getClass());
}