springboot中责任链模式之简朴应用

打印 上一主题 下一主题

主题 777|帖子 777|积分 2331

一、简朴需求

在CSDN博客主动阅读器-服务端推送技术SSE之简朴应用 一文中,我们实现了个人博客文章的后台推送功能。
初始化推送数据是通过接口来实现的,如今我们盼望实现如下功能优化:

  • 定义多种初始化数据来源,详细而言,有3种方式:①web 接口、②docker映射文件、③本地资源文件
  • 支持初始化数据方式的优先级指定。
  • 不排除将来会添加其他的初始化方式。比方,通过本地接口提交初始化数据。
二、实现过程

下面我们利用责任链模式来实现上述需求。
1、定义接口

  1. import java.util.List;
  2. import com.fly.demo.entity.Article;
  3. /**
  4. * 数据初始化
  5. */
  6. public interface DataInitor
  7. {
  8.     /**
  9.      * 执行初始化
  10.      *
  11.      * @param articles
  12.      * @return 是否成功
  13.      */
  14.     boolean init(List<Article> articles);
  15. }
复制代码
2、定义实现类

1.)web 接口方式初始化类

留意: web 接口方式初始化Webclient不可利用异步,而且需要设置超时时间,避免长时间无相应的情况下导致的无谓的等候。
  1. import java.nio.charset.StandardCharsets;
  2. import java.time.Duration;
  3. import java.util.List;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.core.annotation.Order;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.stereotype.Component;
  8. import org.springframework.util.CollectionUtils;
  9. import org.springframework.web.reactive.function.client.WebClient;
  10. import com.fly.demo.entity.Article;
  11. import com.fly.demo.entity.BlogData;
  12. import lombok.extern.slf4j.Slf4j;
  13. /**
  14. * 通过WebApi接口初始化
  15. */
  16. @Slf4j
  17. @Order(1)
  18. @Component
  19. public class WebApiDataInitor implements DataInitor
  20. {
  21.     @Autowired
  22.     WebClient webClient;
  23.    
  24.     @Override
  25.     public boolean init(List<Article> articles)
  26.     {
  27.         try
  28.         {
  29.             log.info("start init...");
  30.             BlogData blogData = webClient.get()
  31.                 .uri("https://00fly.online/upload/data.json")
  32.                 .acceptCharset(StandardCharsets.UTF_8)
  33.                 .accept(MediaType.APPLICATION_JSON)
  34.                 .retrieve()
  35.                 .bodyToMono(BlogData.class)
  36.                 .timeout(Duration.ofSeconds(10)) // 单独设置超时
  37.                 .block();
  38.             if (blogData != null)
  39.             {
  40.                 articles.addAll(blogData.getData().getList());
  41.             }
  42.             return !CollectionUtils.isEmpty(articles);
  43.         }
  44.         catch (Exception e)
  45.         {
  46.             log.error(e.getMessage(), e);
  47.             return false;
  48.         }
  49.     }
  50. }
复制代码
2.)Docker映射文件初始化

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.nio.charset.StandardCharsets;
  4. import java.util.Arrays;
  5. import java.util.Collections;
  6. import java.util.List;
  7. import java.util.stream.Collectors;
  8. import org.apache.commons.io.IOUtils;
  9. import org.springframework.core.annotation.Order;
  10. import org.springframework.core.io.Resource;
  11. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  12. import org.springframework.stereotype.Component;
  13. import org.springframework.util.CollectionUtils;
  14. import com.fly.core.utils.JsonBeanUtils;
  15. import com.fly.demo.entity.Article;
  16. import com.fly.demo.entity.BlogData;
  17. import lombok.extern.slf4j.Slf4j;
  18. /**
  19. * 通过Docker映射文件初始化
  20. */
  21. @Slf4j
  22. @Order(2)
  23. @Component
  24. public class DockerDataInitor implements DataInitor
  25. {
  26.     PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  27.    
  28.     @Override
  29.     public boolean init(List<Article> articles)
  30.     {
  31.         try
  32.         {
  33.             log.info("start init...");
  34.             Resource[] jsons = resolver.getResources("file:/data/data*.json");
  35.             articles.addAll(Arrays.stream(jsons).map(json -> parseToArticles(json)).flatMap(List::stream).distinct().collect(Collectors.toList()));
  36.             return !CollectionUtils.isEmpty(articles);
  37.         }
  38.         catch (IOException e)
  39.         {
  40.             log.error(e.getMessage(), e);
  41.             return false;
  42.         }
  43.     }
  44.    
  45.     /**
  46.      * 解析Resource为List
  47.      *
  48.      * @param resource
  49.      * @return
  50.      */
  51.     private List<Article> parseToArticles(Resource resource)
  52.     {
  53.         try (InputStream input = resource.getInputStream())
  54.         {
  55.             String jsonData = IOUtils.toString(input, StandardCharsets.UTF_8);
  56.             return JsonBeanUtils.jsonToBean(jsonData, BlogData.class, true).getData().getList();
  57.         }
  58.         catch (IOException e)
  59.         {
  60.             log.error(e.getMessage(), e);
  61.             return Collections.emptyList();
  62.         }
  63.     }
  64. }
复制代码
3.)通过资源文件初始化

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.nio.charset.StandardCharsets;
  4. import java.util.Arrays;
  5. import java.util.Collections;
  6. import java.util.List;
  7. import java.util.stream.Collectors;
  8. import org.apache.commons.io.IOUtils;
  9. import org.springframework.core.annotation.Order;
  10. import org.springframework.core.io.Resource;
  11. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  12. import org.springframework.stereotype.Component;
  13. import org.springframework.util.CollectionUtils;
  14. import com.fly.core.utils.JsonBeanUtils;
  15. import com.fly.demo.entity.Article;
  16. import com.fly.demo.entity.BlogData;
  17. import lombok.extern.slf4j.Slf4j;
  18. /**
  19. * 通过资源文件初始化
  20. */
  21. @Slf4j
  22. @Order(3)
  23. @Component
  24. public class ResourceDataInitor implements DataInitor
  25. {
  26.     PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  27.    
  28.     @Override
  29.     public boolean init(List<Article> articles)
  30.     {
  31.         try
  32.         {
  33.             log.info("start init...");
  34.             Resource[] jsons = resolver.getResources("classpath:*.json");
  35.             articles.addAll(Arrays.stream(jsons).map(json -> parseToArticles(json)).flatMap(List::stream).distinct().collect(Collectors.toList()));
  36.             return !CollectionUtils.isEmpty(articles);
  37.         }
  38.         catch (IOException e)
  39.         {
  40.             log.error(e.getMessage(), e);
  41.             return false;
  42.         }
  43.     }
  44.    
  45.     /**
  46.      * 解析Resource为List
  47.      *
  48.      * @param resource
  49.      * @return
  50.      */
  51.     private List<Article> parseToArticles(Resource resource)
  52.     {
  53.         try (InputStream input = resource.getInputStream())
  54.         {
  55.             String jsonData = IOUtils.toString(input, StandardCharsets.UTF_8);
  56.             return JsonBeanUtils.jsonToBean(jsonData, BlogData.class, true).getData().getList();
  57.         }
  58.         catch (IOException e)
  59.         {
  60.             log.error(e.getMessage(), e);
  61.             return Collections.emptyList();
  62.         }
  63.     }
  64. }
复制代码
3、编写初始化逻辑

  1. import java.io.IOException;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.atomic.AtomicInteger;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cache.annotation.Cacheable;
  7. import org.springframework.stereotype.Service;
  8. import com.fly.demo.entity.Article;
  9. import com.fly.demo.service.init.DataInitor;
  10. import lombok.extern.slf4j.Slf4j;
  11. /**
  12. * DataService
  13. */
  14. @Slf4j
  15. @Service
  16. public class DataService
  17. {
  18.     @Autowired
  19.     List<DataInitor> dataInitors;
  20.    
  21.     /**
  22.      * 获取url数据列表
  23.      *
  24.      * @return
  25.      * @throws IOException
  26.      */
  27.     @Cacheable(cacheNames = "data", key = "'articles'", sync = true)
  28.     public List<Article> getArticles()
  29.     {
  30.         AtomicInteger count = new AtomicInteger();
  31.         dataInitors.stream().forEach(d -> log.info("{}. {}", count.incrementAndGet(), d));
  32.         
  33.         // 串行流,有一个DataInitor执行init成功就返回
  34.         List<Article> articles = new ArrayList<>();
  35.         dataInitors.stream()
  36.             .peek(d -> log.info("{}", d.getClass().getName())) // debug
  37.             .anyMatch(d -> d.init(articles));
  38.         log.info("############## articles.size: {} ", articles.size());
  39.         return articles;
  40.     }
  41. }
复制代码
三、单位测试

为了方便演示,我们编写了单位测试代码
1、单位测试代码

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.concurrent.atomic.AtomicInteger;
  4. import org.junit.jupiter.api.BeforeEach;
  5. import org.junit.jupiter.api.Test;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
  9. import com.fly.demo.entity.Article;
  10. import com.fly.demo.service.init.DataInitor;
  11. import lombok.extern.slf4j.Slf4j;
  12. @Slf4j
  13. @SpringBootTest(webEnvironment = WebEnvironment.NONE)
  14. public class DataInitTest
  15. {
  16.     @Autowired
  17.     List<DataInitor> dataInitors;
  18.    
  19.     @BeforeEach
  20.     public void before()
  21.     {
  22.         AtomicInteger count = new AtomicInteger();
  23.         dataInitors.stream().forEach(d -> log.info("{}. {}", count.incrementAndGet(), d));
  24.     }
  25.    
  26.     @Test
  27.     public void testStream()
  28.     {
  29.         // lambda写法, 串行流至少有一个DataInitor执行init成功
  30.         List<Article> articles = new ArrayList<>();
  31.         dataInitors.stream()
  32.             .peek(d -> log.info("{}", d.getClass().getName())) // debug
  33.             .anyMatch(d -> d.init(articles));
  34.         log.info("############## articles.size: {} ", articles.size());
  35.     }
  36.    
  37.     @Test
  38.     public void testCommon()
  39.     {
  40.         // 传统写法
  41.         List<Article> articles = new ArrayList<>();
  42.         for (DataInitor dataInitor : dataInitors)
  43.         {
  44.             log.info("{}", dataInitor.getClass().getName());
  45.             if (dataInitor.init(articles))
  46.             {
  47.                 log.info("############## articles.size: {} ", articles.size());
  48.                 return;
  49.             }
  50.         }
  51.     }
  52. }
复制代码
2、运行结果

  1. .   ____          _            __ _ _
  2. /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
  3. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  4. \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  5.   '  |____| .__|_| |_|_| |_\__, | / / / /
  6. =========|_|==============|___/=/_/_/_/
  7. :: Spring Boot ::        (v2.2.4.RELEASE)
  8. 2024-12-22 13:04:16.155  INFO 3144 --- [           main] c.f.DataInitTest                         : Starting DataInitTest on 7t9lppye5cj7lud with PID 3144 (started by 00fly in D:\Gitcode\csdn-reader)
  9. 2024-12-22 13:04:16.162  INFO 3144 --- [           main] c.f.DataInitTest                         : The following profiles are active: dev
  10. 2024-12-22 13:04:18.201  INFO 3144 --- [           main] c.f.c.u.SpringContextUtils               : ###### execute setApplicationContext ######
  11. 2024-12-22 13:04:19.535  INFO 3144 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
  12. 2024-12-22 13:04:19.663  INFO 3144 --- [           main] c.f.DataInitTest                         : Started DataInitTest in 4.551 seconds (JVM running for 10.165)
  13. 2024-12-22 13:04:20.366  INFO 3144 --- [           main] c.f.DataInitTest                         : 1. com.fly.demo.service.init.WebApiDataInitor@2881ad47
  14. 2024-12-22 13:04:20.367  INFO 3144 --- [           main] c.f.DataInitTest                         : 2. com.fly.demo.service.init.DockerDataInitor@37fdfb05
  15. 2024-12-22 13:04:20.367  INFO 3144 --- [           main] c.f.DataInitTest                         : 3. com.fly.demo.service.init.ResourceDataInitor@5e39850
  16. 2024-12-22 13:04:20.374  INFO 3144 --- [           main] c.f.DataInitTest                         : com.fly.demo.service.init.WebApiDataInitor
  17. 2024-12-22 13:04:20.374  INFO 3144 --- [           main] c.f.d.s.i.WebApiDataInitor               : start init...
  18. 2024-12-22 13:04:24.000  INFO 3144 --- [           main] c.f.DataInitTest                         : ############## articles.size: 126
  19. 2024-12-22 13:04:26.082  INFO 3144 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskScheduler          : Shutting down ExecutorService 'taskScheduler'
复制代码
3、如何控制优先级

细心的同鞋,已经发现了在我们的实现类中利用了@Order 注解,仔细关心上面的日记输出,我们发现order的取值会影响 List<DataInitor> dataInitors的实现类的排列顺序,假如我们需要把Docker映射文件初始化优先级提升,只需要把order改小,改为0或-1均可,大家可以动手尝试!
四、源码放送

https://gitcode.com/00fly/csdn-reader

有任何问题和发起,都可以向我提问讨论,大家一起进步,谢谢!
-over-

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

知者何南

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表