博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring MVC异常处理
阅读量:4090 次
发布时间:2019-05-25

本文共 5157 字,大约阅读时间需要 17 分钟。

异常处理 发表在 

Spring MVC框架提供了多种机制用来处理异常,初次接触可能会对他们用法以及适用的场景感到困惑。现在以一个简单例子来解释这些异常处理的机制。

假设现在我们开发了一个博客应用,其中最重要的资源就是文章(Post),应用中的URL设计如下:

  • 获取文章列表:GET /posts/
  • 添加一篇文章:POST /posts/
  • 获取一篇文章:GET /posts/{id}
  • 更新一篇文章:PUT /posts/{id}
  • 删除一篇文章:DELETE /posts/{id}

这是非常标准的复合RESTful风格的URL设计,在Spring MVC实现的应用过程中,相应也会有5个对应的用@RequestMapping注解的方法来处理相应的URL请求。在处理某一篇文章的请求中(获取、更新、删除),无疑需要做这样一个判断——请求URL中的文章id是否在于系统中,如果不存在需要返回404 Not Found

使用HTTP状态码

在默认情况下,Spring MVC处理Web请求时如果发现存在没有应用代码捕获的异常,那么会返回HTTP 500(Internal Server Error)错误。但是如果该异常是我们自己定义的并且使用@ResponseStatus注解进行修饰,那么Spring MVC则会返回指定的HTTP状态码:

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No Such Post")//404 Not Foundpublic class PostNotFoundException extends RuntimeException {}

Controller中可以这样使用它:

@RequestMapping(value = "/posts/{id}", method = RequestMethod.GET)public String showPost(@PathVariable("id") long id, Model model) {    Post post = postService.get(id);    if (post == null) throw new PostNotFoundException("post not found");    model.addAttribute("post", post);    return "postDetail";}

这样如果我们访问了一个不存在的文章,那么Spring MVC会根据抛出的PostNotFoundException上的注解值返回一个HTTP 404 Not Found给浏览器。

最佳实践

上述场景中,除了获取一篇文章的请求,还有更新和删除一篇文章的方法中都需要判断文章id是否存在。在每一个方法中都加上if (post == null) throw new PostNotFoundException("post not found");是一种解决方案,但如果有10个、20个包含/posts/{id}的方法,虽然只有一行代码但让他们重复10次、20次也是非常不优雅的。

为了解决这个问题,可以将这个逻辑放在Service中实现:

@Servicepublic class PostService {
@Autowired private PostRepository postRepository; public Post get(long id) { return postRepository.findById(id) .orElseThrow(() -> new PostNotFoundException("post not found")); }}这里`PostRepository`继承了`JpaRepository`,可以定义`findById`方法返回一个`Optional
`——如果不存在则Optional为空,抛出异常。

这样在所有的Controller方法中,只需要正常活取文章即可,所有的异常处理都交给了Spring MVC。

Controller中处理异常

Controller中的方法除了可以用于处理Web请求,还能够用于处理异常处理——为它们加上@ExceptionHandler即可:

@Controllerpublic class ExceptionHandlingController {  // @RequestHandler methods  ...  // Exception handling methods  // Convert a predefined exception to an HTTP Status code  @ResponseStatus(value=HttpStatus.CONFLICT, reason="Data integrity violation")  // 409  @ExceptionHandler(DataIntegrityViolationException.class)  public void conflict() {    // Nothing to do  }  // Specify the name of a specific view that will be used to display the error:  @ExceptionHandler({SQLException.class,DataAccessException.class})  public String databaseError() {    // Nothing to do.  Returns the logical view name of an error page, passed to    // the view-resolver(s) in usual way.    // Note that the exception is _not_ available to this view (it is not added to    // the model) but see "Extending ExceptionHandlerExceptionResolver" below.    return "databaseError";  }  // Total control - setup a model and return the view name yourself. Or consider  // subclassing ExceptionHandlerExceptionResolver (see below).  @ExceptionHandler(Exception.class)  public ModelAndView handleError(HttpServletRequest req, Exception exception) {    logger.error("Request: " + req.getRequestURL() + " raised " + exception);    ModelAndView mav = new ModelAndView();    mav.addObject("exception", exception);    mav.addObject("url", req.getRequestURL());    mav.setViewName("error");    return mav;  }}

首先需要明确的一点是,在Controller方法中的@ExceptionHandler方法只能够处理同一个Controller中抛出的异常。这些方法上同时也可以继续使用@ResponseStatus注解用于返回指定的HTTP状态码,但同时还能够支持更加丰富的异常处理:

  • 渲染特定的视图页面
  • 使用ModelAndView返回更多的业务信息

大多数网站都会使用一个特定的页面来响应这些异常,而不是直接返回一个HTTP状态码或者显示Java异常调用栈。当然异常信息对于开发人员是非常有用的,如果想要在视图中直接看到它们可以这样渲染模板(以JSP为例):

Error Page

Application has encountered an error. Please contact support on ...

全局异常处理

提供了和上一节一样的异常处理能力,但是可以被应用于Spring应用上下文中的所有@Controller

@ControllerAdviceclass GlobalControllerExceptionHandler {    @ResponseStatus(HttpStatus.CONFLICT)  // 409    @ExceptionHandler(DataIntegrityViolationException.class)    public void handleConflict() {        // Nothing to do    }}

Spring MVC默认对于没有捕获也没有被@ResponseStatus以及@ExceptionHandler声明的异常,会直接返回500,这显然并不友好,可以在@ControllerAdvice中对其进行处理(例如返回一个友好的错误页面,引导用户返回正确的位置或者提交错误信息):

@ControllerAdviceclass GlobalDefaultExceptionHandler {    public static final String DEFAULT_ERROR_VIEW = "error";    @ExceptionHandler(value = Exception.class)    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {        // If the exception is annotated with @ResponseStatus rethrow it and let        // the framework handle it - like the OrderNotFoundException example        // at the start of this post.        // AnnotationUtils is a Spring Framework utility class.        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)            throw e;        // Otherwise setup and send the user to a default error-view.        ModelAndView mav = new ModelAndView();        mav.addObject("exception", e);        mav.addObject("url", req.getRequestURL());        mav.setViewName(DEFAULT_ERROR_VIEW);        return mav;    }}

总结

Spring在异常处理方面提供了一如既往的强大特性和支持,那么在应用开发中我们应该如何使用这些方法呢?以下提供一些经验性的准则:

  • 不要在@Controller中自己进行异常处理逻辑。即使它只是一个Controller相关的特定异常,在@Controller中添加一个@ExceptionHandler方法处理。
  • 对于自定义的异常,可以考虑对其加上@ResponseStatus注解
  • 使用@ControllerAdvice处理通用异常(例如资源不存在、资源存在冲突等)
更多文章请访问

你可能感兴趣的文章
DirectX11 兰伯特余弦定理(Lambert)
查看>>
DirectX11 漫反射光
查看>>
DirectX11 环境光
查看>>
DirectX11 镜面光
查看>>
DirectX11 三种光照组成对比
查看>>
DirectX11 指定材质
查看>>
DirectX11 平行光
查看>>
DirectX11 点光
查看>>
DirectX11 聚光灯
查看>>
DirectX11 HLSL打包(packing)格式和“pad”变量的必要性
查看>>
DirectX11 光照演示示例Demo
查看>>
漫谈一下前端的可视化技术
查看>>
VUe+webpack构建单页router应用(一)
查看>>
Vue+webpack构建单页router应用(二)
查看>>
从头开始讲Node.js——异步与事件驱动
查看>>
Node.js-模块和包
查看>>
Node.js核心模块
查看>>
express的应用
查看>>
NodeJS开发指南——mongoDB、Session
查看>>
Express: Can’t set headers after they are sent.
查看>>