当前位置: 首页 > article >正文

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

文章目录

  • 一、简单需求
  • 二、实现过程
    • 1、定义接口
    • 2、定义实现类
      • 1.)web 接口方式初始化类
      • 2.)Docker映射文件初始化
      • 3.)通过资源文件初始化
    • 3、编写初始化逻辑
  • 三、单元测试
    • 1、单元测试代码
    • 2、运行结果
    • 3、如何控制优先级
  • 四、源码放送

一、简单需求

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

  1. 定义多种初始化数据来源,具体而言,有3种方式:①web 接口、②docker映射文件、③本地资源文件
  2. 支持初始化数据方式的优先级指定。
  3. 不排除未来会添加其他的初始化方式。例如,通过本地接口提交初始化数据。

二、实现过程

下面我们使用责任链模式来实现上述需求。

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-


http://www.kler.cn/a/447814.html

相关文章:

  • 使用 AI 辅助开发一个开源 IP 信息查询工具:一
  • halcon单相机+机器人*眼在手外标定心得
  • AI的进阶之路:从机器学习到深度学习的演变(三)
  • CTF学习24.12.21[隐写术进阶]
  • 探索 Python编程 调试案例:计算小程序中修复偶数的bug
  • 【HarmonyOS之旅】DevEco Studio的安装与环境配置
  • 《开启微服务之旅:Spring Boot Web开发》(一)
  • Numpy数组索引,切片
  • 2025年西安市科技创新奖励补贴政策一览
  • Android10 rk3399 隐藏截屏功能
  • ISO/IEC 25010:2023 系统和软件的质量模型(产品质量模型)
  • 第二十六周学习周报
  • c语言图书信息管理系统源码
  • YOLOv8改进,YOLOv8引入Hyper-YOLO的MANet混合聚合网络+HyperC2Net网络
  • AI图像生成利器:Stable Diffusion 3.5本地运行与远程出图操作流程
  • Nginx - 负载均衡及其配置(Balance)
  • SVM理论推导
  • NLP自然语言学习路径图
  • MAC地址和IP地址的区别
  • 【HarmonyOs学习日志(14)】计算机网络之域名系统DNS
  • 【Pandas】pandas Series size
  • mysql,数据库数据备份
  • [Unity Shader]【游戏开发】【图形渲染】 Shader数学基础5-方阵、单位矩阵和转置矩阵
  • 地址栏输入URL浏览器会发生什么?
  • 有关异步场景的 10 大 Spring Boot 面试问题
  • CentOS 7 安装、测试和部署FastDFS