Spring MVC 默認的 SimpleMappingExceptionResolver 僅支持將異常映射到視圖頁面。實際項目中往往需要區分「普通請求」與「AJAX 請求」:前者跳轉錯誤頁,後者返回 JSON。繼承並重寫 doResolveException 即可實現這一目標。
public class GlobalExceptionResolver extends SimpleMappingExceptionResolver {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
String viewName = determineViewName(ex, request);
if (viewName == null) return null;
// 允許控制器通過 request.setAttribute("errorPage", "customError") 指定錯誤頁
String customErrorPage = (String) request.getAttribute("errorPage");
if (StringUtils.isNotBlank(customErrorPage)) viewName = customErrorPage;
// 判斷是否為 AJAX 請求
boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"))
|| request.getHeader("accept") != null && request.getHeader("accept").contains("application/json");
if (!isAjax) {
// 普通請求:返回錯誤視圖
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) applyStatusCodeIfPossible(request, response, statusCode);
return getModelAndView(viewName, ex, request);
} else {
// AJAX 請求:返回 JSON
try {
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", ex.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(result));
} catch (IOException e) {
logger.error("寫入 AJAX 錯誤響應失敗", e);
}
return null; // 避免 Spring 再次渲染視圖
}
}
}
<bean class="com.example.exception.GlobalExceptionResolver">
<!-- 默認錯誤頁 -->
<property name="defaultErrorView" value="error" />
<!-- 異常對象在視圖中的屬性名 -->
<property name="exceptionAttribute" value="ex" />
<!-- 特定異常映射到特定頁面 -->
<property name="exceptionMappings">
<props>
<prop key="java.io.IOException">error/ioError</prop>
<prop key="java.sql.SQLException">error/sqlError</prop>
</props>
</property>
</bean>
@Controller
public class OrderController {
@PostMapping("/submit")
public String submit(Order order, HttpServletRequest request) {
try {
orderService.process(order);
} catch (BusinessException e) {
request.setAttribute("errorPage", "order/fail"); // 覆蓋默認錯誤頁
throw e; // 拋出讓解析器捕獲
}
return "order/success";
}
}
SimpleMappingExceptionResolver 而非實現 HandlerExceptionResolver,復用現有映射邏輯。X-Requested-With 和 Accept 頭精準識別 AJAX 請求。null 表示已自行處理響應,阻止後續視圖渲染。exceptionMappings 配置,實現「約定優於配置」與「特例特辦」並存。