您的位置:首页 > 编程语言 > Java开发

SpringMVC异常统一处理

2017-09-14 15:52 369 查看
SpringMVC异常统一处理有三种方式

第一种:SimpleMappingExceptionResolver

使用框架中提供的类,这种方式具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,但该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。

查看期源码发现SimpleMappingExceptionResolver

public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {}
不难发现SimpleMappingExceptionResolver实际上是实现HandlerExceptionResolver接口。这个接口是springMVC提供的异常处理的统一接口SimpleMappingExceptionResolver是其简单实现。
<!-- 全局异常拦截器 SimpleMappingExceptionResolver是HandlerExceptionResolver的简单实现类-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
//未知异常处理
<prop key="java.lang.Throwable">error/500</prop>
</props>
</property>
</bean>第二种:HandlerExceptionResolver
实现HandlerExceptionResolver进行开发相比第一种来说,HandlerExceptionResolver能准确显示定义的异常处理页面,达到了统一异常处理的目标

public class GlobalHandlerExceptionResolver implements HandlerExceptionResolver {
private static final Logger LOG = LoggerFactory.getLogger(GlobalHandlerExceptionResolver.class);

/**
* 在这里处理所有得异常信息
*/
@Override
public ModelAndView resolveException(HttpServletRequest req, HttpServletResponse resp, Object o, Exception ex) {
ex.printStackTrace();
if (ex instanceof AthenaException) {
// AthenaException为一个自定义异常
ex.printStackTrace();
printWrite(ex.toString(), resp);
return new ModelAndView();
}
// RspMsg为一个自定义处理异常信息的类
// ResponseCode为一个自定义错误码的接口
RspMsg unknownException = null;
if (ex instanceof NullPointerException) {
unknownException = new RspMsg(ResponseCode.CODE_UNKNOWN, "业务判空异常", null);
} else {
unknownException = new RspMsg(ResponseCode.CODE_UNKNOWN, ex.getMessage(), null);
}
printWrite(unknownException.toString(), resp);
return new ModelAndView();
}

/**
* 将错误信息添加到response中
*
* @param msg
* @param response
* @throws IOException
*/
public static void printWrite(String msg, HttpServletResponse response) {
try {
PrintWriter pw = response.getWriter();
pw.write(msg);
pw.flush();
pw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}第三种:@ExceptionHandler
使用注解@ExceptionHandler方式进行开发每个Controller都可以写入一个这个注解方法,来进行异常控制,或者定义一个超类BaseController中来实现,是一样的

public abstract class BaseController {

/**
* 日志对象
*/
protected Logger logger = LoggerFactory.getLogger(getClass());

@ExceptionHandler
public String exp(HttpServletRequest request, HttpServletResponse response, Exception ex) {
logger.error("【发现异常】===" + ex.getMessage());
return "error/500";
}
}
我们可以使用第二种方法使用@ControllerAdvice注解配合使用
@ControllerAdvice
public class GlobalExceptionHandler extends BaseGlobalExceptionHandler {

protected Logger logger = LoggerFactory.getLogger(getClass());

// 比如404的异常就会被这个方法捕获
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ModelAndView handle404Error(HttpServletRequest req, HttpServletResponse rsp, Exception e) throws Exception {
return handleError(req, rsp, e, "error/error", HttpStatus.NOT_FOUND);
}

// 500的异常会被这个方法捕获
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleError(HttpServletRequest req, HttpServletResponse rsp, Exception e) throws Exception {
return handleError(req, rsp, e, "error/error", HttpStatus.INTERNAL_SERVER_ERROR);
}

// TODO 你也可以再写一个方法来捕获你的自定义异常
// TRY NOW!!!
}保证GlobalExceptionHandler可以被扫描到,注入到Spring的容器中,运行时我们发现可以拦截500的错误,但是404的错误拦截不到
我们查看一下SpringMVC的源码,从源头找DispatcherServlet入口类,核心方法doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}

applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}查看其noHandlerFound源码
/**
* No handler found -> set appropriate HTTP response status.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if(throwExceptionIfNoHandlerFound) {
ServletServerHttpRequest req = new ServletServerHttpRequest(request);
throw new NoHandlerFoundException(req.getMethod().name(),
req.getServletRequest().getRequestURI(),req.getHeaders());
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}变量throwExceptionIfNoHandlerFound很关键
private boolean throwExceptionIfNoHandlerFound = false;发现定义的是false,对于404的这种情况SpringMVC有特殊处理response.sendError(HttpServletResponse.SC_NOT_FOUND);没有抛出异常
我们需要设置throwExceptionIfNoHandlerFound为true让其抛出NoHandlerFoundException由我们自己进行管理

我们可以在web.xml中配置
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 4xx 异常拦截打开 -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>打开了异常拦截,这一步网上有资料搜索就可以了,下面进行测试
测试发现还是没有拦截到404的错误,也是很郁闷,网上搜了很多资料还是一筹莫展。

关键:在springmvc的配置文件中找到了<mvc:default-servlet-handler/>,看着就很熟悉是吧,这是由于SpringMVC拦截所有请求,对于静态的资源也不放过,我们要放行静态资源加入这个注解,在springMVC-servlet.xml中配置<mvc:default-servlet-handler
/>后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。

对url进行筛选。

查看源码

/**
* Set whether to throw a NoHandlerFoundException when no Handler was found for this request.
* This exception can then be caught with a HandlerExceptionResolver or an
* {@code @ExceptionHandler} controller method.
* <p>Note that if
* {@link org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler}
* is used, then requests will always be forwarded to the default servlet and
* a NoHandlerFoundException would never be thrown in that case.
* <p>Default is "false", meaning the DispatcherServlet sends a NOT_FOUND error
* through the Servlet response.
* @since 4.0
*/
public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) {
this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
}设置这个NoHandlerFoundException是有条件的,当有DefaultServletHttpRequestHandler时则默认转到servlet,绝不会抛出NoHandlerFoundException异常
而<mvc:default-servlet-handler />恰恰是在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,所以没有抛异常。

要想拦截404这种异常不能使用<mvc:default-servlet-handler />,但是不使用又会导致静态资源的拦截,很为难。我们想到还要有个注解 <mvc:resources />静态资源注解

<mvc:resources mapping="/static/**" location="/static/" />这种方式只是解决了不访问静态资源的时候,404会抛出异常,访问静态资源时找不到的404还是拦截不到,这也是没有办法,SpringMVC的异常处理处理不了系统的所有异常.
我们需要在web.xml中定义其异常时的错误页面

<error-page>
<error-code>500</error-code>
<location>/WEB-INF/view/error/500.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/view/error/404.jsp</location>
</error-page>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息