- 前言
这篇我来介绍一下spring boot 中常见的处理异常的三种方式, 话不多说直接开始吧。
- 环境配置
JDK: 1.8
spring boot: v2.1.6.RELEASE
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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 准备一个测试的Demo
项目包结构如下图:
MyController
package com.springboot.demo.controller;
import com.springboot.demo.exception.MyException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/my/")
public class MyController {
@GetMapping("exception")
public String test() {
throw new MyException(-1, "异常啦!");
// return "ok";
}
}
MyException
package com.springboot.demo.exception;
public class MyException extends RuntimeException {
private static final long serialVersionUID = -8733049534544489762L;
private int status;
private String msg;
public MyException(int status, String msg) {
this.status = status;
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
这样一个简单的测试Demo就写好了, 访问 http://localhost:8080/my/exception
效果如图
由于在代码中手动抛出了一个运行时异常, 所以会显示默认的异常页面, 下面我们就来对异常进行个性化的处理, 来返回我们想要的页面。
1. 自定义错误页面
这是最简单的一种, 实现起来很简单, 只需要在资源文件夹底下新建 resources/error 目录, 并根据错误状态码来命名需要自定义的异常页面就可以了, 具体如下图:
创建文件夹
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body>
<h1>您所访问的页面不存在</h1>
</body>
</html>
500.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>500</title>
</head>
<body>
<h1>服务器内部错误</h1>
</body>
</html>
运行效果如下
访问 http://localhost:8080/my/exception
访问 http://localhost:8080/my/exception1
这种方式虽然简单, 但是局限性很大, 对于接口模式开发的项目来说就不太实用了, 再来看下一种方式。
2. 控制器通知
这种方式是基于 @ControllerAdvice 注解的控制器通知, 新建类ControllerExceptionHandler
ControllerExceptionHandler
package com.springboot.demo.controller;
import com.springboot.demo.exception.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice(basePackages = {"com.springboot.demo.controller.*"}, annotations = {RestController.class})
public class ControllerExceptionHandler {
Logger logger = LoggerFactory.getLogger(getClass());
@ExceptionHandler(MyException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map exceptionHandler(MyException ex) {
logger.info("异常处理器: " + this.getClass().getSimpleName());
Map res = new HashMap<>();
res.put("status", ex.getStatus());
res.put("message", ex.getMsg());
return res;
}
}
,>,>
basePackages 表示拦截的控制器所在的包, annotations 表示只拦截 RestController.class 注解的控制器, 启动项目来看看效果。
访问 http://localhost:8080/my/exception
日志也正常打印
最后再看看扩展性更高的一种, HandlerExceptionResolver
3. HandlerExceptionResolver
这种实现方式也不难实现, 只需要继承 HandlerExceptionResolver 并实现对应方法即可, 代码如下:
新建 ExceptionResolver
ExceptionResolver
package com.springboot.demo.resolver;
import com.springboot.demo.exception.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class ExceptionResolver implements HandlerExceptionResolver {
Logger logger = LoggerFactory.getLogger(getClass());
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
logger.info("异常处理器: " + this.getClass().getSimpleName());
ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
if (e instanceof MyException) {
modelAndView.addObject("status", ((MyException) e).getStatus());
modelAndView.addObject("msg", ((MyException) e).getMsg());
}
return modelAndView;
}
}
由于该接口方法需要返回一个 ModelAndView 对象, 所以你可以使用各种自定义的 ModelAndView 对象, 这里我使用 MappingJackson2JsonView 来返回一个 json 视图, 来看看效果。
访问 http://localhost:8080/my/exception
打印日志
之所以说最后一种是扩展性高的一种方式, 是因为在这个方法内还可以拿到 HttpServletRequest 与 HttpServletResponse 对象, 可以进行一些个性化的操作, 这种也是本人最常用的一种方式。
end ~