Java如何实现简单SPI

其他教程   发布日期:2023年07月24日   浏览次数:366

本文小编为大家详细介绍“Java如何实现简单SPI”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何实现简单SPI”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

SPI标注注解

标注提供

  1. SPI
能力接口的注解
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface SPI {
  5. /**
  6. * value
  7. * @return value
  8. */
  9. String value() default "";
  10. }

标准

  1. SPI
实现的注解
  1. @Join
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface Join {
  5. }

SPI核心实现

SPI的一些Class和扩展对象缓存

  1. SPI
实现是一个懒加载的过程,只有当通过
  1. get
方法获取扩展的实例时才会加载扩展,并创建扩展实例,这里我们定义一个集合用于缓存扩展类,扩展对象等,代码如下:
  1. @Slf4j
  2. @SuppressWarnings("all")
  3. public class ExtensionLoader<T> {
  4. /**
  5. * SPI配置扩展的文件位置
  6. * 扩展文件命名格式为 SPI接口的全路径名,如:com.redick.spi.test.TestSPI
  7. */
  8. private static final String DEFAULT_DIRECTORY = "META-INF/log-helper/";
  9. /**
  10. * 扩展接口 {@link Class}
  11. */
  12. private final Class<T> tClass;
  13. /**
  14. * 扩展接口 和 扩展加载器 {@link ExtensionLoader} 的缓存
  15. */
  16. private static final Map<Class<?>, ExtensionLoader<?>> MAP = new ConcurrentHashMap<>();
  17. /**
  18. * 保存 "扩展" 实现的 {@link Class}
  19. */
  20. private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
  21. /**
  22. * "扩展名" 对应的 保存扩展对象的Holder的缓存
  23. */
  24. private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
  25. /**
  26. * 扩展class 和 扩展点的实现对象的缓存
  27. */
  28. private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();
  29. /**
  30. * 扩展点默认的 "名称" 缓存
  31. */
  32. private String cacheDefaultName;
  33. // 省略代码后面介绍
  34. }

获取扩展器ExtensionLoader

  1. public static<T> ExtensionLoader<T> getExtensionLoader(final Class<T> tClass) {
  2. // 参数非空校验
  3. if (null == tClass) {
  4. throw new NullPointerException("tClass is null !");
  5. }
  6. // 参数应该是接口
  7. if (!tClass.isInterface()) {
  8. throw new IllegalArgumentException("tClass :" + tClass + " is not interface !");
  9. }
  10. // 参数要包含@SPI注解
  11. if (!tClass.isAnnotationPresent(SPI.class)) {
  12. throw new IllegalArgumentException("tClass " + tClass + "without @" + SPI.class + " Annotation !");
  13. }
  14. // 从缓存中获取扩展加载器,如果存在直接返回,如果不存在就创建一个扩展加载器并放到缓存中
  15. ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) MAP.get(tClass);
  16. if (null != extensionLoader) {
  17. return extensionLoader;
  18. }
  19. MAP.putIfAbsent(tClass, new ExtensionLoader<>(tClass));
  20. return (ExtensionLoader<T>) MAP.get(tClass);
  21. }

扩展加载器构造方法

  1. public ExtensionLoader(final Class<T> tClass) {
  2. this.tClass = tClass;
  3. }

获取SPI扩展对象

获取SPI扩展对象是懒加载过程,第一次去获取的时候是没有的,会触发从问家中加载资源,通过反射创建对象,并缓存起来。

  1. public T getJoin(String cacheDefaultName) {
  2. // 扩展名 文件中的key
  3. if (StringUtils.isBlank(cacheDefaultName)) {
  4. throw new IllegalArgumentException("join name is null");
  5. }
  6. // 扩展对象存储缓存
  7. Holder<Object> objectHolder = cachedInstances.get(cacheDefaultName);
  8. // 如果扩展对象的存储是空的,创建一个扩展对象存储并缓存
  9. if (null == objectHolder) {
  10. cachedInstances.putIfAbsent(cacheDefaultName, new Holder<>());
  11. objectHolder = cachedInstances.get(cacheDefaultName);
  12. }
  13. // 从扩展对象的存储中获取扩展对象
  14. Object value = objectHolder.getT();
  15. // 如果对象是空的,就触发创建扩展,否则直接返回扩展对象
  16. if (null == value) {
  17. synchronized (cacheDefaultName) {
  18. value = objectHolder.getT();
  19. if (null == value) {
  20. // 创建扩展对象
  21. value = createExtension(cacheDefaultName);
  22. objectHolder.setT(value);
  23. }
  24. }
  25. }
  26. return (T) value;
  27. }

创建扩展对象

反射方式创建扩展对象的实例

  1. private Object createExtension(String cacheDefaultName) {
  2. // 根据扩展名字获取扩展的Class,从Holder中获取 key-value缓存,然后根据名字从Map中获取扩展实现Class
  3. Class<?> aClass = getExtensionClasses().get(cacheDefaultName);
  4. if (null == aClass) {
  5. throw new IllegalArgumentException("extension class is null");
  6. }
  7. Object o = joinInstances.get(aClass);
  8. if (null == o) {
  9. try {
  10. // 创建扩展对象并放到缓存中
  11. joinInstances.putIfAbsent(aClass, aClass.newInstance());
  12. o = joinInstances.get(aClass);
  13. } catch (InstantiationException e) {
  14. e.printStackTrace();
  15. } catch (IllegalAccessException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. return o;
  20. }

从Holder中获取获取扩展实现的Class集合

  1. public Map<String, Class<?>> getExtensionClasses() {
  2. // 扩区SPI扩展实现的缓存,对应的就是扩展文件中的 key - value
  3. Map<String, Class<?>> classes = cachedClasses.getT();
  4. if (null == classes) {
  5. synchronized (cachedClasses) {
  6. classes = cachedClasses.getT();
  7. if (null == classes) {
  8. // 加载扩展
  9. classes = loadExtensionClass();
  10. // 缓存扩展实现集合
  11. cachedClasses.setT(classes);
  12. }
  13. }
  14. }
  15. return classes;
  16. }

加载扩展实现Class

加载扩展实现Class,就是从文件中获取扩展实现的Class,然后缓存起来

  1. public Map<String, Class<?>> loadExtensionClass() {
  2. // 扩展接口tClass,必须包含SPI注解
  3. SPI annotation = tClass.getAnnotation(SPI.class);
  4. if (null != annotation) {
  5. String v = annotation.value();
  6. if (StringUtils.isNotBlank(v)) {
  7. // 如果有默认的扩展实现名,用默认的
  8. cacheDefaultName = v;
  9. }
  10. }
  11. Map<String, Class<?>> classes = new HashMap<>(16);
  12. // 从文件加载
  13. loadDirectory(classes);
  14. return classes;
  15. }
  16. private void loadDirectory(final Map<String, Class<?>> classes) {
  17. // 文件名
  18. String fileName = DEFAULT_DIRECTORY + tClass.getName();
  19. try {
  20. ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
  21. // 读取配置文件
  22. Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName)
  23. : ClassLoader.getSystemResources(fileName);
  24. if (urls != null) {
  25. // 获取所有的配置文件
  26. while (urls.hasMoreElements()) {
  27. URL url = urls.nextElement();
  28. // 加载资源
  29. loadResources(classes, url);
  30. }
  31. }
  32. } catch (IOException e) {
  33. log.error("load directory error {}", fileName, e);
  34. }
  35. }
  36. private void loadResources(Map<String, Class<?>> classes, URL url) {
  37. // 读取文件到Properties,遍历Properties,得到配置文件key(名字)和value(扩展实现的Class)
  38. try (InputStream inputStream = url.openStream()) {
  39. Properties properties = new Properties();
  40. properties.load(inputStream);
  41. properties.forEach((k, v) -> {
  42. // 扩展实现的名字
  43. String name = (String) k;
  44. // 扩展实现的Class的全路径
  45. String classPath = (String) v;
  46. if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
  47. try {
  48. // 加载扩展实现Class,就是想其缓存起来,缓存到集合中
  49. loadClass(classes, name, classPath);
  50. } catch (ClassNotFoundException e) {
  51. log.error("load class not found", e);
  52. }
  53. }
  54. });
  55. } catch (IOException e) {
  56. log.error("load resouces error", e);
  57. }
  58. }
  59. private void loadClass(Map<String, Class<?>> classes, String name, String classPath) throws ClassNotFoundException {
  60. // 反射创建扩展实现的Class
  61. Class<?> subClass = Class.forName(classPath);
  62. // 扩展实现的Class要是扩展接口的实现类
  63. if (!tClass.isAssignableFrom(subClass)) {
  64. throw new IllegalArgumentException("load extension class error " + subClass + " not sub type of " + tClass);
  65. }
  66. // 扩展实现要有Join注解
  67. Join annotation = subClass.getAnnotation(Join.class);
  68. if (null == annotation) {
  69. throw new IllegalArgumentException("load extension class error " + subClass + " without @Join" +
  70. "Annotation");
  71. }
  72. // 缓存扩展实现Class
  73. Class<?> oldClass = classes.get(name);
  74. if (oldClass == null) {
  75. classes.put(name, subClass);
  76. } else if (oldClass != subClass) {
  77. log.error("load extension class error, Duplicate class oldClass is " + oldClass + "subClass is" + subClass);
  78. }
  79. }

存储Holder

  1. public static class Holder<T> {
  2. private volatile T t;
  3. public T getT() {
  4. return t;
  5. }
  6. public void setT(T t) {
  7. this.t = t;
  8. }
  9. }

测试SPI

定义SPI接口

  1. @SPI
  2. public interface TestSPI {
  3. void test();
  4. }

扩展实现1和2

  1. @Join
  2. public class TestSPI1Impl implements TestSPI {
  3. @Override
  4. public void test() {
  5. System.out.println("test1");
  6. }
  7. }
  8. @Join
  9. public class TestSPI2Impl implements TestSPI {
  10. @Override
  11. public void test() {
  12. System.out.println("test2");
  13. }
  14. }

在resources文件夹下创建META-INF/log-helper文件夹,并创建扩展文件

文件名称(接口全路径名):com.redick.spi.test.TestSPI

文件内容

testSPI1=com.redick.spi.test.TestSPI1Impl
testSPI2=com.redick.spi.test.TestSPI2Impl

动态使用测试

  1. public class SpiExtensionFactoryTest {
  2. @Test
  3. public void getExtensionTest() {
  4. TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI1");
  5. testSPI.test();
  6. }
  7. }

测试结果:

test1

  1. public class SpiExtensionFactoryTest {
  2. @Test
  3. public void getExtensionTest() {
  4. TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI2");
  5. testSPI.test();
  6. }
  7. }

测试结果:

test2

以上就是Java如何实现简单SPI的详细内容,更多关于Java如何实现简单SPI的资料请关注九品源码其它相关文章!