Spring 有幾個異常是在通過 url path 訪問 Controller 方法時,由於參數不匹配而拋出的,比如 MethodArgumentTypeMismatchException
和 MissingServletRequestParameterException
異常。由於拋出該異常的時候還沒有進入 path 指定的方法,因此方法內的 try...catch
是無法捕獲的。如果要對它們進行捕獲,有兩種解決方案。
通過 ExceptionHandler
進行捕獲
ExceptionHandler
可以指定對某個類型的異常進行定制化的處理:
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public void handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
String name = ex.getName();
String type = ex.getRequiredType().getSimpleName();
Object value = ex.getValue();
String message = String.format("'%s' should be a valid '%s' and '%s' isn't",name, type, value);
// Do the graceful handling
}
ExceptionHandler
可以放在某個指定的 Controller 中,也可以放在一個 @ControllerAdvice
注解的類中對所有的 Controller 生效。
通過 HandlerExceptionResolver
進行捕獲
有些時候不方便通過 ExceptionHandler
進行捕獲,比如由於團隊框架的原因繼承了某個 BaseController,而 BaseController 中又被定義了 Throwable
級的處理(沒錯,很蠢的設計)。此時就需要更深一層的定制,即 HandlerExceptionResolver
。
這里要先介紹下 Spring 處理異常的邏輯。Spring 在捕獲到未處理的異常時,會通過一個 HandlerExceptionResolver
的列表,依次調用其中的每個元素的 resolveException
方法,如果返回 null,則會繼續下一個元素的進行調用,不為 null 即終止。其實上面的 ExceptionHandler
也是被 Spring 自己的一個通用 HandlerExceptionResolver
所使用。
因此自定義的 HandlerExceptionResolver
要加到 resolvers 的列表開頭,優先於通用的 HandlerExceptionResolver
進行調用:
package com.xxx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RequestParameterExceptionHandler implements HandlerExceptionResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestParameterExceptionHandler.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex instanceof MissingServletRequestParameterException) {
MissingServletRequestParameterException e = (MissingServletRequestParameterException) ex;
String body = String.format("缺少 %s 類型的參數 %s", e.getParameterType(), e.getParameterName());
try {
response.getWriter().write(body);
} catch (IOException ioException) {
LOGGER.error("{} 發生異常。", LOG_PREFIX, ioException);
}
return new ModelAndView();
} else if (ex instanceof MethodArgumentTypeMismatchException) {
MethodArgumentTypeMismatchException e = (MethodArgumentTypeMismatchException) ex;
String body = String.format("%s 應該是 %s 類型", e.getName(), e.getRequiredType().getSimpleName()));
try {
response.getWriter().write(body);
} catch (IOException ioException) {
LOGGER.error("{} 發生異常。", LOG_PREFIX, ioException);
}
return new ModelAndView();
}
return null;
}
}
接下來要注冊到 Mvc:
package com.xxx;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(0, new RequestParameterExceptionHandler());
}
}
這樣就可以了。