点击阅读原文
Spring提供了一系列健全的异常处理框架。我们可以采用@ResponseStatus,@ExceptionHandler,HandlerExceptionResolver以及 @ControllerAdvice处理异常。@ResponseStatus可以为异常指定状态码,可以应用到用户定义的异常类以及controller中被@ExceptionHandler注解的方法上。在controller中,我们可以通过使用@ExceptionHandler注解异常处理方法,被注解的方法只能在当前controller内使用。Spring还提供了@ControllerAdvice用于全局异常处理,对所有的controller都有效。定义一个全局异常处理类,并且用@ControllerAdvice注解,在类中需要定义异常处理方法并且用@ExceptionHandler注解。Spring提供了很多种方法处理异常,在XML文件或者java文件中可以使用HandlerExceptionResolver定义异常类型与view名称的映射。下面是一些完整的例子。
@ExceptionHandler
SpringMVC中@ExceptionHandler注解用来处理异常,我们使用这个注解来标注controller方法。如此,被注解的方法可以有Exception,HttpServletReques,HttpServletResponse,Session,WebRequest等任何顺序的参数类型,方法返回值可以是ModelAndView, Model,Map, View, String, @ResponseBody甚至void类型。返回void类型时, 我们也可以利用HttpServletResponse重新定向。
@ResponseStatus
@ResponseStatus可以用在定义的类或者controller方法上,它包含两个元素,value和reason。Value用于设置response的状态码,例如404,200等,reason用于响应,可以是内容语句。
HandlerExceptionResolver
HandlerExceptionResolver是各个执行时异常处理的接口,它有各种各样的实现,例如ExceptionHandlerExceptionResolver, HandlerExceptionResolverComposite,,SimpleMappingExceptionResolver等。在例子中,我们采用了SimpleMappingExceptionResolver。它将异常与一个view名称映射,因此它可以展现error页面适用于任何error类型。
@ControllerAdvice
@ControllerAdvice注解是在classpath扫描时自动检测的,java配置时我们须要使用@EnableWebMvc。Spring中使用@ControllerAdvice与@ExceptionHandler做全局异常处理,使用@ControllerAdvice注解定义的类,@ExceptionHandler注解定义类中的方法。底下的例子中,我们使用@ControllerAdvice做全局异常处理。
运行底下的demo,需要的软件环境:
1.Java 7
2.Tomcat 8
3.Eclipse
4.Gradle
5.Spring 4
Eclipse中的工程结构图:
Jar依赖文件的Gradle配置:build.gradle
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'war'
archivesBaseName = 'concretepage'
version = '1'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web:1.2.2.RELEASE'
compile 'jstl:jstl:1.2'
providedCompile 'org.springframework.boot:spring-boot-starter-tomcat:1.2.2.RELEASE'
}
例子开始啦,注意注意注意!
为了能够采用@ResponseStatus处理异常,我们必须使用ResponseStatus注解我们定义的异常,并且声明状态码和reason;
KeywordNotFoundException.java
package com.concretepage.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Keyword")
public class KeywordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public KeywordNotFoundException(String key){
super(key+" not available");
}
}
无论我们在应用的任何地方抛出这个异常,404状态码和reason定义的message将会被接收到,在controller中,抛出这个异常,如下:
package com.concretepage.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.concretepage.exception.KeywordNotFoundException;
@Controller
@RequestMapping("/keyword")
public class KeywordController {
@RequestMapping("/info")
public String info(@RequestParam(value="key") String key, Model model) {
if ("key101".equals(key)) {
model.addAttribute("msg", "Hello Key World!");
} else {
throw new KeywordNotFoundException(key);
}
return "success";
}
}
KeywordNotFoundException异常抛出后会返回404状态码,全局的异常处理机制会抓取到这个状态码并且处理。否则,404状态码会和@ResponseStatus中定义的消息一起抛出。部署成功后,访问http://localhost:8080/concretepage-1/keyword/info?key=key1011我们会得到如下界面:
@ExceptionHandler异常处理:
@ExceptionHandler应用在被@controller和@ControllerAdvice注解的controller中的方法上。为了在controller内部处理异常,我们使用这个注解标注方法,如果需要的话也可以同时使用@ExceptionHandle和@ResponseStatus。定义异常处理方法时,同时也定义view名称,异常对象名称等。
MyWorldExceptionController.java
package com.concretepage.controller;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/myworld")
public class MyWorldExceptionController {
@ResponseStatus(value=HttpStatus.CONFLICT, reason="Data already present")
@ExceptionHandler(SQLException.class)
public void dataConflict() {
System.out.println("----Caught SQLException----");
}
@ExceptionHandler(FileNotFoundException.class)
public ModelAndView myError(Exception exception) {
System.out.println("----Caught FileNotFoundException----");
ModelAndView mav = new ModelAndView();
mav.addObject("exc", exception);
mav.setViewName("myerror");
return mav;
}
@RequestMapping("/check")
public String myInfo(@RequestParam(value="id") String id, Model model) throws Exception {
if ("1".equals(id)) {
throw new SQLException();
}else if ("2".equals(id)) {
throw new FileNotFoundException("File not found.");
}else if ("3".equals(id)) {
throw new IOException("Found IO Exception");
}else {
model.addAttribute("msg", "Welcome to My World.");
}
return "success";
}
}
当抛出异常时,Spring针对每一个异常类型搜索对应的异常处理方法来处理异常。如果我们返回值为void时,必须使用@ResponseStatus注解方法。任何的全局异常处理方法会抓取到这个状态码。异常处理的机制是,当前controller内没有异常处理方法的话,该异常会被全局异常处理机制抓取到。
1 访问http://localhost:8080/concretepage-1/myworld/check?id=1
由于SQLEXCEPTION的类型,处理方法返回值类型为void且返回状态码为409。全局异常并没有定义409状态码,因此@ResponseStatus定义的reason和状态码一起显示。
2 访问http://localhost:8080/concretepage-1/myworld/check?id=2
文件找不到异常,我们返回了错误页,该错误页在处理方法中定义了。
myerror.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
Spring MVC Exception
Error : ${exc.message}
${st}
3 访问http://localhost:8080/concretepage-1/myworld/check?id=3
Controller内部没有定义方法捕获IO异常,因此它被全局处理机制抓获。
使用@ControllerAdvice做全局异常处理
为了处理全局异常,spring提供了@ControllerAdvice来注解定义的handler类,类中处理方法用@ExceptionHandler注解。
GlobalExceptionHandler.java
package com.concretepage.controller;
import java.io.IOException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import com.concretepage.exception.KeywordNotFoundException;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IOException.class)
public ModelAndView myError(Exception exception) {
System.out.println("----Caught IOException----");
ModelAndView mav = new ModelAndView();
mav.addObject("exception", exception);
mav.setViewName("globalerror");
return mav;
}
@ExceptionHandler(KeywordNotFoundException.class)
public String notFound() {
System.out.println("----Caught KeywordNotFoundException----");
return "404";
}
}
无论什么时候一个异常被抛出,若它没有被controller内部抓获,它将会被全局异常处理机制抓获。@ControllerAdvice使得全局异常处理对于应用内任何一个controller抛出的异常都可以抓获。这样,spring可以处理具体的error为具体的状态码。举个栗子,我们在全局异常handler类中创建了两个异常处理方法,如下:
globalerror.jsp
Global Error
Error: ${exception.message}
404.jsp
Spring MVC Exception
404 Exception
使用SimpleMappingExceptionResolver处理异常
我们有很多方式定义异常和view的映射,例如HandlerExceptionResolver。SimpleMappingExceptionResolver是HandlerExceptionResolver的实现类,我们在java配置中定义SimpleMappingExceptionResolverbean,实例化异常和映射view。我们也可以用它定义默认error页面和异常对象。
AppConfig.java
package com.concretepage.config;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
@Configuration
@ComponentScan("com.concretepage.controller")
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
@Bean
public UrlBasedViewResolver urlBasedViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
@Bean
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties errorMaps = new Properties();
errorMaps.setProperty("ElectricityNotFoundException", "error");
errorMaps.setProperty("NullPointerException", "error");
resolver.setExceptionMappings(errorMaps);
resolver.setDefaultErrorView("globalerror");
resolver.setExceptionAttribute("exc");
return resolver;
}
}
举个栗子,我们创建了用户异常,并且与SimpleMappingExceptionResolver bean映射。Demo里面,我们将定义了的error页面与SimpleMappingExceptionResolver 映射。
ElectricityNotFoundException.java
package com.concretepage.exception;
public class ElectricityNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ElectricityNotFoundException(String villageName) {
super(villageName+":Electricity not available");
}
}
error.jsp
Spring MVC Exception
Error: ${exc.message}
VillageController.java
package com.concretepage.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.concretepage.exception.ElectricityNotFoundException;
@Controller
@RequestMapping("/myvillage")
public class VillageController {
@RequestMapping("/info")
public String myInfo(@RequestParam(value="vid") String vid, Model model) throws Exception {
if ("111".equals(vid)) {
throw new ElectricityNotFoundException("Dhananajaypur");
}else if ("222".equals(vid)) {
throw new NullPointerException("Data not found.");
}else {
model.addAttribute("msg", "Welcome to My Village.");
}
return "success";
}
}
Demo里,我们用SimpleMappingExceptionResolver处理了Controller异常。
访问http://localhost:8080/concretepage-1/myvillage/info?vid=111
Demo里面的WebAppInitializer实现。
WebAppInitializer.java
package com.concretepage.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class WebAppInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.addMapping("/");
dynamic.setLoadOnStartup(1);
}
}
成功页面如下:
success.jsp
Spring MVC Success
Message : ${msg}
Spring异常处理到此结束,真是一个愉快的旅程!
PS: 想要下载源码的,请阅读原文下载代码。