From b7d712da2149ef2f13ad16f67c52acf9bb4f73f0 Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 5 Nov 2020 00:53:32 +0800 Subject: [PATCH] =?UTF-8?q?[Add][Change]=20Common=20=E6=9B=B4=E6=94=B9=20C?= =?UTF-8?q?acheStoreBuilder=20=E4=B8=BA=E5=AE=9E=E4=BE=8B=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F,=20=E4=B8=BA=20CacheStoreFactory=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20initial=20=E6=96=B9=E6=B3=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Add] CacheStoreFactory 添加 'initial(File)' 方法供缓存组件进行初始化; [Change] CacheStoreBuilder 将 CacheStoreBuilder 从静态类更改为实例类, 增加对 CacheStoreFactory 的初始化; [Change] CacheStoreBuilderTest 适配 CacheStoreBuilder 的更改; --- .../cgj/bot/cache/CacheStoreFactory.java | 11 +++ .../cgj/bot/cache/CacheStoreBuilder.java | 73 +++++++++++++++---- .../cgj/bot/cache/CacheStoreBuilderTest.java | 32 +++++++- 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreFactory.java b/ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreFactory.java index a1ec36b..8e6fbb9 100644 --- a/ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreFactory.java +++ b/ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreFactory.java @@ -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 初始化方法. + *

当 Factory 被加载且验证正确后, 将会在第一次获取 CacheStore 前被调用一次(也可能在验证完成后执行). + *

该方法仅调用一次, 之后将不再调用. + * @param dataDirectory 数据存储目录, 可在该目录内存储配置, 或持久化缓存. + * @throws Exception 上层管理类允许捕获任何抛出的异常, 如果 Factory 抛出异常, 将视为失效, 不被使用. + */ + void initial(File dataDirectory) throws Exception; + /** * 获取一个新的 CacheStore 对象. * @param identify 缓存标识. diff --git a/ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java b/ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java index b081995..b7650a5 100644 --- a/ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java +++ b/ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java @@ -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 FACTORY_LIST = new ArrayList<>(); - private final static Map FACTORY_INFO_MAP = new Hashtable<>(); + + private final List FACTORY_LIST = new ArrayList<>(); + private final Map 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 机制加载所有缓存组件. * *

第一次执行时加载, 由 {@link #getFactory(CacheStoreSource, Function)} 调用. *

由于 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 { + private final class PriorityComparator implements Comparator { @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 getFactory(CacheStoreSource storeSource, + private > R getFactory(CacheStoreSource storeSource, Function function) throws NoSuchFactoryException { if (FACTORY_LIST.size() == 0) { @@ -156,7 +199,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static SingleCacheStore newSingleCacheStore(String identify, StringConverter converter) { + public SingleCacheStore newSingleCacheStore(String identify, StringConverter converter) { return newSingleCacheStore(null, identify, converter); } @@ -169,7 +212,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static SingleCacheStore newSingleCacheStore(CacheStoreSource storeSource, String identify, + public SingleCacheStore newSingleCacheStore(CacheStoreSource storeSource, String identify, StringConverter converter) { try { return getFactory(storeSource, factory -> { @@ -192,7 +235,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static ListCacheStore newListCacheStore(String identify, StringConverter converter) { + public ListCacheStore newListCacheStore(String identify, StringConverter converter) { return newListCacheStore(null, identify, converter); } @@ -205,7 +248,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static ListCacheStore newListCacheStore(CacheStoreSource storeSource, String identify, + public ListCacheStore newListCacheStore(CacheStoreSource storeSource, String identify, StringConverter converter) { try { return getFactory(storeSource, factory -> { @@ -228,7 +271,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static SetCacheStore newSetCacheStore(String identify, StringConverter converter) { + public SetCacheStore newSetCacheStore(String identify, StringConverter converter) { return newSetCacheStore(null, identify, converter); } @@ -240,7 +283,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static SetCacheStore newSetCacheStore(CacheStoreSource storeSource, String identify, + public SetCacheStore newSetCacheStore(CacheStoreSource storeSource, String identify, StringConverter converter) { try { return getFactory(storeSource, factory -> { @@ -263,7 +306,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static MapCacheStore newMapCacheStore(String identify, StringConverter converter) { + public MapCacheStore newMapCacheStore(String identify, StringConverter converter) { return newMapCacheStore(null, identify, converter); } @@ -276,7 +319,7 @@ public class CacheStoreBuilder { * @return 返回新的存储容器, 与其他容器互不干扰. * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. */ - public static MapCacheStore newMapCacheStore(CacheStoreSource storeSource, String identify, + public MapCacheStore newMapCacheStore(CacheStoreSource storeSource, String identify, StringConverter converter) { try { return getFactory(storeSource, factory -> { diff --git a/ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java b/ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java index 509efe9..d881b77 100644 --- a/ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java +++ b/ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java @@ -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 converter = new StringToStringConverter(); + CacheStoreBuilder cacheStoreBuilder; + try { + cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot()); + } catch (IOException e) { + Assert.fail(Throwables.getStackTraceAsString(e)); + return; + } - SingleCacheStore singleCacheStore = CacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter); + SingleCacheStore singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter); Assert.assertNotNull(singleCacheStore); Assert.assertEquals(RedisSingleCacheStore.class, singleCacheStore.getClass()); - ListCacheStore listCacheStore = CacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter); + ListCacheStore listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter); Assert.assertNotNull(listCacheStore); Assert.assertEquals(CopyOnWriteArrayListCacheStore.class, listCacheStore.getClass()); - MapCacheStore mapCacheStore = CacheStoreBuilder.newMapCacheStore(CacheStoreSource.REMOTE, identify, converter); + MapCacheStore mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.REMOTE, identify, converter); Assert.assertNotNull(mapCacheStore); Assert.assertEquals(RedisMapCacheStore.class, mapCacheStore.getClass()); - SetCacheStore setCacheStore = CacheStoreBuilder.newSetCacheStore(identify, converter); + SetCacheStore setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter); Assert.assertNotNull(setCacheStore); Assert.assertEquals(HashSetCacheStore.class, setCacheStore.getClass()); }