不灭的焱

革命尚未成功,同志仍须努力下载JDK17

作者:Albert.Wen  添加时间:2021-05-25 16:54:46  修改时间:2023-01-28 22:06:09  分类:Java框架/系统  编辑

在Spring MVC项目中,为了随时都能取到当前请求的Request对象,可以通过RequestContextHolder的静态方法getRequestAttributes()获取Request相关的变量,如Request, Response等。 

平常项目中,对RequestContextHolder的使用进一步封装,简化为RequestHolder类,如下:

public class RequestHolder {
    public static HttpServletRequest getRequest(){
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return req;
    }

    public static HttpServletResponse getResponse(){
        HttpServletResponse resp = ((ServletWebRequest)RequestContextHolder.getRequestAttributes()).getResponse();
        return resp;
    }
}

在大部分的情况下,它都能很好地工作,但在商品管理编辑中,新增商品时,却出现了意外的问题:通过RequestHolder.getRequest().getParameter()得不到参数值,通过debug发现,通过Spring MVC的method注入的Request对象实际为MultipartHttpServletRequest,而通过RequestHolder.getRequest()获取到的Request对象却是org.apache.catalina.connector.RequestFacade的实例。 

public class RequestFacade implements HttpServletRequest 

原来在商品新增时,由于使用了文件上传,form表单的enctype类型为“multipart/form-data”,Spring MVC对文件上传的处理类实际却为spring-mvc.xml文件中配置的CommonsMultipartResolver,该类先判断当前请求是否为multipart类型,如果是的话,将Request对象转为MultipartHttpServletRequet,相关的源码见DisptcherServlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    ......
    processedRequest = checkMultipart(request);
    multipartRequestParsed = processedRequest != request;
    ......
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    ......
}

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (request instanceof MultipartHttpServletRequest) {
            logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
                    "this typically results from an additional MultipartFilter in web.xml");
        } else {
            return this.multipartResolver.resolveMultipart(request);
        }
    }
    
    // If not returned before: return original request.
    return request;
}

那么,RequestContextHolder中的Request又是从哪来的呢?

继续翻看DispatcherServlet的源码,从其父类FrameworkServlet中找到的processRequest()以相关方法源码:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    ......

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        doService(request, response);
    }
    ......
}

protected ServletRequestAttributes buildRequestAttributes(
        HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) {

    if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
        return new ServletRequestAttributes(request);
    } else {
        return null;  // preserve the pre-bound RequestAttributes instance
    }
}

private void initContextHolders(
        HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {

    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Bound request context to thread: " + request);
    }
}

从这里可以看到,initContextHolder()方法中完成了RequestContextHolder的requestAttributes设置,而doService()在这之后调用,DispatcherServlet中的processRequest()方法即在doService()之中,所以从RequestContextHolder中获取到的就是原来的RequestFacade对象,而不是经过spring mvc处理之后的MultipartHttpServletRequest对象,其后果就是,从RequestContextHolder获取request后,无法直接通过getParameter()获取参数值。

最便捷的解决办法

直接将HttpServletRequest作为Spring MVC的控制器方法入参,即可以正确获取参数值