(01)Spring MVC之处理异常的两种方式及优先级
项目开发中异常需要统一处理,总的来说有两种方式,一种是实现HandlerExceptionResolver接口,一种是使用@ExceptionHandler注解的方式。其中Spring已经为我们提供了一个实现了HandlerExceptionResolver接口的类SimpleMappingExceptionResolver,有人把它单独列为一种方式,不过我认为方式越少越好,哈哈哈哈哈,下面记录一下Spring MVC处理异常的这两种方式。
1、实现HandlerExceptionResolver接口
1)在spring-context.xml中配置默认的实现类SimpleMappingExceptionResolver
<!-- 默认处理异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 为所有的异常定义默认的异常处理页面,exceptionMappings未定义的异常使用本默认配置 -->
<property name="defaultErrorView" value="page/error/defaultError"/>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"/>
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页文件名作为值,将不同的异常映射到不同的页面上。-->
<property name="exceptionMappings">
<props>
<prop key="IOException">page/error/ioexp</prop>
<prop key="java.sql.SQLException">page/error/sqlexp</prop>
</props>
</property>
</bean>
新建测试类:TestExceptionController.java
package com.sl.controller; import java.io.IOException; import java.sql.SQLException; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/testException") public class TestExceptionController { public static Logger logger=Logger.getLogger(TestExceptionController.class); @RequestMapping("/iOException") @ResponseBody public void getException1() throws IOException { throw new IOException("测试抛出io异常"); } @RequestMapping("/sQLException") @ResponseBody public void getException2() throws SQLException{ throw new SQLException("测试抛出sql异常"); } @RequestMapping("/runtimeException") @ResponseBody public void getRuntimeException(){ throw new RuntimeException("测试抛出runtimeException异常"); } @RequestMapping("/classNotFoundException") @ResponseBody public void getClassNotFoundException(){ try { throw new ClassNotFoundException("测试捕获classNotFoundException异常"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
新建IO异常跳转页面:page/error/ioexp.jsp
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%> <%@ page import="java.lang.Exception" %> <!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"> <title>IO异常页面</title> </head> <body> IO异常页面<br> <% Exception ex = (Exception)request.getAttribute("ex"); %> <H2>Exception: <%= ex.getMessage()%></H2> <P/> <% ex.printStackTrace(new java.io.PrintWriter(out)); %> </body> </html>
新建SQL异常跳转页面:page/error/sqlexp.jsp
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%> <%@ page import="java.lang.Exception" %> <!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"> <title>默认异常页面</title> </head> <body> sql异常页面<br> <% Exception ex = (Exception)request.getAttribute("ex"); %> <H2>Exception: <%= ex.getMessage()%></H2> <P/> <% ex.printStackTrace(new java.io.PrintWriter(out)); %> </body> </html>
新建默认异常跳转页面,没有指定跳转页面的异常跳转到该页面:page/error/defaultError.jsp
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%> <%@ page import="java.lang.Exception" %> <!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"> <title>默认异常页面</title> </head> <body> 默认异常页面<br> <% Exception ex = (Exception)request.getAttribute("ex"); %> <H2>Exception: <%= ex.getMessage()%></H2> <P/> <% ex.printStackTrace(new java.io.PrintWriter(out)); %> </body> </html>
测试:http://localhost:8080/spring-web/testException/iOException
测试:http://localhost:8080/spring-web/testException/sQLException
测试:http://localhost:8080/spring-web/testException/runtimeException
测试:http://localhost:8080/spring-web/testException/classNotFoundException,捕获异常,不会跳转页面
2)自定义实现HandlerExceptionResolver接口
新建类:CustomExceptionHandler
package com.core.exception; import java.io.IOException; import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; @Component public class CustomExceptionHandler implements HandlerExceptionResolver{ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object object, Exception exception) { request.setAttribute("ex",exception); if(exception instanceof IOException){ return new ModelAndView("page/error/ioexp2"); }else if(exception instanceof SQLException){ return new ModelAndView("page/error/sqlexp2"); } return new ModelAndView("page/error/defaultError2"); } }
新建IO异常跳转页面:page/error/ioexp2.jsp,内容一样,“IO异常页面”字样改为“IO异常页面_2”以示区别。
新建SQL异常跳转页面:page/error/sqlexp2.jsp,内容一样,“sql异常页面”字样改为“sql异常页面_2”以示区别。
新建默认异常跳转页面:page/error/defaultError2.jsp,内容一样,“默认异常页面”字样改为“默认异常页面_2”以示区别。
测试:http://localhost:8080/spring-web/testException/iOException
测试:http://localhost:8080/spring-web/testException/sQLException
测试:http://localhost:8080/spring-web/testException/runtimeException
测试:http://localhost:8080/spring-web/testException/classNotFoundException,捕获异常,不会跳转页面
2、使用@ExceptionHandler注解方式
1)测试在本Controller中抛出异常统一处理
新建测试类 ExceptionHandlerController.java
package com.core.exception; import java.io.IOException; import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/test") public class ExceptionHandlerController { @ExceptionHandler(value={Exception.class}) public String exp3(Exception ex,HttpServletRequest request) { request.setAttribute("ex", ex); return "page/error/defaultError3"; } @ExceptionHandler(value={IOException.class}) public String exp(Exception ex,HttpServletRequest request) { request.setAttribute("ex", ex); return "page/error/ioexp3"; } @ExceptionHandler(value={SQLException.class}) public String exp2(Exception ex,HttpServletRequest request) { request.setAttribute("ex", ex); return "page/error/sqlexp3"; } @RequestMapping("/iOException") @ResponseBody public void getException1() throws IOException { throw new IOException("测试抛出io异常"); } @RequestMapping("/sQLException") @ResponseBody public void getException2() throws SQLException{ throw new SQLException("测试抛出sql异常"); } @RequestMapping("/runtimeException") @ResponseBody public void getRuntimeException(){ throw new RuntimeException("测试抛出runtimeException异常"); } @RequestMapping("/classNotFoundException") @ResponseBody public void getClassNotFoundException(){ try { throw new ClassNotFoundException("测试捕获classNotFoundException异常"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
新建IO异常跳转页面:page/error/ioexp3.jsp,内容一样,“IO异常页面”字样改为“IO异常页面_3”以示区别。
新建SQL异常跳转页面:page/error/sqlexp3.jsp,内容一样,“sql异常页面”字样改为“sql异常页面_3”以示区别。
新建默认异常跳转页面:page/error/defaultError3.jsp,内容一样,“默认异常页面”字样改为“默认异常页面_3”以示区别。
测试:http://localhost:8080/spring-web/test/iOException
测试:http://localhost:8080/spring-web/test/sQLException
测试:http://localhost:8080/spring-web/test/runtimeException
测试:http://localhost:8080/spring-web/test/classNotFoundException
2)测试其它Controller中抛出异常统一处理
测试:http://localhost:8080/spring-web/testException/iOException ,发现统一异常处理没起作用
如果用注解的方式处理全局的异常,需要在处理异常所在的Controller上添加注解@ControllerAdvice
下面修改 ExceptionHandlerController.java,添加注解@ControllerAdvice
@ControllerAdvice @Controller @RequestMapping("/test") public class ExceptionHandlerController ... ...
测试外部Controller抛出的异常:http://localhost:8080/spring-web/testException/iOException
测试内部Controller抛出的异常:http://localhost:8080/spring-web/test/iOException
3、测试异常优先级
新增加一种处理异常页面跳转的方式,在web.xml里面配置异常与页面跳转的关系,如下:
<!-- 没有全局异常处理的,则跳到该页面,页面不要放在WEB-INF下,否则无法访问到 --> <error-page> <error-code>500</error-code> <location>/500.jsp</location> </error-page> <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/500.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/404.jsp</location> </error-page>
新建页面:src/main/webapp/404.jsp
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%>
<%@ page import="java.lang.Exception" %>
<!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">
<title>404异常页面</title>
</head>
<body>
<H2>这是404异常页面</H2>
</body>
</html>
新建页面:src/main/webapp/500.jsp
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%>
<%@ page import="java.lang.Exception" %>
<!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">
<title>500异常页面</title>
</head>
<body>
<H2>这是500异常页面</H2>
</body>
</html>
下面测试一下这几种异常跳转页面的优先级,我们细分了5种:
a、配置了SimpleMappingExceptionResolver
b、自定义了HandlerExceptionResolver的实现类CustomExceptionHandler
c、新建类ExceptionHandlerController,使用注解@ExceptionHandler处理该类的内部异常
d、新建类ExceptionHandlerController,使用注解@ExceptionHandler和@ControllerAdvice处理所有类的异常
e、web.xml中配置异常码或异常类与错误页面的关系
下面开始测试:
首先找满足5种情况的测试:http://localhost:8080/spring-web/test/iOException,进入到io异常页面_3,可见内部加注解方式优先级最高。
然后找除了上面的一种,只满足4条的测试:http://localhost:8080/spring-web/testException/iOException,外部全局注解的方式第2
然后找除了上面的两种,只满足3条的测试,去掉ExceptionHandlerController的@ControllerAdvice注解,执行下面的测试
http://localhost:8080/spring-web/testException/iOException ,进入到IO异常页面_2,可见自定义异常实现接口第3。
然后找除了上面的三种,只满足2条的测试,去掉自定义异常CustomExceptionHandler的注解@Component,执行下面的测试,
http://localhost:8080/spring-web/testException/iOException ,进入到IO异常页面,可见SimpleMappingExceptionResolver第4。
然后找除了上面的四种,只满足1条的测试,去掉spring-context.xml中配置的异常SimpleMappingExceptionResolver,执行下面的测试,
http://localhost:8080/spring-web/testException/iOException ,进入到500页面,可见web.xml中的配置第5。
最后,去掉web.xml中的配置,再次测试http://localhost:8080/spring-web/testException/iOException,出现了tomcat经典500异常界面:
综上所述:
使用@ExceptionHandler处理本Controller内部异常优先级最高;
使用@ExceptionHandler+@ControllerAdvice处理外部Controller异常优先级第二;
自定义了实现了HandlerExceptionResolver接口的类优先级第三;
spring-context.xml中配置SimpleMappingExceptionResolver优先级第四;
web.xml配置error-page优先级第五;
不做任何处理,会跳转到tomcat默认的异常页面;