场景: 某个复杂长链路请求, 例如下单, 需要多组件方法参与,但是对于各种参数聚合, 耦合度很高对于二次开发不方便
解决思路: 利用 ThreadLocal 线程变量来完成, 以后每次改造只需要改造,或者向 ThreadLocal 中增加自己的参数即可,
注意:
- 禁止参与多线程!!!否则会导致参数丢失!!!!!
- 注意每个线程必须执行 destroy 操作!!!
- 建议在入口处进行线程变量初始化和销毁
1. 创建业务上下文类
package net.xinhuamm.converge.model.common;
import lombok.Data;
import net.xinhuamm.converge.bean.news.ConvergeNewsInsertRequest;
import net.xinhuamm.converge.bean.news.ConvergeNewsPushRequest;
import net.xinhuamm.gusteau.usercenter.model.response.admin.AdminContextResponse;
/**
* Description: 业务上下文, 里面有业务配置参数,以及环境变量等
* User: zhouzhou
* Date: 2019-04-15
* Time: 14:53
*/
@Data
public class BizContext {
/**
* 当前系统环境
*/
private String systemEnv;
/**
* 当前租户id,可为空看当时场景定
*/
private Long tenantId;
/**
* 当前租户信息
*/
private AdminContextResponse tenantInfo;
/**
* 录入请求
*/
private ConvergeNewsInsertRequest convergeNewsInsertRequest;
}
2. 创建 ThreadLocal 线程变量类
package net.xinhuamm.converge.model.common;
import net.xinhuamm.converge.util.SpringContextUtil;
/**
* Description:业务会话
* User: zhouzhou
* Date: 2019-01-15
* Time: 14:54
*/
public class BizSession {
// 初始化
private static ThreadLocal<BizContext> INSTANCE = new ThreadLocal<BizContext>(){
@Override
protected BizContext initialValue() {
BizContext context = new BizContext();
if (SpringContextUtil.isTestEnv()){
context.setSystemEnv("test");
}
return context;
}
};
/**
* 获取当前对象实例
* @return
*/
public static BizContext currentSession(){
return INSTANCE.get();
}
/**
* 创建会话
* @param bizContext
*/
public static void store(BizContext bizContext){
INSTANCE.set(bizContext);
}
public static void destroy(){
INSTANCE.remove();
}
}
3. 线程变量的初始化与销毁, 这边是在拦截器使用的初始化和销毁
package net.xinhuamm.converge.interceptor;
import net.xinhuamm.converge.model.common.BizContext;
import net.xinhuamm.converge.model.common.BizSession;
import net.xinhuamm.converge.util.SpringContextUtil;
import net.xinhuamm.gusteau.usercenter.model.response.admin.AdminContextResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Description:租户切面
* User: zhouzhou
* Date: 2020-05-19
* Time: 2:45 PM
*
* @author zhouzhou
*/
@Component
public class TenantInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == 404 || response.getStatus() == 500) {
return true;
}
BizContext bizContext = BizSession.currentSession();
// 获取用户 token
if (SpringContextUtil.isLocal()){
AdminContextResponse info = new AdminContextResponse();
info.setCurrentTenantId(1L);
info.setRealName("张三");
bizContext.setTenantInfo(info);
}else{
// ResultModel<AdminContextResponse> tokenModel = iUserCenterApiService.getUserByToken();
// if (tokenModel.getCode() != 0 || tokenModel.getData() == null) {
// return false;
// }
// bizContext.setTenantInfo(tokenModel.getData());
// bizContext.setTenantId(tokenModel.getData().getCurrentTenantId());
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// ignore
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
BizSession.destroy();
}
}
4. 如何在组件中获取和更新线程变量呢?
// 统计记录日志
ConvergeNewsOperateLogDTO logDTO = new ConvergeNewsOperateLogDTO();
logDTO.setDescription(operateRequest.getDescription());
logDTO.setNewsId(newsId);
logDTO.setOperateType(operateType);
logDTO.setOperator(BizSession.currentSession().getTenantInfo().getRealName());
logService.insert(logDTO);