Spring Mvc异常处理对于确保不向客户机发送服务器异常非常重要。今天我们将使用@ExceptionHandler、@ControllerAdvice和HandlerExceptionResolver研究Spring异常处理。任何web应用程序需要良好的异常处理设计,因为当应用程序抛出任何未经处理的异常时,我们不想为容器生成的页面提供服务。
目录
Spring异常处理
对于任何web应用程序框架来说,拥有一个定义良好的异常处理方法都是一个巨大的优势Spring MVC框架在我们的web应用程序中,当涉及到异常和错误处理时,提供了很好的服务。
Spring Mvc框架提供了以下方法来帮助我们实现健壮的异常处理。
- 基于控制器我们可以在控制器类中定义异常处理程序方法。我们只需要用
@ExceptionHandler
annotation. This annotation takes Exception class as argument. So if we have defined one of these for Exception class, then all the exceptions thrown by our request handler method will have handled.这些异常处理程序方法与其他请求处理程序方法一样,我们可以构建错误响应,并使用不同的错误页面进行响应。我们还可以发送JSON错误响应,稍后将在我们的示例中看到。
如果定义了多个异常处理程序方法,则使用最接近异常类的处理程序方法。例如,如果我们为IOException和Exception定义了两个处理程序方法,并且我们的请求处理程序方法抛出IOException,那么IOException的处理程序方法将被执行。
- 全局异常处理程序异常处理是一个横切关注点,应该对应用程序中的所有切入点进行处理。我们已经调查过了面向切面编程这就是为什么Spring
@ControllerAdvice
annotation that we can use with any class to define our global exception handler.全局控制器通知中的处理程序方法与基于控制器的异常处理程序方法相同,并在控制器类无法处理异常时使用。
- 手柄异常解析程序对于一般的异常,大多数时候我们提供静态页面。Spring框架提供
HandlerExceptionResolver
interface that we can implement to create global exception handler. The reason behind this additional way to define global exception handler is that Spring framework also provides default implementation classes that we can define in our spring bean configuration file to get spring framework exception handling benefits.SimpleMappingExceptionResolver
is the default implementation class, it allows us to configure exceptionMappings where we can specify which resource to use for a particular exception. We can also override it to create our own global handler with our application specific changes, such as logging of exception messages.
让我们创建一个Spring Mvc项目,在这个项目中,我们将研究基于控制器、基于AOP和基于异常解析器的异常和错误处理方法的实现。我们还将编写一个异常处理程序方法来返回JSON响应。如果您不熟悉JSON,请阅读springrestfuljson教程.
我们的最终项目将如下图所示,我们将逐一查看应用程序的所有组件。
Spring异常处理Maven依赖项
除了标准的Spring Mvc依赖项之外,我们还需要杰克逊JSONJSON支持的依赖项。
我们的决赛pom.xml文件文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://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>
我已经更新了springframework、AspectJ、Jackson和slf4j版本以使用最新版本。
Spring Mvc异常处理部署描述符
我们的web.xml文件文件如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://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框架。所以当我们的应用程序抛出404错误时,这个页面将被用作响应。当我们的springweb应用程序抛出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响应,所以让’;s创建一个javabean,其中包含将作为响应发送的异常详细信息。
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
annotation with exception classes to define the HTTP code that will be sent by our application when this type of exception is thrown by our application and handled by our exception handling implementations.
如您所见,我正在将HTTP status设置为404,并且我们为此定义了一个错误页,因此,如果我们没有返回任何视图,那么我们的应用程序应该为这种类型的异常使用错误页。
我们还可以重写异常处理程序方法中的状态代码,当异常处理程序方法未将任何视图页作为响应返回时,将其视为默认的http状态代码。
SpringMVC异常处理控制器类异常处理程序
让我们看看控制器类,在这里我们将抛出不同类型的异常。
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发送。我们将在全局异常处理程序实现中研究这种类型的实现。
因为我只处理controller中的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,我返回的是数据库_错误.jsp作为响应页,http状态代码为200。
对于IOException,我们返回的是void,状态代码为404,因此我们的错误页面将在本例中使用。
正如您所看到的,这里我没有处理任何其他类型的异常,这部分我留给HandlerExceptionResolver实现。
手柄异常解析程序
我们只是扩展SimpleMappingExceptionResolver并重写其中一个方法,但我们可以重写它最重要的方法resolveException
for logging and sending different types of view pages. But that is same as using ControllerAdvice implementation, so I am leaving it. We will be using it to configure view page for all the other exceptions not handled by us by responding with generic error page.
Spring异常处理配置文件
我们的Spring Bean配置文件如下所示。
spring.xml文件代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://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>
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="simpleMappingExceptionResolver">
<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>
<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">
</beans:bean>
<context:component-scan base-package="com.journaldev.spring" />
</beans:beans>
请注意,在我们的web应用程序中,为支持JSON而配置的bean。唯一与异常处理相关的部分是simpleMappingExceptionResolver bean定义,我们在其中定义泛型_错误.jsp作为异常类的视图页。这将确保应用程序未处理的任何异常都不会导致发送服务器生成的错误页作为响应。
Spring Mvc异常处理JSP视图页面
现在是时候看看我们的应用程序的最后一部分了,我们将在应用程序中使用的查看页面。
主页.jsp代码:
<%@ taglib uri="https://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>
主页.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" "https://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>
jsp用于生成404 http状态代码的视图,对于我们的实现,这应该是当我们在客户端请求中得到id为3时的响应。
错误.jsp代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<%@ taglib uri="https://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>
错误.jsp在控制器类请求处理程序方法引发EmployeeNotFoundException时使用。当客户端请求中的id值为1时,我们应该得到这个页面作为响应。
数据库_错误.jsp代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://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>
数据库_错误.jsp在我们的应用程序抛出SQLException时使用,如GlobalExceptionHandler类中配置的那样。当客户端请求中的id值为2时,我们应该将此页面作为响应。
通用_错误.jsp代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://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容器中,本例中我使用的是apachetomcat7。
下图显示了应用程序基于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响应,如下图所示。