你了解PostProcessor机制吗?

news2025/1/11 5:40:41

Spring框架对于后置处理器的最佳实践

PostProcessor译为后置处理器,大多数开发人员都使用过springboot对后置处理器的实例级别实践,也就是BeanPostProcessor接口。其实spring还提供了两种容器级别的实践:BeanDefinitionRegistryPostProcessor与BeanFactoryPostProcessor接口。这里不过多赘述spring实践的具体用法,简单的说明beanPostprocessor接口扩展执行的时机:重写的handleBefore方法执行在bean初始化之前,属性填充之后,而handleAfter则执行在bean的初始化结束之后,具体的应用场景也有很多,比如修改bean的个性化配置,亦或者修改某一类bean实例化配置。

后置处理器机制能带来什么

原始的mvc结构的代码是java开发的流行层级,那么随着用户需求的更变与增加,定制化越来越常用,我们不断地在service组件中增加if、或者是增加service组件数量,亦或是使用aop做前后置处理(但是我有一个习惯,绝不在aop方法上做耗时操作)。那么后期随着项目时间的增加,很容易出现某个组件代码成为垃圾堆,不仅读起来麻烦,修改起来更麻烦,有可能修改了一个无关紧要的地方都会影响到我们的主流程。

所以这里笔者给读者们带来一种解决方案,后置处理器机制的实现,也可以叫它扩展与埋点思想的实现,这是一种编程方法,开闭原则的一种实践。

实践postprocessor机制过程

接口依赖流程图:

 

跟随笔者一起研究整个demo实现过程中的开发方法与postprocessor机制使用的巧妙。

需要区分的是postprocessor机制属于一种靠近容器级别的切面实现,并不是aspectJ那种方法级别的切面实现,postprocessor可以做到预留扩展(也叫做埋点,等待后续使用,或者通过这个点拿到我们想拿到的信息对象,比如applicationContextAware感知扩展,可以在这个埋点的set方法里拿到容器的applicationContext对象的引用从而获取它的信息),且可以使得各种实现扩展的组件按照某种顺序依次处理,而不是像aspectJ一样只能单切,一不小心就会使得代码冗余,逻辑复杂化。

1、定义一个基础扩展接口BasePostProcessor<T>

public interface BasePostProcessor<T>{
    default  boolean handleBefore(PostContext<T> postContext){
        return true;
    }
    default  boolean handleAfter(PostContext<T> postContext){
        return true;
    }
    default int getLevel(){
        return 0;
    }
}

它包含前置处理。后置处理,处理优先级三个方法,需要注意的是这里的优先级我们可以在其余的处理组件上重写,以做到后续依次执行前置方法,后置方法的排序依据。

这里也充分体现java8之后接口默认方法可以增加方法体的特性,有兴趣的小伙伴可以去查询java8的新变动。

2、定义一个识别BasePostProcessor扩展接口并按照优先级依次执行前后置方法的驱动类

public class PostProcessorExecute<T> {
    private Class<BasePostProcessor> initPostProcessor;

    public static <T> PostProcessorExecute getInstance(Class<T> serviceClazz) {
        PostProcessorExecute postProcessorExecute = new PostProcessorExecute();
        postProcessorExecute.initPostProcessor = serviceClazz;
        return postProcessorExecute;
    }

    public Boolean handleBefore(PostContext<T> postContext) {
        List<? extends BasePostProcessor> list = ApplicationContextUtil.getBeanListByType(initPostProcessor);
        if (CollectionUtils.isEmpty(list)) {
            return true;
        }
        list.stream()
                .sorted(Comparator.comparing(BasePostProcessor::getLevel))
                .forEach(e -> e.handleBefore(postContext));
        return false;
    }

    public void handleAfter(PostContext<T> postContext) {
        List<? extends BasePostProcessor> list = ApplicationContextUtil.getBeanListByType(initPostProcessor);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.stream()
                .sorted(Comparator.comparing(BasePostProcessor::getLevel, Comparator.reverseOrder()))
                .forEach(e -> e.handleAfter(postContext));
    }

}

笔者将详细说明以上驱动类的代码逻辑:

private Class<BasePostProcessor> initPostProcessor;

public static <T> PostProcessorExecute getInstance(Class<T> serviceClazz) {
        PostProcessorExecute postProcessorExecute = new PostProcessorExecute();
        postProcessorExecute.initPostProcessor = serviceClazz;
        return postProcessorExecute;
  }

定义一个扩展类型的类型变量initPostProcessor,利用getInstance方法获取驱动类实例时,先利用传入的类型变量覆盖我们的initPostProcessor变量;

List<? extends BasePostProcessor> list = ApplicationContextUtil.getBeanListByType(initPostProcessor);

if (CollectionUtils.isEmpty(list)) {
            return true;
   }

获取所有BasePostProcessor类型的bean(这里的bean其实就是我们自定义增加的非通用service外的定制或可复用service),如果为null则立刻返回true,进入主流程;

list.stream()
          .sorted(Comparator.comparing(BasePostProcessor::getLevel))
          .forEach(e -> e.handleBefore(postContext));
return false;

根据每一个service复写的优先级进行升序排序并依次执行它们的前置处理方法,这里需要注意优先级越高越靠近我们的主流程(也就是业务内核),之后返回false,主流程可根据接收的布尔值进行判断处理后续逻辑;

list.stream()
           .sorted(Comparator.comparing(BasePostProcessor::getLevel, Comparator.reverseOrder()))
           .forEach(e -> e.handleAfter(postContext));

需要注意的点是我们定义过优先级高的靠近主流程,那么后置处理时一定是倒序排序。 

3、定义业务承载实体

@Data
public class Text {
    private String textCode;
    private String textTitle;
    private String textCreat;
    private Integer textAuthLevel;
    private String textInfo;
    private LocalDateTime optionTime;
}

4、定义承载实体类型定制的扩展接口,注意该接口是埋入的通用扩展(Text处理流程的主要扩展类型)

public interface TextPostProcessor extends BasePostProcessor<Text> {
}

直接规定泛型的类型,便于规定实现它的所有service组件都能在重写方法时直接获取Text类型。

5、定义承载实体类的包装类型(可能后续会传入其余数据,可在此类型添加接收)

public class  PostContext<T> {
    private T data;
    public T getData(){
        return this.data;
    }
    public void setData(T data){
        this.data = data;
    }
}

这里重写的set与get方法是为了便捷后续对data的覆盖

6、编写主流程service中的getKetText方法

@Service("renderService")
@Slf4j
public class RenderServiceImpl implements RenderService{
    
    @Override
    public String getKeyContext(Text text) {
        log.info("---------renderService------------>");
        PostContext<Text> postContext = new PostContext<>();
        postContext.setData(text);
        PostProcessorExecute postProcessorExecute = PostProcessorExecute.getInstance(TextPostProcessor.class);
        try {
            Boolean handleBefore = postProcessorExecute.handleBefore(postContext);
            if (!handleBefore) {
                //返回已经经过前置处理过的文本内容
                return text.getTextInfo();
            }
            //下面就是未经过文本处理的内容,非定制需求走通用返回
            postProcessorExecute.handleAfter(postContext);
        }catch (RuntimeException e){
            e.printStackTrace();
            return null;
        }
        return text.getTextInfo();
    }
}

从这个方法我们可以看出通用流程被保护(判断驱动类前置处理的返回值不同采取不同的行动)

7、新增需求1:现在需要根据用户文本的校验等级决定主流程处理或者不处理该文本

方案一:直接在主流程加入if(text.getTextAuthLevel() > 校验等级值)......等代码。

方案二:新增扩展AuthLevelService组件处理校验等级业务(如下代码)

@Service
@Slf4j
public class AuthServiceImpl implements TextPostProcessor {
    
    @Override
    public int getLevel() {
        return Integer.MIN_VALUE;
    }

    @Override
    public boolean handleBefore(PostContext<Text> postContext) {
        log.info("---------authService------------>");
        Text data = postContext.getData();
        if (data.getTextAuthLevel() < 1){
            throw new RuntimeException("权限校验失败");
        }
        return true;
    }
}

这里将它的优先级定义为最小的原因是,业务的处理流程中  等级的校验操作  一定是最早的。

8、新增需求2:现在需要给文本创建公司H  新增文本渲染在页面上的变大处理

方案一:直接在主流程加入代码 if(text.getTextCreat() == "H").....等代码

方案二:新增扩展KeySetService组件处理H公司的定制文本处理需求(如下代码)

@Service
@Slf4j
public class KeySetServiceImpl implements TextPostProcessor {
    @Override
    public boolean handleBefore(PostContext<Text> postContext) {
        log.info("---------keySetService------------>");
        Text data = postContext.getData();
        //推荐不要使用魔法值
        if ("H".equals(data.getTextCreat())){
            String textInfo = data.getTextInfo();
            if (StringUtils.isEmpty(textInfo)){
                throw new RuntimeException("文本读取为空文本");
            }
            textInfo = "<h1>"+textInfo+"</h1>";
            data.setTextInfo(textInfo);
            postContext.setData(data);
        }
        return true;
    }

    @Override
    public int getLevel() {
        return Integer.MAX_VALUE;
    }
}

9、附上获取容器中某一类型的Bean链表的组件类

@Component
@Lazy(value = false)
public class ApplicationContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.applicationContext = applicationContext;
    }

    public static <T> List<T> getBeanListByType(Class<T> beanType) {
        Map<String, T> beansOfType = applicationContext.getBeansOfType(beanType);
        if (CollectionUtils.isEmpty(beansOfType)) {
            return null;
        }
        return new ArrayList<>(beansOfType.values());
    }

}

我们可以深入思考一下:

假设H公司后续又提出了不同的文本处理需求,采用8中的方案1的话我们就会无穷无尽的陷入if-else中,代码越来越长,代码垃圾堆就出现了。

假设后续还要接入其他定制业务?假设还需要对文本做过滤不健康内容呢?

PostProcessor机制只是一种设计上的优化,它还有很多可以让我们深省自己开发时遇到的设计上问题之思考。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/457694.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

今天试了试chatgpt

今天试了试chatgpt&#xff0c;真是服了 arcade&#xff1f; Arcade是一个Python游戏开发库&#xff0c;它提供了一系列的工具和函数&#xff0c;可以帮助开发者快速地创建2D游戏。以下是Arcade的一些特点&#xff1a; 简单易用&#xff1a;Arcade提供了简单易用的API&#x…

egg3.0连接egg-mongoose插入一条数据、插入多条数据

插入一条数据 app/router.js use strict;/*** param {Egg.Application} app - egg application*/ module.exports app > {const { router, controller } app;router.get(/, controller.home.index);router.get(/role, controller.role.index);router.post(/role/add, co…

【ChatGPT】如何用十分钟部署一个属于自己的chatgpt网站

&#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是Zeeland&#xff0c;全栈领域优质创作者。&#x1f4dd; CSDN主页&#xff1a;Zeeland&#x1f525;&#x1f4e3; 我的博客&#xff1a;Zeeland&#x1f4da; Github主页: Undertone0809 (Zeeland) (github.com)&…

【两个月算法速成】day03-链表

目录 203. 移除链表元素 题目链接 思路 代码 206. 反转链表 题目链接 思路 代码 总结 203. 移除链表元素 题目链接 力扣 思路 如下图所示就是移除链表的过程 但是值得注意的是&#xff0c;移除头节点和其他位置的节点是不一样的&#xff0c;以为头结点前面没有节点。…

每日学术速递4.24

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Collaborative Diffusion for Multi-Modal Face Generation and Editing(CVPR 2023) 标题&#xff1a;多模态人脸生成和编辑的协同扩散 作者&#xff1a;Ziqi Huang, Kelvin C.K. …

Vue3进阶使用详解(node.js、Vue3路由基础项目、axios的使用详细(实现数据分页---前后端分离)、axios加载失败)

Vue3进阶使用详解(node.js、Vue3路由基础项目、axios的使用详细(实现数据分页—前后端分离)、axios加载失败) Vue cli CLI是Commond-Line Interface&#xff0c;翻译为命令界面&#xff0c;又称脚手架。VueCLI是一个官方发布vue.js项目脚手架。使用VueCLI可以快速搭建vue开发…

【IAR工程】STM8S基于ST标准库读取DHT11数据

【IAR工程】STM8S基于ST标准库读取DHT11数据 ✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。&…

HTTP协议 GET和POST区别 请求响应 Fiddler postman ajax

&#x1f496; 欢迎来阅读子豪的博客&#xff08;JavaEE篇 &#x1f934;&#xff09; &#x1f449; 有宝贵的意见或建议可以在留言区留言 &#x1f4bb; 欢迎 素质三连 点赞 关注 收藏 &#x1f9d1;‍&#x1f680;码云仓库&#xff1a;补集王子的代码仓库 不要偷走我小火…

Mac下nvm安装使用

​欢迎光临我的博客查看最新文章: https://river106.cn 1、简介 nvm 是 Mac 下的 node.js 管理工具。可以通过 nvm 安装和切换不同版本的 node.js。 官网&#xff1a;https://nvm.uihtm.com/ github&#xff1a;https://github.com/nvm-sh/nvm 2、安装 curl -o- https://raw…

移动端适配rem方案

做移动端的适配我们就是要考虑&#xff0c;对于不同大小的手机屏幕&#xff0c;怎么动态改变页面布局中所有盒子的宽度高度、字体大小等。 这个问题我们可以使用相对单位rem。 那么什么是 rem&#xff1f; rem&#xff08;font size of the root element&#xff09;是指相对…

Linux-中断和时间管理(上)

目录 中断的进入过程 中断的进入过程 为方便实验&#xff0c;本章以配套的目标板 FS4412为例来介绍 Linux 的中断子系统&#xff0c;并且编写相应的中断处理程序。FS4412 上的处理器是 SAMSUNG公司的 Exynos4412&#xff0c;该处理器使用的是4核的 Cortex-A9&#xff0c;&…

c++Lambda匿名函数

cLambda匿名函数 &#xff08;1&#xff09; 定义a. [外部变量方位方式说明符]b. (参数)c. mutabled.noexcept/throw()e.->返回值类型f.函数体 2&#xff09;c11中的拉姆达表达式中的&#xff08;&#xff09;可以省略吗 所谓匿名函数&#xff0c;简单地理解就是没有名称的函…

《C++ Primer Plus》(第6版)第17章编程练习

《C Primer Plus》&#xff08;第6版&#xff09;第17章编程练习 《C Primer Plus》&#xff08;第6版&#xff09;第17章编程练习1. 计算输入流中第一个\$之前的字符数目2. 将键盘输入&#xff08;直到模拟的文件尾&#xff09;复制到通过命令行指定的文件中3. 将一个文件复制…

完全免费的基于区块链和 IPFS 的去中心化博客平台

一、前言 xLog是一个基于Crossbell区块链的博客解决方案&#xff0c;专注于Web3数据由用户掌控。Crossbell是一个基于Web3技术的去中心化博客平台&#xff0c;用户可以在该平台上发布文章并进行交流和创作。社区提供多种交流平台和有奖创作活动。 xLog是基于 Crossbell 区块链…

【AI回复】“我问它,你对五一调休怎么看”

前言 马上就要到五一啦&#xff0c;放假打算去哪里玩呢&#xff1f; “我肯定是宅在家里写博客啊” 最近五一调休在某博上引起大家的共鸣&#xff0c;看了评论那叫一个惨不忍睹哇。 因为我比较对AI感兴趣&#xff0c;所以想看看它是怎么看待调休的。 首先&#xff0c;在百度…

【UE】简易的水材质

引擎版本&#xff1a;4.26 效果 步骤 1. 创建一个材质&#xff0c;命名为“M_Water” 2. 打开“M_Water”&#xff0c;将混合模式设为半透明&#xff0c; 光照模式设为表面半透明体积&#xff0c;在这种模式下我们可以使用金属度、粗糙度等接口 3. 创建一个4维常量节点&…

Android 基于NumberPicker自定义弹出窗口Dialog整合日期选择器

Android实现把年月选择器放到AlertDialog中_左眼看成爱的博客-CSDN博客 Android使用NumberPicker实现年月滚动选择器_左眼看成爱的博客-CSDN博客 前面两篇文章我们分别讲了 1&#xff0c;如何用NumberPicker实现年月选择器 2&#xff0c;如何把1中的用NumberPicker实现的年…

基于DE2-115平台实现VGA显示器的显示实验

目录 什么是VGA协议VGA显示原理VGA时序图VGA参数图实验记录准备PLLROM取模代码data_drive.vkey_debounce.vvga_drive.vvga_top.v 实验现象 什么是VGA协议 这一部分摘录自野火的征途Pro《FPGA Verilog开发实战指南——基于Altera EP4CE10》2021.7.10&#xff08;上&#xff09;…

ctfshow web入门phpcve web311-315

1.web311 通过抓包发现php版本时为PHP/7.1.33dev 漏洞cve2019-11043 远程代码执行漏洞 利用条件&#xff1a; nginx配置了fastcgi_split_path_info 受影响系统&#xff1a; PHP 5.6-7.x&#xff0c;Nginx>0.7.31 下载工具进行利用 需要安装go环境 yum install golang -y …

一文技术解析ART虚拟机method tracing

一、method tracing介绍 概述 这个是谷歌提供的对java的函数级trace工具&#xff0c;和systrace只支持打点不同&#xff0c;method tracing能支持到函数&#xff0c;看到具体的函数执行时间&#xff0c;准确的分析出来执行的时间短板。 1.生成trace的方式 sampling方式&…