【稀里糊涂学springmvc】HandlerExceptionResolver

 

HandlerExceptionResolver是什么?
用大白话说,在springmvc中,HandlerExceptionResolver就是用来处理我们controller(handler)中抛出的异常的。从何而知?看下面源码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 调用controller中处理请求的方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        }catch (Exception ex) {
            // 如果controller的处理请求的方法中抛出异常,便会在这被扑捉
            dispatchException = ex;
        }
    // 这个方法中发现dispatchException !==null,则会去看异常属于哪个
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

看过我前面文章的都应该知道,ha.handle方法便是调用我们controller中处理请求方法的地方,那么如果这时我们controller中抛出异常,就会在这里被catch住,就会把异常赋值给dispatchException属性。然后在processDispatchResult方法中,处理这个异常,我们看这个方法的原码,如下:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
    if (exception != null) {
        // 当controller抛出异常时,便会调用这里
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
    }
    ...其他部分省略...		
}

从源码很清晰的看出,如果exception不为空,那就直接通过processHandlerException方法来处理这个异常,源码如下:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) throws Exception {
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);	
	}
}

到这里,是不是发现我们本章的猪脚HandlerExceptionResolver 出现了吧,HandlerExceptionResolver 顾名思义:异常解析器,你把一个异常扔到我的方法中,我便会处理这个异常。
从源码中的for语句我们发现,这个HandlerExceptionResolver 会有好多个,springmvc提供了大约有哪些呢?看下图:


至于handlerExceptionResolvers里面会有哪些resolver,会根据情况而定。当通过遍历拿到每个handlerExceptionResolver后,便调用resolveException方法去处理解析,那么这个异常是不是当前这个解析器处理的范围呢,这个可以通过在方法中自己进行判断。比如springmvc提供的AbstractHandlerExceptionResolver这个类,看下图的例子:

AbstractHandlerExceptionResolver的resolveException方法中便通过shouldApplyTo方法来判断是不是有自己来处理这个异常。如果是,那么就调用方法处理这个异常,并返回结果。这个结果就会是将在在页面上显示的东西了。

到此为止,关于HandlerExceptionResolver是什么时候执行的,怎么执行的的整个过程就结束了,是不是很简单吧^^。

下面,我将在通过一个实例,来给大家剖析一下:
这个例子的效果就是:
我在controller中抛出一个HttpRequestMethodNotSupportedException异常,然后会被容器中的异常解析器来处理这个异常,将对应解析器处理的结果显示在页面上。
例子如下:
首先,我在controller中抛出HttpRequestMethodNotSupportedException异常,代码如下:

@Controller
public class RequestMappingHandlerMappingController {
	@RequestMapping("/hello")
	@ResponseBody
	public String helloAndView() throws  HttpRequestMethodNotSupportedException{
		int a = 2;
		if(a==2) {
			throw new HttpRequestMethodNotSupportedException("oh,no");
		}
		return "hello";
	}
}

然后,我发送请求http://localhost:8088/hello 进行处理,下面通过截来展示请求发起后到最后处理这个异常并展示的整个流程。
 第一步:DispatcherServlet的doDispatch方法会找到我们的这个controller作为handler来处理请求,当进入controller的helloAndView方法后,通过判断直接抛出我们的异常。
第二步:进入processDispatchResult方法来处理方法的返回结果(源码都在上面有,自己看),因为我们抛异常了,所以就会进出入processHandlerException方法来处理异常,这时便会从容器中将所有的异常解析器拿出来,然后遍历来让各个解析器来决定是否有自己来处理这个异常,此时容器中会有那些解析器呢,看下图:

经过遍历发现,我们controller抛出的HttpRequestMethodNotSupportedException异常由DefaultHandlerExceptionResolver来处理,为什么呢,看下图:

DefaultHandlerExceptionResolver的doResolveException方法中,经判断我们抛出的异常,正好符合它的条件,所有就会由这个resolver来处理这个异常,他是怎么处理的呢?然后会把一个什么结果显示在页面呢?看下面:

看到了吧,这里直接把我们的异常信息进行包装,然后通过response直接输出到页面了。虽然最后return了新的ModelAndView,但这个ModelAndView因为里面没东西,在后面的程序中会被处理掉。这里可以忽略。重点在于return之前的response.sendError方法,要知道这里便是输出我们页面看到的内容的地方。
最终页面的样子如下:

观察红框内容,是不是有我们在controller中抛出的异常信息。
到这里,对于HandlerExceptionResolver应该清楚多了吧。

凡事要做到举一反三,也许有人会想,现在用的是springmvc自带的,如果想自定义一个HandlerExceptionResolver来处理我们的某些指定的异常,然后用我们自定义的页面来展示我们对异常的处理结果,该怎么做?下面,我们就通过时下HandlerExceptionResolver接口,来自定义我们的Resolver,并通过我们自己的ModeAndView来显示处理的结果。如下走起:
第一步:自定义一个实现了HandlerExceptionResolver接口的类,后面会用他来处理我们controller抛出的异常:


public class MyHandlerExceptionResolver implements HandlerExceptionResolver,Ordered{
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
        // 如果是io异常,那么就由我们这个resolver来处理,容器中其他resolver中都没有处理这个异常的解析器,如果有的话,那么就就需要看谁先执行了
		if (ex instanceof IOException) {
            // 设置通过exception.jsp页面来展示对异常的处理结果
			ModelAndView modelAndView = new ModelAndView("exception");
            // exception.jsp页面会打印下面这段话
			modelAndView.addObject("message", "i am a custom exception resolver page");
			return modelAndView;
		}
		return null;
	}

	/**
	 * 如果想优先让我们自定义的异常解析器来处理异常,就实现Ordered接口,然后内容如下写法
	 */
	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}
	
}

第二步:在spring-mvc.xml中声明这个类,然后放到容器中

<bean class="com.lhb.MyHandlerExceptionResolver"/>

第三步:创建exception.jsp页面(名字随便起,只要跟上面代码的ModeAndView中的参数值相同就行)

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<span>${message}</span><br/>
</body>
</html>

在这个页面中,将会把MyHandlerExceptionResolver 中设置的message的值显示出来。
第四步:在我们的controller中抛出IO异常,如下:

@Controller
public class RequestMappingHandlerMappingController {
	
	@RequestMapping("/hello")
	@ResponseBody
	public String helloAndView() throws  IOException{
		int a = 2;
		if(a==2) {
			throw new IOException("oh,no");
		}
		return "hello";
	}
}

第五步:上面代码已就绪,现在访问http://localhost:8088/hello 然后来看调用的过程,如下:
程序同样DispatcherServlet的doDispatch方法,跑到controller的方法中,然后抛出异常,在进入到processDispatchResult方法中的processHandlerException方法,如下:

 

注意观察红框处,是不是发现我们自定义的HandlerExceptionResolver了。这里经过排序,这样就可以先使用我们自定义的异常解析类的resolveException方法中(如何排序在第一位,实现Ordered接口即可,代码以实现)。如下:

因为我们controller中抛出的是IOException,满足自定义resolver中的条件,所以就有我们这个自定义类来进行处理,并且自定义了通过那个页面来展示处理结果。这里我们指定在exception.jsp页面展示结果。当然你也可以进行更复杂的设定,比如可以通过handler的判断来决定是通过页面展示处理结果,还是通过response直接输出一些东西。经过这一连串处理,边跳转到页面,并显示我么指定的信息,如下:

这波操作后,对于HandlerExceptionResolver应该都明白了吧?再不明白请留言。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
在章节的最后出,我再把springmvc自带的那些HandlerExceptionResolver来给大家简单过一遍,来,走起:

  • ExceptionHandlerExceptionResolver
    当抛出异常的controller中找含有@ExceptionHandler注解的方法时(注解中的参数必须跟抛出异常的类型相同),那么这个Resolver便会生效,这个Resolver便会调用哪个controller中的含有@ExceptionHandler的方法。
    如果在controller中找不到,则会去@ControllerAdvice注解的类中去找@ExceptionHandler注解的方法,如果有,并且跟这个这个注解里的参数跟抛出异常相同,便会调用这个注解的方法来处理异常。看下面例子:
    @Controller
    public class RequestMappingHandlerMappingController {
    	@RequestMapping("/hello")
    	@ResponseBody
    	public String helloAndView() throws  IOException{
    		int a = 2;
    		if(a==2) {
    			throw new IOException("oh,no");
    		}
    		return "hello";
    	}
    	
    	@ExceptionHandler(IOException.class)
    	@ResponseBody
    	public String handleException() {
    		System.out.println();
    		return "aa";
    	}
    }

    代码中通过@ExceptionHandler(IOException.class)注解来说明这个方法专门用来处理IOException异常的,所以会被ExceptionHandlerExceptionResolver来处理,并调用这个方法来处理异常。然后返回aa,并将aa显示在浏览器上。
    这里要说一下关于这个处理异常的返回值,这里我么返回的是字符串aa,其实这里我们还可以返回jsp页面,也可以返回ModelAndView对象。从源码上看,调用这个controller中处理异常的方法与调用普通controller中的方法最终使用的代码是相同的。都是通过ServletInvocableHandlerMethod的invokeAndHandle方法,最后通过反射调用我们controller中的方法,然后在获取到返回值,再根据返回值设置view来显示。如下:

    如果你不想把@ExceptionHandler注解的方法写在当前controller中,可以通过专门写一个@ControllerAdvice注解的类,然后在这个类用@ExceptionHandler来写一些方法,然后专门来负责处理相关的异常。之所以可以这样做,是因为源码中明确说明,当当前controller中没有能处理相关异常的@ExceptionHandler注解的方法时,便会逐层向上去找@ControllerAdvice注解的类中是否存在相关的处理对应异常的@ExceptionHandler注解的方法,所以我们可以把所以想要处理的异常都单独写在一个用@ControllerAdvice注解的类中,如下:

    @ControllerAdvice
    public class MyControllerAdvice {
    	@ExceptionHandler(IOException.class)
    	@ResponseBody
    	public String handleException() {
    		System.out.println();
    		return "aa";
    	}
        
        // 处理MyException异常方法,
        @ExceptionHandler(MyException.class)
        @ResponseBody
        public String handleException1(){
        }
        
        ...其他处理异常的方法....
        @ExceptionHandler(OtherException.class)
        @ResponseBody
        public String handleExceptionOther(){
        }
    }

    这样我们就可以删除掉上线那个controller中的了。效果都是一样的。下面看一下浏览器的效果:

  • ResponseStatusExceptionResolver
    当我们的handler(controller)抛出异常时,如果这个异常时自定义异常,并且异常类上有@ResponseStatus注解,那么这个类便会使用到,这个Resolver会获取到@ResponseStatus中的属性值,然后将这个值显示在页面上。
    另外,这个@ResponseStatus注解还可以配合上面的@ExceptionHandle一起使用,这样的话当抛出的异常与@ExceptionHandle中指定的异常匹配时,便会把相对应的@ResponseStatus中的内容返回到页面上。
    例子如下:

    @ResponseStatus(value=HttpStatus.FORBIDDEN, reason="请求的网页不存在")
    public class MyException extends RuntimeException{
    
    }
    @Controller
    public class RequestMappingHandlerMappingController {
    	@RequestMapping("/hello")
    	@ResponseBody
    	public String helloAndView() throws  IOException{
    		int a = 2;
    		if(a==2) {
    			 throw new MyException();
    		}
    		return "hello";
    	}
    }

    效果如下:

    这样当controller抛出异常后,就可以直接将@ResponseStatus中的reason和code显示在页面上了。
     

  • DefaultHandlerExceptionResolver

    当我们handler抛出的异常,属于这个Resolver中的某一个是,那么便会通过这个类来处理异常,并返回给页面(或者其他客户端)。给大家看一下源码明白了,如下:

    注意红框部分,我们最开始的时候的例子就是通过抛出这个异常,然后最后使用这个resolver来处理这个异常的。这里再把例子粘贴一次:

    @Controller
    public class RequestMappingHandlerMappingController {
    	@RequestMapping("/hello")
    	@ResponseBody
    	public String helloAndView() throws  HttpRequestMethodNotSupportedException{
    		int a = 2;
    		if(a==2) {
    			throw new HttpRequestMethodNotSupportedException("oh,no");
    		}
    		return "hello";
    	}
    }

    效果如下:
     

  • SimpleMappingExceptionResolver
    通过xml将抛出的异常和出现异常时显示的页面进行映射,当出现对应的异常时,直接会跳转到对应的页面。例子如下:

    spring-mvc.xml
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <!-- 指定所有没有指定的异常,都跳转到该页面
            <property name="defaultErrorView" value="/defaultException" /> -->
            <!-- 跳转时携带异常对象 -->
            <property name="exceptionAttribute" value="ex"></property>
             <property name="exceptionMappings">  
                <props>  
                    <prop key="java.lang.ArithmeticException">/defaultException</prop>  
                    <prop key="java.sql.SQLException">/defaultException</prop>  
                </props>  
            </property>  
        </bean> 

    controller

    @Controller
    @RequestMapping("/view")
    public class ViewController {
    	@RequestMapping("/hellojsp")
    	public String toHelloJsp() {
    		int i = 3 / 0;
    		return "hello";
    	}
    }
    

    defaultException.jsp

    <%@ page language="java" contentType="text/html; charset=GBK"
        pageEncoding="GBK"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>    
    <%
        String path = request.getContextPath();
        String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=GBK">
    <base href="<%=basePath %>">
    <title>DefaultExceptionPage</title>
    </head>
    <body>
     ERROR! DefaultExceptionPage
    </body>
    </html>

    测试:浏览器访问:http://localhost:8087/view/hellojsp

到这里,关于HandlerExceptionResolver的讲解就结束了。如果大家还有不清楚了的,或者想了解的,请留言!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值