供个人记录用。
一、问题描述
客户端A通过MQTT协议向客户端B发送一条消息,客户端B需要根据消息的topic定位到金蝶云苍穹系统中的一个单据页面,并在页面中添加消息携带的message。
按照以往设计插件的经验,只需要使用BusinessDataServiceHelper类就可以成功定位到单据页面。但是与注册插件不同的是,插件是由前端事件触发,从前端进入后端再由后端来对前端页面进行修改;而本问题中需要直接从后端来定位到前端页面并进行修改。所以就存在问题:无法从后端直接定位到前端单据页面。
二、问题原因
涉及了多线程问题,个人理解是前端页面的构建属于一个线程,MQTT协议的部分属于另一个线程,在执行MQTT协议的部分时,页面构建线程会被撤销,从而页面实体也就不存在了,所以是无法定位到单据页面的。
要解决这一问题需要构建一个线程池,使得MQTT协议工作时,页面构建线程不会被撤销,然后在定位单据页面之前切换请求上下文(切换线程)。
(对线程的理解不太对,后续有更深刻了解后再做修改)
三、线程池概念介绍
1、线程池的作用
线程池是一个暂存线程的存储空间,由一个线程队列与一个任务队列组成。线程队列保存着空闲线程,当线程运行结束,就加入线程队列等待下一个任务到来,而不是直接撤销。任务队列保存着等待执行的任务,若任务需要用到某个线程,则直接从线程队列中将其唤醒,而不需要重新创建。因此,线程池可以大大减少线程创建与销毁的开销。
此外,线程池还利于线程的管理、提高线程利用率和系统吞吐量。
2、线程池的参数
- corePoolSize:线程池的核心线程数量,核心线程默认一直存活与线程池。
- maxmumPoolSize:最大线程数。
- keepAliveTime:临时线程的最大生存时间。
- unit:规定keepAliveTime的时间单位。
- workQueue:指定线程池的任务队列。
- threadFactory:指定创建新线程的线程工厂。
- rejectedExecutionHandler:规定线程池任务队列满了之后的处理策略。
3、线程池创建
苍穹环境下的创建:
参考:苍穹线程池的最佳实践
executeOnce | 执行一次 |
executeOnceIncludeRequestContext | 携带Request Context执行一次 |
newCachedThreadPool | 创建可缓存线程池(返回ThreadPool接口) |
newFixedThreadPool | 创建固定线程池(返回ThreadPool接口) |
newCachedExecutorService | 创建可缓存线程池(返回jdk的ExecutorService接口) |
newExecutorService | 创建固定线程池(返回jdk的ExecutorService接口) |
四、解决代码
切换上下文的作用是切换数据源,让程序能从正确的数据源中定位到单据页面。
//创建executeOnce类型的线程池,此类线程池将会执行一次runnable的方法后销毁
//()->是Lambda表达式的写法,将函数作为方法的参数
ThreadPools.executeOnce("MQTT-TEST",()->{
//MQTT相关动作
*******
*******
// 配置请求上下文
RequestContext rc = RequestContext.create(true);
rc.setUserAgent("UserAgent");
rc.setClient("Client"); //设置客户端
rc.setTraceId(TraceId);
rc.setTenantId("TenantId");
rc.setAccountId("AccountId");
rc.setUserId("UserId");
rc.setLang(Lang.defaultLang());
rc.setGlobalSessionId(SessionIdUtils.newSessionId());
//切换上下文
RequestContextThreadBinder.bind(rc);
//定位单据页面
******
});