SpringMVC异常统一处理

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>





















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值