java.lang.IllegalStateException: Cannot call sendError() after the response has been committed解讀

KillerTwo發表於2019-02-20

原始碼:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    String uri = request.getRequestURI();
    if(pathMatcher.match("/", uri)) {
        System.err.println("跳轉");
        response.sendRedirect("/swagger-ui.html");
        // return false;    // 如果此處不返回false, 則springMvc會繼續對“/”路徑進行處理,就會出現多次返回響應的錯誤。
        
    }
    return true;
}

注:此處對“/”路徑的訪問返回404.

DispatcherServlet.doDispatch()中對攔截 器的preHandle進行呼叫:

// 如果攔截器的PreHandle返回false,則此處直接返回退出方法。
if(!mappedHandler.applyPreHandle(request,response)){
    return ;
}
// Actually invoke the handler. 呼叫處理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 由此處可知如果攔截器的preHandle方法返回false則不會呼叫處理器(控制器類的方法)

mappedHandler是一個HandlerExcutionChain物件由HandlerMapping返回,HandlerExcutionChain包含一個Handler(處理器物件)和攔截器陣列,通過applyPreHandle(request,response)方法會對攔截器陣列中的每一個攔截器的preHandle進行呼叫。

// HandlerExcutionChain類
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

在本例中出現上述錯誤的原因:

  1. 攔截器攔截了對“/”路徑的請求,並且呼叫response.sendRedirect(“/swagger-ui.html”)返回了響應。由於攔截器沒有返回false,所以SpringMVC會繼續對“/”路徑進行處理。

  2. 在沒有找到“/”對應的處理器時,SpringMVC預設會使用ResourceHttpRequestHandler進行請求處理。ResourceHttpRequestHandler在進行請求處理時會進行404檢查,如果路徑或資源不存在則會呼叫response.sendError(HttpServletResponse.SC_NOT_FOUND);原始碼如下:

    // ResourceHttpRequestHandler中進行404檢查
    // For very general mappings (e.g. "/") we need to check 404 first
    Resource resource = getResource(request);
    if (resource == null) {
        logger.debug("Resource not found");
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
  3. 由2可知,如果資源不存在就會呼叫response.sendError(HttpServletResponse.SC_NOT_FOUND);而在攔截器中已經呼叫response.sendRedirect(“/swagger-ui.html”)對響應進行了返回,所以就會出現多次返回響應的錯誤。

對於上述問題的解決辦法是在response.sendRedirect(“/swagger-ui.html”);後返回false,或者將攔截路徑由“/”改為response.sendError(HttpServletResponse.SC_NOT_FOUND);後的路徑(在此處為“/error”)。

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    String uri = request.getRequestURI();
    if(pathMatcher.match("/", uri)) {

        response.sendRedirect("/swagger-ui.html");
        return false;    // 如果此處不返回false, 則springMvc會繼續對“/”路徑進行處理,就會出現多次返回響應的錯誤。
        
    }
    return true;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    String uri = request.getRequestURI();
    if(pathMatcher.match("/error", uri)) {

        response.sendRedirect("/swagger-ui.html");
        
    }
    return true;
}

下面是SpringMVC處理”/api/test/error”請求時列印的部分日誌(/api/test/error沒有對應的處理器,即該路徑不存在,報404錯誤):

注:自定義的攔截器是在對映結束後才執行的。

// 請求路徑
2019-02-20 14:55:57.086 DEBUG 2676 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : GET "/api/test/error", parameters={}
// 處理器對映器和請求路徑對應的處理器
2019-02-20 14:55:57.092 DEBUG 2676 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]
// 處理結果
2019-02-20 14:55:57.094 DEBUG 2676 --- [nio-8080-exec-1] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2019-02-20 14:55:57.095 DEBUG 2676 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND

下面是SpringMVC處理“/api/test/error1”請求時列印的部分日誌(/api/test/error1對應的處理器在org.lwt.controller.RoleController類下的.ErrorTest()方法):

// 請求路徑
2019-02-20 15:13:23.860 DEBUG 2676 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : GET "/api/test/error1", parameters={}
// 處理器對映器和請求路徑對應的處理器
2019-02-20 15:13:23.861 DEBUG 2676 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.lwt.vo.Result<java.lang.String> org.lwt.controller.RoleController.ErrorTest()
 
攔截器呼叫
// 處理結果
2019-02-20 15:13:23.865 DEBUG 2676 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : Failed to complete request: org.joda.time.IllegalInstantException: 自己丟擲錯誤

2019-02-20 15:13:23.867 ERROR 2676 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.joda.time.IllegalInstantException: 自己丟擲錯誤] with root cause
// RoleController類
@RestController
@RequestMapping("/api")
@Api
public class RoleController {
    // /api/test/error1對應的處理器
    @GetMapping("/test/error1")
    public Result<String> ErrorTest(){
        throw new IllegalInstantException("自己丟擲錯誤");
        // return Result.success("多引數傳遞");
    }
}

下面是SpringMVC處理“/api/test/error1”請求時列印的部分日誌(/api/test/error1對應的處理器在org.lwt.controller.RoleController類下的.ErrorTest()方法):

此處和上一次呼叫的區別是此次呼叫處理器沒有報錯:

// 請求路徑
2019-02-20 15:21:31.440 DEBUG 8252 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : GET "/api/test/error1", parameters={}
// 處理器對映器和請求路徑對應的處理器
2019-02-20 15:21:31.444 DEBUG 8252 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.lwt.vo.Result<java.lang.String> org.lwt.controller.RoleController.ErrorTest()
    
攔截器中的uri: /api/test/error1

// 處理結果
2019-02-20 15:21:31.473 DEBUG 8252 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Using `application/json;q=0.8`, given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json]

2019-02-20 15:21:31.473 DEBUG 8252 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Writing [org.lwt.vo.Result@334348d5]

2019-02-20 15:21:31.486 DEBUG 8252 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

相關文章