我解决了这个问题,通过编写和注册一个异常处理程序,每当异常被传递到异常处理程序时,该异常处理程序都会响应 JSON 编码的错误消息,并且请求接受类型标头为application/json
or application/json; charset=utf-8
.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.WebUtils;
/**
* Handle exceptions for Accept Type {@code json/application} (and {@code application/json; charset=utf-8}), by returning the
* plain exception.
*
* <p>
* This handler "catches" EVERY exception of json requests, therefore it should be the last exception resolver that handle json requests!
* </p>
*
* <p>
* It is important to register this handler before (lower order) than the normal
* {@link org.springframework.web.servlet.handler.SimpleMappingExceptionResolver}.
* </p>
*
*
* A typical configuration will looks like this pay attention to the order:
* <pre> {@code
* <!--
* dispatcher servlet:
* <init-param>
* <param-name>detectAllHandlerExceptionResolvers</param-name>
* <param-value>false</param-value>
* </init-param>
* -->
* <bean id="handlerExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
* <property name="exceptionResolvers">
* <list>
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0" />
*
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0" />
*
* <bean class="JsonPlainExceptionResolver">
* <!-- <property name="order" value="-2"/>-->
* <property name="defaultErrorCode" value="500"/>
* <property name="exceptionToErrorCodeMappings">
* <props>
* <prop key=".DataAccessException">500</prop>
* <prop key=".NoSuchRequestHandlingMethodException">404</prop>
* <prop key=".TypeMismatchException">404</prop>
* <prop key=".MissingServletRequestParameterException">404</prop>
* <prop key=".ResourceNotFoundException">404</prop>
* <prop key=".AccessDeniedException">403</prop>
* </props>
* </property>
* </bean>
*
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0" />
* </list>
* </property>
* </bean>
* }
* </pre>
* </p>
*
* <p>
* It is recommended to use this exception resolver together with an
* {@link ResponseCommittedAwarenessExceptionResolverWrapper}
* </p>
*
* @author Ralph Engelmann
*/
public class JsonPlainExceptionResolver extends AbstractHandlerExceptionResolver {
/** Logger for this class. */
private static final Logger LOGGER = Logger.getLogger(JsonPlainExceptionResolver.class);
/** Accept header attribute for application/json. */
private static final String APPLICATION_JSON = "application/json";
/** Accept header attribute for application/json with explicit utf-8 charset. */
private static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
/** The default for the {@link #defaultErrorCode}. */
private static final int DEFAULT_DEFAULT_ERROR_CODE = 500;
/** This error code is used when no explicit error code is configured for the exception. */
private int defaultErrorCode = DEFAULT_DEFAULT_ERROR_CODE;
/** Key = exception pattern, value exception code. */
private Properties exceptionToErrorCodeMappings;
public int getDefaultErrorCode() {
return this.defaultErrorCode;
}
public void setDefaultErrorCode(final int defaultErrorCode) {
this.defaultErrorCode = defaultErrorCode;
}
public Properties getExceptionToErrorCodeMappings() {
return this.exceptionToErrorCodeMappings;
}
/**
* Set the mappings between exception class names and error codes
* The exception class name can be a substring, with no wildcard support at present.
* A value of "ServletException" would match <code>javax.servlet.ServletException</code>
* and subclasses, for example.
* @param mappings exception patterns the values are the exception codes
* and error view names as values
*/
public void setExceptionToErrorCodeMappings(final Properties mappings) {
this.exceptionToErrorCodeMappings = mappings;
}
/**
* Check whether this resolver is supposed to apply to the given handler.
*
* <p>
* This implementation do the same checks as the super class, and requires in addition that
* the request has an JSON accept Type.
* </p>
*/
@Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
String acceptType = request.getHeader("Accept");
return super.shouldApplyTo(request, handler)
&& (acceptType != null)
&& (acceptType.equalsIgnoreCase(APPLICATION_JSON) || acceptType.equalsIgnoreCase(APPLICATION_JSON_UTF8));
}
/**
* Do resolve exception.
*
* @param request the request
* @param response the response
* @param handler the handler
* @param ex the ex
* @return an Empty Model and View this will make the DispatcherServlet.processHandlerException in conjunction with
* DispatcherServlet.processDispatchResult assume that the request is already handeled.
*
* @see
* org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#doResolveException(javax.servlet.http
* .HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
*/
@Override
protected ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Handle exception from request: "+ request, ex);
}
String exceptionDetails = JsonPlainExceptionResolver.getExceptionDetailsAndCompleteStackTrace(ex);
applyErrorCodeIfPossible(request, response, determineErrorCode(ex));
try {
response.getOutputStream().write(exceptionDetails.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not supported???", e);
} catch (IOException e) {
throw new RuntimeException("Error while writing exception " + exceptionDetails + ", to response", e);
}
WebUtils.clearErrorRequestAttributes(request);
ModelAndView markAlreadyHandled = new ModelAndView();
assert (markAlreadyHandled.isEmpty());
return markAlreadyHandled;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#buildLogMessage(java.lang.Exception,
* javax.servlet.http.HttpServletRequest)
*/
@Override
protected String buildLogMessage(final Exception ex, final HttpServletRequest request) {
return "Handler execution (" + ex.getClass() + ") resulted in exception , request: "
+ request);
}
/**
* Determine the view name for the given exception, searching the {@link #setExceptionMappings "exceptionMappings"},
* using the {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* @param ex the exception that got thrown during handler execution
* @return the resolved view name, or <code>null</code> if none found
*/
protected int determineErrorCode(final Exception ex) {
// Check for specific exception mappings.
if (this.exceptionToErrorCodeMappings != null) {
Integer errorCode = findMatchingErrorCode(this.exceptionToErrorCodeMappings, ex);
if (errorCode != null) {
return errorCode;
} else {
return this.defaultErrorCode;
}
}
return this.defaultErrorCode;
}
/**
* Find a matching view name in the given exception mappings.
* @param exceptionMappings mappings between exception class names and error view names
* @param ex the exception that got thrown during handler execution
* @return the view name, or <code>null</code> if none found
* @see #setExceptionMappings
*/
protected Integer findMatchingErrorCode(final Properties exceptionMappings, final Exception ex) {
Integer errorCode = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if ((depth >= 0) && (depth < deepest)) {
deepest = depth;
errorCode = Integer.parseInt(exceptionMappings.getProperty(exceptionMapping));
}
}
return errorCode;
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* @param exceptionMapping the exception mapping
* @param ex the ex
* @return the depth
*/
protected int getDepth(final String exceptionMapping, final Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
}
/**
* Gets the depth.
*
* @param exceptionMapping the exception mapping
* @param exceptionClass the exception class
* @param depth the depth
* @return the depth
*/
private int getDepth(final String exceptionMapping, final Class<?> exceptionClass, final int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Apply the specified HTTP status code to the given response, if possible (that is,
* if not executing within an include request).
* @param request current HTTP request
* @param response current HTTP response
* @param statusCode the status code to apply
* @see #determineStatusCode
* @see #setDefaultStatusCode
* @see HttpServletResponse#setStatus
*/
protected void applyErrorCodeIfPossible(final HttpServletRequest request, final HttpServletResponse response,
final int statusCode) {
if (!WebUtils.isIncludeRequest(request)) {
response.setStatus(statusCode);
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
}
}
/**
* Gets the exception details and complete stack trace.
*
* @param e the e
* @return the exception details and complete stack trace
*/
public static String getExceptionDetailsAndCompleteStackTrace(final Throwable e) {
StringBuilder detailedMessage = new StringBuilder();
if (e.getLocalizedMessage() != null) {
detailedMessage.append(e.getLocalizedMessage());
}
if (detailedMessage.length() > 0) {
detailedMessage.append("\n");
}
detailedMessage.append(e.getClass().getName());
/** Save: Commons lang does not support generics in this old version. */
@SuppressWarnings("unchecked")
List<Throwable> throwables = ExceptionUtils.getThrowableList(e);
for (int i = 1; i < throwables.size(); i++) {
detailedMessage.append("\n cause: ");
detailedMessage.append(throwables.get(i).getClass().getName());
if (StringUtils.isNotBlank(throwables.get(i).getLocalizedMessage())) {
detailedMessage.append(" -- " + throwables.get(i).getLocalizedMessage());
}
detailedMessage.append(";");
}
detailedMessage.append("\n\n --full Stacktrace--\n");
detailedMessage.append(ExceptionUtils.getFullStackTrace(e));
return detailedMessage.toString();
}
}