IT小栈

  • 主页
  • Java基础
  • RocketMQ
  • Kafka
  • Redis
  • Shiro
  • Spring
  • Spring Boot
  • Spring Cloud
  • 资料链接
  • 关于
所有文章 友链

IT小栈

  • 主页
  • Java基础
  • RocketMQ
  • Kafka
  • Redis
  • Shiro
  • Spring
  • Spring Boot
  • Spring Cloud
  • 资料链接
  • 关于

SpringMVC异常统一处理

2019-06-23

SpringMVC异常统一处理有三种方式

第一种:SimpleMappingExceptionResolver

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

查看源码发现SimpleMappingExceptionResolver
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {...}
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {...}

不难发现SimpleMappingExceptionResolver实际上是实现HandlerExceptionResolver接口。这个接口是springMVC提供的异常处理的统一接口SimpleMappingExceptionResolver是其简单实现。

1
2
3
4
5
6
7
8
9
<!-- 全局异常拦截器 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能准确显示定义的异常处理页面,达到了统一异常处理的目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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中来实现,是一样的

1
2
3
4
5
6
7
8
9
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注解配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 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很关键

1
private boolean throwExceptionIfNoHandlerFound = false;

发现定义的是false,对于404的这种情况SpringMVC有特殊处理response.sendError(HttpServletResponse.SC_NOT_FOUND);没有抛出异常

我们需要设置throwExceptionIfNoHandlerFound为true让其抛出NoHandlerFoundException由我们自己进行管理

我们可以在web.xml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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进行筛选。

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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 />静态资源注解

1
<mvc:resources mapping="/static/**" location="/static/" />

这种方式只是解决了不访问静态资源的时候,404会抛出异常,访问静态资源时找不到的404还是拦截不到,这也是没有办法,SpringMVC的异常处理处理不了系统的所有异常.

我们需要在web.xml中定义其异常时的错误页面

1
2
3
4
5
6
7
8
<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>
本文作者: 顾 明 训
本文链接: https://www.itzones.cn/2019/06/23/SpringMVC异常统一处理/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
  • 异常
  • SpringMVC
  • spring

扫一扫,分享到微信

微信分享二维码
Spring IOC和DI
  1. 1. 第一种:SimpleMappingExceptionResolver
  2. 2. 第二种:HandlerExceptionResolver
  3. 3. 第三种:@ExceptionHandler
© 2020 IT小栈
载入天数...载入时分秒... || 本站总访问量次 || 本站访客数人次
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链

tag:

  • jvm
  • Java基础
  • kafka HW
  • kafka Leader Epoch
  • kafka
  • kafka位移主题
  • kafka位移提交
  • kafka副本机制
  • kafka ISR
  • zookeeper
  • kafka消息丢失
  • kafka日志存储
  • kafka Log Clean
  • kafka Log Compaction
  • kafka消费位移设置
  • kafka Rebalance
  • kafka分区算法
  • kafka生产者拦截器
  • kafka SASL/SCRAM
  • kafka ACL
  • redis
  • redis Ziplist
  • redis Hashtable
  • redis LinkedList
  • redis QuickList
  • redis intset
  • redis String
  • redis SDS
  • redis SkipList
  • redisDb
  • redisServer
  • redis 简介
  • Redis Cluster
  • 主从同步
  • RocketMQ高可用HA
  • 事务消息
  • 内存映射
  • MMAP
  • 同步刷盘
  • 异步刷盘
  • 消息存储文件
  • RocketMQ安装
  • 延迟消息
  • RocketMQ入门
  • 推拉模式
  • PushConsumer
  • 消费结果处理
  • rebalance
  • RocketMQ权限控制
  • RocketMQ ACL
  • 消息过滤
  • 消息重试
  • 消费位置
  • 集群消费
  • 广播消费
  • 运维命令
  • shiro源码分析
  • shiro入门
  • IOC和DI
  • Spring创建Bean
  • Bean生命周期
  • Sping属性注入
  • 异常
  • SpringMVC
  • springCloud
  • Eureka

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 我的OSCHINA
  • 我的CSDN
  • 我的GITHUB
  • 一生太水