一、简朴需求
在CSDN博客主动阅读器-服务端推送技术SSE之简朴应用 一文中,我们实现了个人博客文章的后台推送功能。
初始化推送数据是通过接口来实现的,如今我们盼望实现如下功能优化:
- 定义多种初始化数据来源,详细而言,有3种方式:①web 接口、②docker映射文件、③本地资源文件
- 支持初始化数据方式的优先级指定。
- 不排除将来会添加其他的初始化方式。比方,通过本地接口提交初始化数据。
二、实现过程
下面我们利用责任链模式来实现上述需求。
1、定义接口
- import java.util.List;
- import com.fly.demo.entity.Article;
- /**
- * 数据初始化
- */
- public interface DataInitor
- {
- /**
- * 执行初始化
- *
- * @param articles
- * @return 是否成功
- */
- boolean init(List<Article> articles);
- }
复制代码 2、定义实现类
1.)web 接口方式初始化类
留意: web 接口方式初始化Webclient不可利用异步,而且需要设置超时时间,避免长时间无相应的情况下导致的无谓的等候。
- import java.nio.charset.StandardCharsets;
- import java.time.Duration;
- import java.util.List;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.core.annotation.Order;
- import org.springframework.http.MediaType;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
- import org.springframework.web.reactive.function.client.WebClient;
- import com.fly.demo.entity.Article;
- import com.fly.demo.entity.BlogData;
- import lombok.extern.slf4j.Slf4j;
- /**
- * 通过WebApi接口初始化
- */
- @Slf4j
- @Order(1)
- @Component
- public class WebApiDataInitor implements DataInitor
- {
- @Autowired
- WebClient webClient;
-
- @Override
- public boolean init(List<Article> articles)
- {
- try
- {
- log.info("start init...");
- BlogData blogData = webClient.get()
- .uri("https://00fly.online/upload/data.json")
- .acceptCharset(StandardCharsets.UTF_8)
- .accept(MediaType.APPLICATION_JSON)
- .retrieve()
- .bodyToMono(BlogData.class)
- .timeout(Duration.ofSeconds(10)) // 单独设置超时
- .block();
- if (blogData != null)
- {
- articles.addAll(blogData.getData().getList());
- }
- return !CollectionUtils.isEmpty(articles);
- }
- catch (Exception e)
- {
- log.error(e.getMessage(), e);
- return false;
- }
- }
- }
复制代码 2.)Docker映射文件初始化
- import java.io.IOException;
- import java.io.InputStream;
- import java.nio.charset.StandardCharsets;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.List;
- import java.util.stream.Collectors;
- import org.apache.commons.io.IOUtils;
- import org.springframework.core.annotation.Order;
- import org.springframework.core.io.Resource;
- import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
- import com.fly.core.utils.JsonBeanUtils;
- import com.fly.demo.entity.Article;
- import com.fly.demo.entity.BlogData;
- import lombok.extern.slf4j.Slf4j;
- /**
- * 通过Docker映射文件初始化
- */
- @Slf4j
- @Order(2)
- @Component
- public class DockerDataInitor implements DataInitor
- {
- PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
-
- @Override
- public boolean init(List<Article> articles)
- {
- try
- {
- log.info("start init...");
- Resource[] jsons = resolver.getResources("file:/data/data*.json");
- articles.addAll(Arrays.stream(jsons).map(json -> parseToArticles(json)).flatMap(List::stream).distinct().collect(Collectors.toList()));
- return !CollectionUtils.isEmpty(articles);
- }
- catch (IOException e)
- {
- log.error(e.getMessage(), e);
- return false;
- }
- }
-
- /**
- * 解析Resource为List
- *
- * @param resource
- * @return
- */
- private List<Article> parseToArticles(Resource resource)
- {
- try (InputStream input = resource.getInputStream())
- {
- String jsonData = IOUtils.toString(input, StandardCharsets.UTF_8);
- return JsonBeanUtils.jsonToBean(jsonData, BlogData.class, true).getData().getList();
- }
- catch (IOException e)
- {
- log.error(e.getMessage(), e);
- return Collections.emptyList();
- }
- }
- }
复制代码 3.)通过资源文件初始化
- import java.io.IOException;
- import java.io.InputStream;
- import java.nio.charset.StandardCharsets;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.List;
- import java.util.stream.Collectors;
- import org.apache.commons.io.IOUtils;
- import org.springframework.core.annotation.Order;
- import org.springframework.core.io.Resource;
- import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
- import com.fly.core.utils.JsonBeanUtils;
- import com.fly.demo.entity.Article;
- import com.fly.demo.entity.BlogData;
- import lombok.extern.slf4j.Slf4j;
- /**
- * 通过资源文件初始化
- */
- @Slf4j
- @Order(3)
- @Component
- public class ResourceDataInitor implements DataInitor
- {
- PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
-
- @Override
- public boolean init(List<Article> articles)
- {
- try
- {
- log.info("start init...");
- Resource[] jsons = resolver.getResources("classpath:*.json");
- articles.addAll(Arrays.stream(jsons).map(json -> parseToArticles(json)).flatMap(List::stream).distinct().collect(Collectors.toList()));
- return !CollectionUtils.isEmpty(articles);
- }
- catch (IOException e)
- {
- log.error(e.getMessage(), e);
- return false;
- }
- }
-
- /**
- * 解析Resource为List
- *
- * @param resource
- * @return
- */
- private List<Article> parseToArticles(Resource resource)
- {
- try (InputStream input = resource.getInputStream())
- {
- String jsonData = IOUtils.toString(input, StandardCharsets.UTF_8);
- return JsonBeanUtils.jsonToBean(jsonData, BlogData.class, true).getData().getList();
- }
- catch (IOException e)
- {
- log.error(e.getMessage(), e);
- return Collections.emptyList();
- }
- }
- }
复制代码 3、编写初始化逻辑
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.atomic.AtomicInteger;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.cache.annotation.Cacheable;
- import org.springframework.stereotype.Service;
- import com.fly.demo.entity.Article;
- import com.fly.demo.service.init.DataInitor;
- import lombok.extern.slf4j.Slf4j;
- /**
- * DataService
- */
- @Slf4j
- @Service
- public class DataService
- {
- @Autowired
- List<DataInitor> dataInitors;
-
- /**
- * 获取url数据列表
- *
- * @return
- * @throws IOException
- */
- @Cacheable(cacheNames = "data", key = "'articles'", sync = true)
- public List<Article> getArticles()
- {
- AtomicInteger count = new AtomicInteger();
- dataInitors.stream().forEach(d -> log.info("{}. {}", count.incrementAndGet(), d));
-
- // 串行流,有一个DataInitor执行init成功就返回
- List<Article> articles = new ArrayList<>();
- dataInitors.stream()
- .peek(d -> log.info("{}", d.getClass().getName())) // debug
- .anyMatch(d -> d.init(articles));
- log.info("############## articles.size: {} ", articles.size());
- return articles;
- }
- }
复制代码 三、单位测试
为了方便演示,我们编写了单位测试代码
1、单位测试代码
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.atomic.AtomicInteger;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
- import com.fly.demo.entity.Article;
- import com.fly.demo.service.init.DataInitor;
- import lombok.extern.slf4j.Slf4j;
- @Slf4j
- @SpringBootTest(webEnvironment = WebEnvironment.NONE)
- public class DataInitTest
- {
- @Autowired
- List<DataInitor> dataInitors;
-
- @BeforeEach
- public void before()
- {
- AtomicInteger count = new AtomicInteger();
- dataInitors.stream().forEach(d -> log.info("{}. {}", count.incrementAndGet(), d));
- }
-
- @Test
- public void testStream()
- {
- // lambda写法, 串行流至少有一个DataInitor执行init成功
- List<Article> articles = new ArrayList<>();
- dataInitors.stream()
- .peek(d -> log.info("{}", d.getClass().getName())) // debug
- .anyMatch(d -> d.init(articles));
- log.info("############## articles.size: {} ", articles.size());
- }
-
- @Test
- public void testCommon()
- {
- // 传统写法
- List<Article> articles = new ArrayList<>();
- for (DataInitor dataInitor : dataInitors)
- {
- log.info("{}", dataInitor.getClass().getName());
- if (dataInitor.init(articles))
- {
- log.info("############## articles.size: {} ", articles.size());
- return;
- }
- }
- }
- }
复制代码 2、运行结果
- . ____ _ __ _ _
- /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
- ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
- \\/ ___)| |_)| | | | | || (_| | ) ) ) )
- ' |____| .__|_| |_|_| |_\__, | / / / /
- =========|_|==============|___/=/_/_/_/
- :: Spring Boot :: (v2.2.4.RELEASE)
- 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)
- 2024-12-22 13:04:16.162 INFO 3144 --- [ main] c.f.DataInitTest : The following profiles are active: dev
- 2024-12-22 13:04:18.201 INFO 3144 --- [ main] c.f.c.u.SpringContextUtils : ###### execute setApplicationContext ######
- 2024-12-22 13:04:19.535 INFO 3144 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
- 2024-12-22 13:04:19.663 INFO 3144 --- [ main] c.f.DataInitTest : Started DataInitTest in 4.551 seconds (JVM running for 10.165)
- 2024-12-22 13:04:20.366 INFO 3144 --- [ main] c.f.DataInitTest : 1. com.fly.demo.service.init.WebApiDataInitor@2881ad47
- 2024-12-22 13:04:20.367 INFO 3144 --- [ main] c.f.DataInitTest : 2. com.fly.demo.service.init.DockerDataInitor@37fdfb05
- 2024-12-22 13:04:20.367 INFO 3144 --- [ main] c.f.DataInitTest : 3. com.fly.demo.service.init.ResourceDataInitor@5e39850
- 2024-12-22 13:04:20.374 INFO 3144 --- [ main] c.f.DataInitTest : com.fly.demo.service.init.WebApiDataInitor
- 2024-12-22 13:04:20.374 INFO 3144 --- [ main] c.f.d.s.i.WebApiDataInitor : start init...
- 2024-12-22 13:04:24.000 INFO 3144 --- [ main] c.f.DataInitTest : ############## articles.size: 126
- 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企服之家,中国第一个企服评测及商务社交产业平台。 |