springmvc在controller成员变量中注入request(`@Autowired`),会有线程安全问题

本文探讨了在Spring MVC控制器中通过`@Autowired`注入`HttpServletRequest`导致的线程安全问题。在一个文件上传的业务场景中,由于请求处理时间长,当并发请求到达时,可能导致请求对象混淆,引发类型转换错误。问题出现在请求被绑定到ThreadLocal中,但在并发情况下未正确处理。为避免此类问题,建议在编写文件上传接口时,不使用成员变量注入的请求,而是直接在方法参数中传递`HttpServletRequest`。


springmvc在controller成员变量中注入request(@Autowired),会有线程安全问题。

查阅了很多文章,都说request是绑定在ThreadLocal里面的,在注入的时候是一个代理对象,实际的request对象是注入到ThreadLocal中的。

但是在文件上传业务场景中出现了bug

一、 业务场景

文件上传业务,当文件上传接口处理时间较长,在强制转换(MultipartHttpServletRequest) request 之前,又有新的请求到达这个Controller, 然后在执行到强制转换的时候,会报错RequestFacade cannot be cast to org.springframework.web.multipart.MultipartHttpServletRequest

二、业务代码

@Autowired
    private HttpServletRequest httpServletRequest; 

    /**
     * 模型轻量化转换
     *
     * @return ResultJson
     */
    @RequestMapping("/uploadToBimServer")
    public JsonResult uploadToBimServer(@RequestParam Map<String, Object> reqMap, HttpServletRequest httpServletRequest) {
        JRTransaction trans = JRTransaction.BeginTransaction();
        String userId = this.getUserAuth().getProjectUserId();
        Integer prjId = this.getUserAuth().getPrjId();
        Integer entId = this.getUserAuth().getEntId();
        String orgId = this.getUserAuth().getProjectUserObj().getXss01OrgId();
        Xss12ProjectInfo prjObj = this.getUserAuth().getPrjObj();
        JsonResult result = new JsonResult();
        try {
            if (!(request instanceof MultipartHttpServletRequest)) {// 判断流中是否有文件信息
                log.error("上传文件数据为空");
                return result.buildErrorResult("请选择文件");
            }
            String name = request.getClass().getName();
            System.out.println("name1: "  + name);

            String uploadCancelId = (String)reqMap.get("uploadCancelId");
//            Assert.isTrue(StringUtils.isNotNull(uploadCancelId),"缺少必要参数!");
            redisUtil.set(RedisKeyConstant.CANCEL_UPLOAD_FILE_FLAG + uploadCancelId,"1",RedisKeyConstant.CANCEL_UPLOAD_FILE_FLAG_TIMEOUT);

            Thread.sleep(5000);
            String name2 = request.getClass().getName();
            System.out.println("name2: " + name2);

            String name3 = httpServletRequest.getClass().getName();
            System.out.println("name3: " + name3);

//            MultipartResolver resolver = new CommonsMultipartResolver(request.getSession().getServletContext());
//            MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
//            String name3 = multipartRequest.getClass().getName();
//            System.out.println("name3: " + name3);

            // 开始处理上传文件
            result = _Xfd02ProjectDocService.uploadToBimServer(prjId, reqMap, (MultipartHttpServletRequest) request, entId,
                    orgId, userId, prjObj, "bim", BIM_SERVER_TRANSFER_PATH,uploadCancelId, trans);

            trans.commit();
            return result;
        } catch (Exception e) {
            trans.rollback();
            this.printErrorLog(e.getMessage());
            throw new ServiceException(e.getMessage());
        }
    }

	/**
     * 测试请求
     *
     * @return ResultJson
     */
    @RequestMapping("/test")
    public String test(@RequestParam Map<String, Object> reqMap) {

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "测试";
    }
    

三、实现场景

当调用/uploadToBimServer请求后,立即调用/test请求。

四、原因

暂未知,需要研究底层源码

五、避免方法

在写文件上传接口时,不要使用成员变量注入的request

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值