Spring MVC异常处理 - @ ControllerAdvice,@ ExceptionHandler,HandlerExceptionResolver
Spring MVC异常处理对于确保您不向客户端发送服务器异常非常重要。今天我们将使用@ExceptionHandler,@ ControllerAdvice和HandlerExceptionResolver来研究Spring异常处理。任何Web应用程序都需要良好的异常处理设计,因为当我们的应用程序抛出任何未处理的异常时,我们不希望提供容器生成的页面。
目录[ 隐藏 ]
Spring异常处理
对于任何Web应用程序框架而言,具有良好定义的异常处理方法是一个巨大的优势,即当我们的Web应用程序中的异常和错误处理时,Spring MVC框架可以很好地提供。
Spring MVC Framework提供了以下方法来帮助我们实现强大的异常处理。
- Controller Based - 我们可以在控制器类中定义异常处理程序方法。我们所需要的只是使用
@ExceptionHandler
注释来注释这些方法。此批注将Exception类作为参数。因此,如果我们为Exception类定义了其中一个,那么我们的请求处理程序方法抛出的所有异常都将被处理。这些异常处理程序方法就像其他请求处理程序方法一样,我们可以构建错误响应并响应不同的错误页面。我们还可以发送JSON错误响应,我们将在后面的示例中查看。
如果定义了多个异常处理程序方法,则使用最接近Exception类的处理程序方法。例如,如果我们为IOException和Exception定义了两个处理程序方法,并且我们的请求处理程序方法抛出IOException,那么将执行IOException的处理程序方法。
- 全局异常处理程序 - 异常处理是一个跨领域的问题,应该对我们的应用程序中的所有切入点进行处理。我们已经研究过Spring AOP,这就是为什么Spring提供
@ControllerAdvice
了我们可以用于任何类来定义我们的全局异常处理程序的注释。Global Controller Advice中的处理程序方法与基于Controller的异常处理程序方法相同,并在控制器类无法处理异常时使用。
- HandlerExceptionResolver - 对于一般异常,大多数时候我们提供静态页面。Spring Framework提供
HandlerExceptionResolver
了我们可以实现的接口来创建全局异常处理程序。这种定义全局异常处理程序的额外方法背后的原因是Spring框架还提供了我们可以在spring bean配置文件中定义的默认实现类,以获得spring框架异常处理的好处。SimpleMappingExceptionResolver
它是默认的实现类,它允许我们配置exceptionMappings,我们可以在其中指定用于特定异常的资源。我们还可以覆盖它以使用我们的应用程序特定更改创建我们自己的全局处理程序,例如记录异常消息。
让我们创建一个Spring MVC项目,我们将在其中研究基于Controller,基于AOP和基于异常解析器的异常和错误处理方法的实现。我们还将编写一个异常处理程序方法,该方法将返回JSON响应。如果您是Spring的JSON新手,请阅读Spring Restful JSON Tutorial。
我们的最终项目如下图所示,我们将逐一查看应用程序的所有组件。
Spring异常处理Maven依赖项
除了标准的Spring MVC依赖项之外,我们还需要Jackson JSON依赖项来支持JSON。
我们的最终pom.xml文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>SpringExceptionHandling</artifactId>
<name>SpringExceptionHandling</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
<jackson.databind-version>2.2.3</jackson.databind-version>
</properties>
<dependencies>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.databind-version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
我已经更新了Spring Framework,AspectJ,Jackson和slf4j版本以使用最新版本。
Spring MVC异常处理部署描述符
我们的web.xml文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<error-page>
<error-code>404</error-code>
<location>/resources/404.jsp</location>
</error-page>
</web-app>
除了为404错误定义的错误页面之外,大部分内容是为我们的Web应用程序插入Spring Framework。因此,当我们的应用程序将抛出404错误时,此页面将用作响应。当我们的spring Web应用程序抛出404错误代码时,容器使用此配置。
Spring异常处理 - 模型类
我已经将Employee bean定义为模型类,但是我们将在我们的应用程序中使用它只是为了在特定场景中返回有效响应。在大多数情况下,我们会故意抛出不同类型的例外。
package com.journaldev.spring.model;
public class Employee {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
由于我们也将返回JSON响应,让我们创建一个带有异常详细信息的java bean,它将作为响应发送。
package com.journaldev.spring.model;
public class ExceptionJSONInfo {
private String url;
private String message;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Spring异常处理 - 自定义异常类
让我们创建一个我们的应用程序使用的自定义异常类。
package com.journaldev.spring.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Employee Not Found") //404
public class EmployeeNotFoundException extends Exception {
private static final long serialVersionUID = -3332292346834265371L;
public EmployeeNotFoundException(int id){
super("EmployeeNotFoundException with id="+id);
}
}
请注意,我们可以使用@ResponseStatus
带有异常类的注释来定义我们的应用程序在我们的应用程序抛出此类异常并由异常处理实现处理时将发送的HTTP代码。
正如您所看到的,我将HTTP状态设置为404并且我们为此定义了错误页面,因此如果我们不返回任何视图,我们的应用程序应该使用错误页面来处理此类异常。
我们还可以覆盖异常处理程序方法中的状态代码,当我们的异常处理程序方法没有将任何视图页面作为响应返回时,将其视为默认的http状态代码。
Spring MVC异常处理控制器类异常处理程序
让我们看看我们的控制器类,我们将抛出不同类型的异常。
package com.journaldev.spring.controllers;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.journaldev.spring.exceptions.EmployeeNotFoundException;
import com.journaldev.spring.model.Employee;
import com.journaldev.spring.model.ExceptionJSONInfo;
@Controller
public class EmployeeController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
@RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{
//deliberately throwing different types of exception
if(id==1){
throw new EmployeeNotFoundException(id);
}else if(id==2){
throw new SQLException("SQLException, id="+id);
}else if(id==3){
throw new IOException("IOException, id="+id);
}else if(id==10){
Employee emp = new Employee();
emp.setName("Pankaj");
emp.setId(id);
model.addAttribute("employee", emp);
return "home";
}else {
throw new Exception("Generic Exception, id="+id);
}
}
@ExceptionHandler(EmployeeNotFoundException.class)
public ModelAndView handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
logger.error("Requested URL="+request.getRequestURL());
logger.error("Exception Raised="+ex);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", ex);
modelAndView.addObject("url", request.getRequestURL());
modelAndView.setViewName("error");
return modelAndView;
}
}
请注意,对于EmployeeNotFoundException处理程序,我返回ModelAndView,因此http状态代码将被发送为OK(200)。如果它返回void,那么http状态代码将被发送为404.我们将在我们的全局异常处理程序实现中研究这种类型的实现。
由于我只在控制器中处理EmployeeNotFoundException,因此我们的控制器抛出的所有其他异常将由全局异常处理程序处理。
@ControllerAdvice和@ExceptionHandler
这是我们的全局异常处理程序控制器类。请注意,该类使用@ControllerAdvice批注进行批注。方法也使用@ExceptionHandler注释进行注释。
package com.journaldev.spring.controllers;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(SQLException.class)
public String handleSQLException(HttpServletRequest request, Exception ex){
logger.info("SQLException Occured:: URL="+request.getRequestURL());
return "database_error";
}
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="IOException occured")
@ExceptionHandler(IOException.class)
public void handleIOException(){
logger.error("IOException handler executed");
//returning 404 error code
}
}
请注意,对于SQLException,我将database_error.jsp作为响应页面返回,其http状态代码为200。
对于IOException,我们返回void,状态代码为404,因此在这种情况下将使用我们的错误页面。
正如您所看到的,我在这里没有处理任何其他类型的异常,我已经为HandlerExceptionResolver实现了这一部分。
HandlerExceptionResolver
我们只是扩展SimpleMappingExceptionResolver并覆盖其中一个方法,但我们可以覆盖它最重要的方法resolveException
来记录和发送不同类型的视图页面。但这与使用ControllerAdvice实现相同,所以我要离开它。我们将使用它来配置我们通过响应一般错误页面而未处理的所有其他异常的视图页面。
Spring异常处理配置文件
我们的spring bean配置文件如下所示。
spring.xml代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="simpleMappingExceptionResolver" class="com.journaldev.spring.resolver.MySimpleMappingExceptionResolver">
<beans:property name="exceptionMappings">
<beans:map>
<beans:entry key="Exception" value="generic_error"></beans:entry>
</beans:map>
</beans:property>
<beans:property name="defaultErrorView" value="generic_error"/>
</beans:bean>
<!-- Configure to plugin JSON as request and response in method handler -->
<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<beans:property name="messageConverters">
<beans:list>
<beans:ref bean="jsonMessageConverter"/>
</beans:list>
</beans:property>
</beans:bean>
<!-- Configure bean to convert JSON to POJO and vice versa -->
<beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</beans:bean>
<context:component-scan base-package="com.journaldev.spring" />
</beans:beans>
请注意配置为在我们的Web应用程序中支持JSON的bean。与异常处理相关的唯一部分是simpleMappingExceptionResolver bean定义,我们将generic_error.jsp定义为Exception类的视图页面。这样可以确保我们的应用程序未处理的任何异常都不会导致将服务器生成的错误页面作为响应发送。
Spring MVC异常处理JSP视图页面
现在是时候研究我们的应用程序的最后一部分,我们的视图页面将在我们的应用程序中使用。
home.jsp代码:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h3>Hello ${employee.name}!</h3><br>
<h4>Your ID is ${employee.id}</h4>
</body>
</html>
home.jsp用于响应有效数据,即当我们在客户端请求中获得id为10时。
404.jsp代码:
<%@ 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=UTF-8">
<title>404 Error Page</title>
</head>
<body>
<h2>Resource Not Found Error Occured, please contact support.</h2>
</body>
</html>
404.jsp用于生成404 http状态代码的视图,对于我们的实现,这应该是我们在客户端请求中获取id为3时的响应。
error.jsp代码:
<%@ 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">
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Error Page</title>
</head>
<body>
<h2>Application Error, please contact support.</h2>
<h3>Debug Information:</h3>
Requested URL= ${url}<br><br>
Exception= ${exception.message}<br><br>
<strong>Exception Stack Trace</strong><br>
<c:forEach items="${exception.stackTrace}" var="ste">
${ste}
</c:forEach>
</body>
</html>
当我们的控制器类请求处理程序方法抛出EmployeeNotFoundException时,使用error.jsp。当客户端请求中的id值为1时,我们应该将此页面作为响应。
database_error.jsp代码:
<%@ 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=UTF-8">
<title>Database Error Page</title>
</head>
<body>
<h2>Database Error, please contact support.</h2>
</body>
</html>
当我们的应用程序抛出SQLException时,将使用database_error.jsp,如GlobalExceptionHandler类中所配置的那样。当客户端请求中的id值为2时,我们应该将此页面作为响应。
generic_error.jsp代码:
<%@ 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=UTF-8">
<title>Generic Error Page</title>
</head>
<body>
<h2>Unknown Error Occured, please contact support.</h2>
</body>
</html>
当我们的应用程序代码没有处理任何异常并且simpleMappingExceptionResolver bean负责处理时,这应该是页面作为响应。当客户端请求中的id值不是1,2,3或10时,我们应该将此页面作为响应。
运行Spring MVC异常处理应用程序
只需将应用程序部署在您正在使用的servlet容器中,我就在本例中使用Apache Tomcat 7。
下图显示了我们的应用程序根据id值返回的不同响应页面。
ID = 10,有效回复。
ID = 1,使用基于控制器的异常处理程序
ID = 2,全局异常处理程序与视图一起用作响应
ID = 3,使用404错误页面
ID = 4,simpleMappingExceptionResolver用于响应视图
正如您所看到的,我们在所有情况下都获得了预期的响应。
Spring异常处理程序JSON响应
我们几乎完成了我们的教程,除了最后一点,我将解释如何从异常处理程序方法发送JSON响应。
我们的应用程序具有所有JSON依赖项和jsonMessageConverter,我们需要实现异常处理程序方法。
为简单起见,我将重写EmployeeController handleEmployeeNotFoundException()方法以返回JSON响应。
只需使用以下代码更新EmployeeController异常处理程序方法,然后再次部署该应用程序。
@ExceptionHandler(EmployeeNotFoundException.class)
public @ResponseBody ExceptionJSONInfo handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
ExceptionJSONInfo response = new ExceptionJSONInfo();
response.setUrl(request.getRequestURL().toString());
response.setMessage(ex.getMessage());
return response;
}
现在,当我们在客户端请求中使用id为1时,我们得到以下JSON响应,如下图所示。
这就是Spring异常处理和Spring MVC异常处理的全部内容,请从下面的URL下载应用程序并使用它来了解更多信息。