处理异常
在请求的过程中,错误往往是不可避免的,那么发生异常时,该给客户端什么响应呢?Servlet请求的输出和输入均是一个Servlet响应。因此,异常必须要以某种方式转换为响应。Spring提供多种方式将异常转换为响应:
- a、特定的Spring异常将会自动映射为指定的HTTP状态码。
- b、异常可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码;
- c、在方法上可以添加@ExceptionHandler注解,使其用来处理异常。
将异常映射为HTTP状态码
Spring提供了一种机制,能够通过@ResponseStatus注解将异常映射为HTTP状态码。实例如下;
在SpittleController中通常会有一个根据ID查询用户的处理器方法,如下:
@RequestMapping(value="/{spittleId}",method=GET)
public String spittle(@PathVariable("spittleId") long spittleId, Model model){
Spittle spittle=spittleRepository.findOne(spittleId);
if(spittle == null)
{
throw new SpittleNotFoundException();
}
model.addAttribute(spittle);
return "spittle";
}
需要注意的是当spittle为空的时候,会抛出一个SpittleNotFoundException()异常,这个异常是我们自定义的异常:
package spittr.exception;
public class SpittleNotFoundException extends RuntimeException{}
如果调用spittle()方法来处理请求,并且给定ID获取到的结果为空,那么SpittleNotFoundException(默认)将会产生500状态码(Internal Server Error)的响应。实际上,如果出现任何没有映射的异常,响应都会带有500状态码,但是,我们可以通过映射SpittleNotFoundException对这种默认行为进行变更。
当抛出SpittleNotFoundException异常时,这是一种请求资源没有找到的场景。如果资源没有找到的话,HTTP状态码404是最为精确的响应状态码。所以,我们要使用@ResponseStatus注解将SpittleNotFoundException映射为HTTP状态码404。
package spittr.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException{}
在引入@ResponseStatus注解之后,如果控制器方法抛出SpittleNotFoundException异常的话,响应将会具有404状态码,这是因为SpittleNotFound。
编写异常处理的方法
假设有这样一个场景,用户视图创建的Spittle与已创建的Spittle文本完全相同,那么SpittleRepository的save()方法将会抛出DuplicateSpittle Exception异常。这意味着SpittleController的saveSpittle()方法可能需要处理这个异常。如下的程序清单处理了这个异常:
public String saveSpittle(SpittleForm form,Model model){
try{
spittleRepository.save(new Spittle(null,form.getMessage(),new Date(),form.getLongitude(),form.getLatitude()));
return "redirect:/spittles";
}catch(DuplicateSpittleException e)
{
return "error/duplicate";
}
}
这是java中处理异常的样例,但是这样让代码看起来很冗杂,并且让方法多了一个路径。如果能让saveSpittle()方法之关注正确的路径,而让其他方法处理异常的话,那么它就能简单一些。
现在我们想将saveSpittle()方法中的异常处理方法剥离掉:
public String saveSpittle(SpittleForm form,Model model){
spittleRepository.save(new Spittle(null,form.getMessage(),new Date(),form.getLongitude(),form.getLatitude()));
return "redirect:/spittles";
}
这样,该方法就可以只关注正确的逻辑。现在,我们为SpittleController添加一个新的方法,它会处理抛出DuplicateSpittleException的情况:
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle(){
return "error/duplicate";
}
当该控制器中的任意一个方法抛出了DuplicateSpittleException后,都会委托该方法来处理。我们不用在每一个可能抛出该异常的地方添加异常处理方法,这样无疑能省去不少的冗余代码,我们的代码看起来也会更加整洁。
那么,更进一步,既然已经有覆盖一个控制器的公用异常处理方法了,那有没有覆盖所有控制器的异常处理方法呢?答案是肯定的,我们只需要将其定义到控制器通知类中即可。
为控制器添加通知
如果控制器的特定切面能够运用到整个应用程序的所有控制器中,那么这将会便利很多。控制器通知(Controller advice)是任意带有@ControllerAdvice注解的类,这个类会包含一个或多个如下类型的方法:
a、@ExceptionHandler注解标注的方法;
b、@InitBinder注解标注的方法;
c、@ModelAttribute注解标注的方法。
在带有@ControllerAdvice注解的类中,以上所述的这些方法会运用到整个应用程序所有控制器中带有@RequestMapping注解的方法上。@ControllerAdvice本身已经使用了@Component,一次可以被自动扫描获取到。
@ControllerAdvice最为实用的一个场景就是将所有的@ExceptionHandler方法收集到一个类中,这样所有控制器的异常就能在一个地方进行一致的处理。如下程序1清单所示:
package spitter.web;
import rog.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice
public class webWideExceptionHandler{
@ExceptionHandler(DuplicateSpittleException,class)
public String duplicateSpittleHandler(){
return "error/duplicate";
}
}
现在,如果任意的控制器方法抛出了DuplicateSpittleException,不管这个方法位于哪个控制器中,都会调用这个duplicateSpittleHandler()方法来处理异常。
上一篇: SpringMVC入门之九:multipart文件上传
下一篇: SpringMVC入门之十一:跨重定向请求传递数据