ThreadLocal线程本地变量在dubbo服务使用时候遇到的一个坑

我昨天遇到一个问题,就是我springboot项目里面有一个提供代办服务审核的dubbo接口,这个接口给房源项目调用,但是碰到一个问题就是,房源项目每天凌晨5点会查询满足条件过期的数据,然后调用我这边的代办审核dubbo接口,将这个代办任务变成自动拒绝

@Override
@Transactional
public WorkListDataResult auditWorkList(WorkListAuditCmd workListAuditCmd, WorkListLoginUserVo loginUserVo) {
    log.info("auditWorkList WorkListAuditCmd={},WorkListLoginUserVo={}", JSON.toJSONString(workListAuditCmd), JSON.toJSONString(loginUserVo));
    LoginUserUtil.setCurrentUser(loginUserVo);
    WorkListAuditAdapterCmd workListAuditAdapterCmd = AutoMapper.transform(WorkListAuditAdapterCmd.class, workListAuditCmd);
    workListAuditAdapterCmd.setAuditComments(AuditStatusEnum.PASS.getCode().equals(workListAuditCmd.getAuditStatus()) || AuditStatusEnum.PENDING_REEVALUATION.getCode().equals(workListAuditCmd.getAuditStatus()) ? "系统通过" : "系统拒绝");
    workListAuditAdapterCmd.setExtAuditComments(workListAuditAdapterCmd.getAuditComments());
    auditWorkListInternal(workListAuditAdapterCmd);

    WorkListAuditReq workListAuditReq = AutoMapper.transform(WorkListAuditReq.class, workListAuditCmd);
    Boolean needCallback = workListAuditAdapterCmd.getNeedCallback();
    if (needCallback != null && needCallback) {
        //如果dubbo接口里面参数需要回调则回调
        // 注册事务同步器,在事务提交后执行异步任务
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                // 确保数据提交后执行异步任务
                CompletableFuture.runAsync(() -> pushAuditResultToBusiness(workListAuditReq), threadPoolExecutor);
            }
        });
    }
    return new WorkListDataResult();
}

这个定时任务调用这个rpc接口时候,第二个参数WorkListLoginUserVo是传的null,这里到后面审核的时候会根据这个 LoginUserUtil.getCurrentUser 判断不为空,就不会进入权限校验的逻辑

protected void auditMultiProcessWorkTaskInternal(WorkListAuditAdapterCmd workListAuditAdapterCmd) {
    log.info("auditMultiProcessWorkTaskInternal WorkListAuditAdapterCmd={}",JSON.toJSONString(workListAuditAdapterCmd));
    //校验是否有审核的
    WorkListRespVo workListInfo = workListQueryService.getWorkListInfoByKeyId(workListAuditAdapterCmd.getWorkListKeyId());

    setAuditInfo(workListAuditAdapterCmd);
    Integer auditStatus = workListInfo.getAuditStatus();
    LoginUser currentUser = Identify.getCurrentUser();
    if(AuditStatusEnum.PENDING_REVIEW.getCode().equals(auditStatus)){
        workListAuditAdapterCmd.setFirstAuditDeptKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditDeptKeyId() : currentUser.getDepartmentKeyId());
        workListAuditAdapterCmd.setFirstAuditUserKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditUserKeyId() : currentUser.getUserKeyId());
        workListAuditAdapterCmd.setAuditStatus(AuditStatusEnum.PASS.getCode().equals(workListAuditAdapterCmd.getAuditStatus()) || AuditStatusEnum.PENDING_REEVALUATION.getCode().equals(workListAuditAdapterCmd.getAuditStatus()) ? AuditStatusEnum.PENDING_REEVALUATION.getCode() : AuditStatusEnum.REFUSE.getCode());
        workListAuditAdapterCmd.setFirstAuditTime(LocalDateTime.now());
    }else {
        workListAuditAdapterCmd.setSecondAuditUserKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditUserKeyId() : currentUser.getUserKeyId());
        workListAuditAdapterCmd.setSecondAuditDeptKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditDeptKeyId() : currentUser.getDepartmentKeyId());
        workListAuditAdapterCmd.setSecondAuditTime(LocalDateTime.now());
        workListAuditAdapterCmd.setFirstAuditTime(null);
        workListAuditAdapterCmd.setFirstAuditDeptKeyId(null);
        workListAuditAdapterCmd.setFirstAuditUserKeyId(null);
    }
    log.info("auditMultiProcessWorkTaskInternal WorkListAuditAdapterCmd={},WorkListRespVo={}",JSON.toJSONString(workListAuditAdapterCmd),JSON.toJSONString(workListInfo));
    // 获取权限代码
    setPermissionCode(workListAuditAdapterCmd, workListInfo);

    //校验是否有审核权限,排除房源定时Job调用的情况
    if(currentUser != null && StringUtils.isNotBlank(currentUser.getUserKeyId()) && StringUtils.isNotBlank(currentUser.getRoleKeyId())){
        checkAuditPermission(currentUser,workListAuditAdapterCmd,workListInfo);
    }
    //更新代办任务数据
    updateWorkList(workListAuditAdapterCmd);
}

生产上面却发现了一个问题,就是有一定概率会有些数据进行了权限校验,也就是currentUser不为空了,后面跟踪房源那边代码才发现,那边调用这个审核的dubbo接口,会调用另外一个查询的rpc接口,

public WorkListDataResult<List<WorkListRespDto>> getWorkListStatusByCondion(WorkListStatusReq req, WorkListLoginUserVo loginUserVo) {
    log.info("getWorkListStatusByCondion WorkListStatusReq={},WorkListLoginUserVo={}", JSON.toJSONString(req), JSON.toJSONString(loginUserVo));
    LoginUserUtil.setCurrentUser(loginUserVo);
    WorkListDataResult<List<WorkListRespDto>> result = new WorkListDataResult<>();
    if (StringUtils.isBlank(req.getApplyType())) {
        result.setFlag(false);
        result.setErrorMessage("待办类型不能为空!");
        return result;
    }
    if (StringUtils.isBlank(req.getPropertyKeyId())) {
        result.setFlag(false);
        result.setErrorMessage("房源ID不能为空!");
        return result;
    }
    List<WorkListRespDto> listResps = new ArrayList<>();
    List<UserByIdOrNumberRpcDto> userList = new ArrayList<>();
    //用于存放用户ID的集合
    List<String> userKeyIdList = new ArrayList<>();
    List<DepartmentByIdOrNoRpcDto> deptList = new ArrayList<>();
    //用于存放部门ID的集合
    List<String> deptIdList = new ArrayList<>();
    //将空值置为null
    SpringBeanUtil.convertEmptyToNull(req);

    WorkListSearchVo workListSearchVo = AutoMapper.transform(WorkListSearchVo.class, req);
    log.info("getWorkListStatusByCondion WorkListSearchVo={}", JSON.toJSONString(workListSearchVo));
    List<WorkListRespVo> list = workListQueryService.getWorkListStatusByCondion(workListSearchVo);
    log.info("getWorkListStatusByCondion list.total=" + list.size());
    if (list != null && list.size() > 0) {
        for (WorkListRespVo viewResp : list) {
            if (viewResp != null) {
                //设置userId和deptId集合
                setDeptOrUserList(viewResp, userKeyIdList, deptIdList);
            }
        }
        if (userKeyIdList != null && userKeyIdList.size() > 0) {
            //批量查询用户信息
            userList = userAndDeptDubboService.queryUserByIdOrNumber(userKeyIdList);
            log.info("getWorkListStatusByCondion userList={}", userList.toString());
        }
        if (deptIdList != null && deptIdList.size() > 0) {
            //批量查询部门信息
            deptList = userAndDeptDubboService.queryByKeyIdOrNo(deptIdList);
            log.info("getWorkListStatusByCondion deptList={}", deptList.toString());
        }
        for (WorkListRespVo viewResp : list) {
            //设置枚举描述
            setWorkListVoDesc(viewResp);
            //设置待办对象用户和部门信息
            setWorkListRespVoDeptOrUser(viewResp, userList, deptList);
            WorkListRespDto workListRespVo = AutoMapper.transform(WorkListRespDto.class, viewResp);
            listResps.add(workListRespVo);
        }
    }
    result.setFlag(true);
    result.setData(listResps);
    return result;
}

这个查询的接口也进行了LoginUserUtil.setCurrentUser(loginUserVo),这段代码会放到ThreadLocal 的线程本地变量里面

而整个这个查询和审核的接口都没有进行这个ThreadLocal线程变量的清除,因为dubbo提供的rpc接口,本质上是使用了线程池技术,会复用一些线程,比如说19号先执行了这个查询代办任务的rpc接口,这个时候设置了currentUser,然后后面调用这个代办审核的接口时候,又刚好拿到了这个19号线程,这个时候通过getCuurentUser拿到的用户就不为空,然后就进入了权限校验的逻辑,解决办法通过自定义dubbo的过滤器,在过滤器里面完成资源的清除

<think>嗯,用户这次的问题很具体,是关于DubboRPC服务中能否直接使用SpringBean。看来用户正在做微服务开发,遇到了框架集成的问题。从用户引用的资料来看,ta应该已经配置过Dubbo服务提供者,对SpringBoot和Dubbo的整合有一定基础,但可能对底层机制不太放心。用户提供的引用材料[1]展示了Dubbo提供者的配置示例,[3][4]提到Dubbo关闭时的异常处理,说明ta可能经历过线上发布时的问题。现在ta关注的是服务层对象注入的可行性,这其实是个很实际的问题——毕竟在Spring环境中,谁不想直接用现成的Bean管理机制呢。先确认下技术事实:Dubbo服务类本身就是SpringBean啊。用户可能忽略了Dubbo的@Service注解就是Spring的@Component变体。想到几个关键点需要解释清楚:第一,服务类被双重代理的特性;第二,注入其他Bean时的循环依赖陷阱;第三,并发调用的线程安全问题。用户没有提到具体使用场景,但引用[3]提到高发时段的RPC失败问题,可能要特别提醒注意Bean的作用域问题。单例模式在高并发下如果状态处理不当确实容易出问题,这点得重点说明。考虑到用户是开发者,回复时要给出可落地的方案。除了基础用法,准备重点解释两种实用场景:事务传播的注意事项和延时注入的技巧。用户提供的引用[4]提到销毁时的zk节点操作,顺便带一句优雅下线的关联性应该能帮助ta理解整个生命周期。咦,突然想到用户没问但可能需要的点:如果要用非Spring管理的第三方类怎么办?这个在微服务架构里其实挺常见的。决定加在拓展思路里,用工厂Bean的方式解决,保持Spring容器的一致性。</think> 在Dubbo RPC服务中**可以直接使用Spring Bean**,且是Dubbo官方推荐的集成方式。以下是具体实现方法及注意事项: --- ### 一、使用方法(基于Spring Boot) 1. **定义Spring Bean** 在服务实现类中直接通过`@Autowired`注入其他Bean: ```java @Service // Dubbo的@Service注解(非Spring的) public class OrderServiceImpl implements OrderService { @Autowired // 注入Spring管理的Bean private ProductService productService; @Override public Order createOrder(OrderRequest request) { // 直接调用Spring Bean的方法 Product product = productService.getProduct(request.getProductId()); // ...业务逻辑 } } ``` 2. **配置类声明(可选)** 确保Dubbo服务实现类本身被Spring管理: ```java @Configuration public class DubboConfig { @Bean // 显式声明为Spring Bean public OrderService orderService() { return new OrderServiceImpl(); } } ``` --- ### 二、技术可行性分析 | 项目 | 说明 | |--------------------|----------------------------------------------------------------------| | **依赖注入** | Dubbo服务类本质是Spring Bean,支持`@Autowired`、`@Resource`等注解 ✅ | | **事务控制** | 支持`@Transactional`进行数据库事务管理 ✅ | | **AOP代理** | 兼容Spring AOP(如日志、权限切面)✅ | | **生命周期同步** | Dubbo服务启动/销毁与Spring容器生命周期自动绑定 ✅ | --- ### 三、关键注意事项 1. **Bean作用域冲突** * 避免在Dubbo服务类中使用`@Scope("prototype")`,可能导致RPC调用状态异常 * 推荐方案:将需要多例的组件拆分为独立的Spring Bean注入 2. **循环依赖陷阱** ```java // 错误示例:两个Dubbo服务相互注入 @Service public class ServiceAImpl implements ServiceA { @Autowired private ServiceB serviceB; // 直接注入另一个Dubbo服务 } ``` **正确做法**:通过Dubbo的`@Reference`注入远程服务: ```java @Service public class ServiceAImpl implements ServiceA { @Reference // Dubbo远程引用 private ServiceB serviceB; } ``` 3. **并发安全** ```java public class PaymentServiceImpl implements PaymentService { private int counter; // 实例变量:非线程安全! @Override public synchronized void process() { counter++; // 必须加锁或使用ThreadLocal } } ``` > 由于Dubbo服务默认单例,高并发时需避免修改共享状态[^3] 4. **优雅下线优化** * 在Spring容器关闭前先注销Dubbo服务: ```java @PreDestroy public void destroy() { // 1. 主动注销Dubbo服务节点 // 2. 等待RPC请求处理完毕 // 3. 释放Spring Bean资源 } ``` 避免直接杀进程导致zk节点未清理[^4] --- ### 四、典型应用场景 1. **数据库操作** 注入`JdbcTemplate`/`MyBatis Mapper`执行本地数据库操作 2. **缓存集成** 直接调用`RedisTemplate`操作分布式缓存 3. **微服务嵌套调用** 通过`@Reference`调用其他Dubbo服务(需区分本地/远程) --- ### 五、实践建议 1. **配置检查** 确保`spring.dubbo.scan`路径包含服务实现类[^1]: ```properties spring.dubbo.scan=com.example.service.impl ``` 2. **版本兼容** 使用Spring Cloud Alibaba >= 2.2.1 + Dubbo >= 2.7.8 保证稳定性[^2] 3. **性能监控** 在注入的Bean中添加`@DubboMonitor`采集方法级性能指标 > 核心结论:Dubbo与Spring Bean的整合是生产级方案,官方文档明确支持该模式,关键在于遵循线程安全和生命周期规范。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值