From 3e591e8c26f9db0743fc46621c60d046cdc5f2cc Mon Sep 17 00:00:00 2001 From: LamGC Date: Wed, 23 Sep 2020 08:00:11 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=20Core=20=E6=B7=BB=E5=8A=A0=E5=86=85?= =?UTF-8?q?=E9=83=A8=20CacheStoreBuilder;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Add] CacheStoreBuilder 添加 Builder 以统一使用所有 Factory 创建 CacheStore; [Add] FactoryInfo Factory 注解的信息实体类; [Add] NoSuchFactoryException 当无可用 Factory 时的自定义异常; [Add] CacheStoreBuilderTest 尚不完善的单元测试类, 仅测试有效情况, 因 SPI 机制原因, 不方便编写多个情况的测试用例; --- .../cgj/bot/cache/CacheStoreBuilder.java | 227 ++++++++++++++++++ .../net/lamgc/cgj/bot/cache/FactoryInfo.java | 65 +++++ .../cgj/bot/cache/NoSuchFactoryException.java | 35 +++ .../cgj/bot/cache/CacheStoreBuilderTest.java | 42 ++++ 4 files changed, 369 insertions(+) create mode 100644 ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java create mode 100644 ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java create mode 100644 ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java create mode 100644 ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java new file mode 100644 index 0000000..af82ad7 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java @@ -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 . + */ + +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 构造器. + * + *

这只是个门面类, 最终调用 {@link CacheStoreFactory} 的具体实现类获取 {@link CacheStore} 对象. + *

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 FACTORY_LIST = new LinkedList<>(); + private final static Map FACTORY_INFO_MAP = new Hashtable<>(); + + /** + * 使用 SPI 机制加载所有缓存组件. + * + *

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

由于 ServiceLoader 线程不安全, 所以通过 synchronized 保证其安全性. + * 不通过 static 块进行初始化的原因是因为担心发生异常导致无法继续执行 + * (除非必要, 否则不要使用 static 执行可能会发生异常的代码.). + */ + private synchronized static void loadFactory() { + if (FACTORY_LIST.size() != 0) { + return; + } + final ServiceLoader 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 { + @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 getFactory(Function function) throws NoSuchFactoryException { + if (FACTORY_LIST.size() == 0) { + loadFactory(); + } + Iterator 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 值类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static SingleCacheStore newSingleCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + SingleCacheStore 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 元素类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static ListCacheStore newListCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + ListCacheStore 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 元素类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static SetCacheStore newSetCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + SetCacheStore 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 值类型. + * @return 返回新的存储容器, 与其他容器互不干扰. + * @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出. + */ + public static MapCacheStore newMapCacheStore(String identify, StringConverter converter) { + try { + return getFactory(factory -> { + MapCacheStore 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); + } + } + +} diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java new file mode 100644 index 0000000..5a2c72e --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java @@ -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 . + */ + +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 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; + } +} diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java new file mode 100644 index 0000000..4783d17 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java @@ -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 . + */ + +package net.lamgc.cgj.bot.cache; + +/** + * 找不到 Factory 异常. + *

当尝试获取 {@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); + } +} diff --git a/ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java b/ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java new file mode 100644 index 0000000..f515e52 --- /dev/null +++ b/ContentGrabbingJi-core/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java @@ -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 . + */ + +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 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)); + } + + +}