生活打了我们一巴掌,我们,一定要想办法再打回来
上一章简单介绍了SpringBoot上传文件到远程服务器(二十九),如果没有看过,请观看上一章
一. 为什么要实现异常信息自定义展示在 Springboot 项目开发中,包括以前的 SSM 框架开发中,我们常常会碰到 404 500 相应的错误信息.
如访问一个除以 0 的功能 (500) 错误
@RequestMapping("/div")
@ResponseBody
public String div(){
int result=10/0;
return "相除后的结果是:"+result;
}
或者访问一个不存在的页面 404 错误
这两个页面都是 SpringBoot 默认提供的.
用户在使用的过程中,如果没有相应的开发或者网络经验,对这些提示信息是很讨厌的。
所以常常进行一些有趣的设计,来减轻项目运行错误导致的不良感受。
如: (以下图片来源于网络)
我们可以自定义错误页面.
二. 自定义错误页面 二.一 Tomcat 服务器配置错误页面在以前的 Tomcat 和 JSP 时代 ,我们可以这样配置错误页面.
二.一.一 自定义 404.jsp 和 505.jsp 页面404.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true"%> <% response.setStatus(HttpServletResponse.SC_OK);%> 404,地址错误
500.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true"%> <% response.setStatus(HttpServletResponse.SC_OK);%> 500, 服务器错误,联系管理员二.一.二 web.xml 配置信息
在 web.xml 配置中,进行配置
二. 二SpringBoot 配置错误页面404 /WEB-INF/jsp/404.jsp java.lang.Throwable /WEB-INF/jsp/500.jsp 500 /WEB-INF/jsp/500.jsp
根据上面的提示,我们知道,需要在 /error 目录下进行配置相关的页面信息.
配置错误页面,包括两种,一种是静态的异常页面,一种是动态的异常页面.
二.二.一 静态异常页面在 static 目录下 创建 error 目录, 里面放置 404.html 和 500.html 页面
404.html
404异常
404异常
500.html
500 异常信息
500异常
重新运行服务器,并且访问
在访问 div (有异常的方法)
在访问 404 错误页面时
就变成了我们自定义的异常页面了.
除了指定 404 ,500 这样确切的错误状态码外,也可以使用 4xx, 5xx 这样的来统一进行接收.
二.二.二 动态错误页面有时候常常需要将错误的信息展示出来,便于开发人员进行处理.
可以使用动态错误页面,通常放置在 templates 目录下
在 templates 目录下,创建动态错误页面
404.html
404状态码展示错误
404
| path | |
| error | |
| message | |
| timestamp | |
| status |
500.html
5xx状态码展示错误
5xx
| path | |
| error | |
| message | |
| timestamp | |
| status |
这五个属性信息, path, error,message,timestamp,status
是 由: org.springframework.boot.web.reactive.error.DefaultErrorAttributes 进行定义的
这个时候,再进行访问
会展示出具体的信息.
发现,动态的错误页面是生效的。
如果动态页面和静态页面同时定义了异常处理页面,
例如 classpath:/static/error/404.html 和 classpath:/templates/error/404.html 同时存在时,
默认使用动态页面。
完整的错误页面查找方式应该是这样:
发生了 404错误–>查找动态 404.html 页面–>查找静态 404.html –> 查找动态 4xx.html–>查找静态 4xx.html。
500 也一样.
发生了 500 错误–>查找动态 500.html 页面–>查找静态 500.html –> 查找动态 5xx.html–>查找静态 5xx.html。
三. 后端服务器自定义异常 三.一 提示信息和内容自定义SpringBoot 提供的页面和属性信息,有时候不符合业务场景,需要后端开发人员进行自定义(自带的 message 是英文的,不符合国人习惯)
我们可以自定义修改
package top.yueshushu.learn.error;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map map = super.getErrorAttributes(webRequest, includeStackTrace);
//也可以放置其他的属性信息,或者替换属性,如 message,或者 timestamp
if ((Integer)map.get("status") == 500) {
map.put("message", "服务器内部错误!");
}
if ((Integer)map.get("status") == 404) {
map.put("message", "页面找不到!");
}
if ((Integer)map.get("status") == 403) {
map.put("message", "未授权!");
}
return map;
}
}
提示信息发生了改变,变成了开发人员自定义的提示信息.
三.二 自定义视图解析和返回内容将 MyErrorAttributes 去掉 @Component 组件注解
继承DefaultErrorViewResolver 解析视图类
@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
super(applicationContext, resourceProperties);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) {
// 可以根据状态 status, 自定义视图页面和 model
//不能直接修改 model
Map newMap = new HashMap<>();
for(Map.Entry entry:model.entrySet()){
newMap.put(entry.getKey(),entry.getValue());
}
newMap.put("findUser","两个蝴蝶飞");
// NOT_FOUND(404, "Not Found"),
if(HttpStatus.NOT_FOUND.equals(status)){
return new ModelAndView("/self/404.html",newMap);
}
// 500错误
// INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
if(HttpStatus.INTERNAL_SERVER_ERROR.equals(status)){
return new ModelAndView("/self/500.html",newMap);
}
//走一个默认的页面
return new ModelAndView("/static/error/500.html",newMap);
}
}
在 template 目录下, 创建 self , 后续创建 500.html, 404.html
500.html , 404.html 添加一个自定义的属性, 就是 findUser
四. 前后端处理自定义异常信息现在的项目,都是前后端分离的项目,希望在出现异常时,返回的是一个 json格式的数据,并不是跳转到页面。
前端开发人员,拿到错误的信息之后,个性化进行处理.
去掉 MyErrorViewResolver 上面的 @Component 注解
同时,去掉 自定义的 静态异常和动态异常信息, 将 error 文件夹重命名为 error2
四.一 定义异常信息 四.一.一 统一返回结果 OutputResult@Data
public class OutputResult implements Serializable {
private Integer code;
private String message;
private Map data=new HashMap();
private OutputResult(){
}
public static OutputResult fail(){
OutputResult outputResult=new OutputResult();
outputResult.code=500;
outputResult.message="失败";
return outputResult;
}
public static OutputResult fail(String message){
OutputResult outputResult=new OutputResult();
outputResult.code=500;
outputResult.message=message;
return outputResult;
}
public static OutputResult success(){
OutputResult outputResult=new OutputResult();
outputResult.code=200;
outputResult.message="成功";
return outputResult;
}
public static OutputResult success(Object data){
OutputResult outputResult=new OutputResult();
outputResult.code=200;
outputResult.message="成功";
outputResult.data.put("result",data);
return outputResult;
}
}
四.一.二 自定义的异常类 BusinessException
public class BusinessException extends Exception {
String message="";
public BusinessException(String message){
super(message);
this.message=message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
四.一.三 Controller 层异常方法 ExceptionController
@Controller
public class ExceptionController {
@RequestMapping("/")
public String index(){
return "index";
}
@RequestMapping("/div")
@ResponseBody
public OutputResult div(){
int result=10/0;
return OutputResult.success(result);
}
@RequestMapping("/npe")
@ResponseBody
public OutputResult npe(){
String str=null;
return OutputResult.success(str.length());
}
@RequestMapping("/array")
@ResponseBody
public OutputResult array(){
String[] arr=new String[]{"岳泽霖","两个蝴蝶飞"};
return OutputResult.success(arr[arr.length]);
}
@RequestMapping("/bus")
@ResponseBody
public OutputResult bus() throws BusinessException {
try{
int aa=10/0;
}catch (Exception e){
//去查询数据库
throw new BusinessException("查询数据库失败了");
}
return OutputResult.success("查询数据库成功");
}
@RequestMapping("/other")
@ResponseBody
public OutputResult other() throws Exception {
//去查询数据库
try{
int aa=10/0;
}catch (Exception e){
//去查询数据库
throw new Exception("其他的异常信息");
}
return OutputResult.success("查询数据库成功");
}
}
进行调用时,
这样很不好看,也不方便管理.
四.二 自定义全局异常处理器 MyExceptionHandler通过 @ExceptionHandler 注解,接收相应的异常信息
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public OutputResult npeException(HttpServletRequest req, NullPointerException e){
return OutputResult.fail(e.getMessage());
}
@ExceptionHandler(ArithmeticException.class)
public OutputResult ariException(HttpServletRequest req, ArithmeticException e){
return OutputResult.fail(e.getMessage());
}
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public OutputResult arrException(HttpServletRequest req, ArrayIndexOutOfBoundsException e){
return OutputResult.fail(e.getMessage());
}
@ExceptionHandler(BusinessException.class)
public OutputResult busException(HttpServletRequest req, BusinessException e){
return OutputResult.fail(e.getMessage());
}
@ExceptionHandler(Exception.class)
public OutputResult otherException(HttpServletRequest req, Exception e){
return OutputResult.fail(e.getMessage());
}
}
这时候,再进行访问:
目前老蝴蝶的 状态码都是 500, 实际用的时候,会定义成不同的状态码, npe表示 300, 除 o表示 301,其它异常都有各自的状态码。
前端可以根据状态码,进行不同的展示信息
从而实现前后端分离下的全局异常处理机制.
五. 非抛出异常处理在实际的业务中,会有 dao数据库层,servie层, controller 层,每一层都会有相应的异常抛出,也可以各自在自己的层捕获异常。
当异常被补获时,没有被抛出时,是什么情况呢? 一般不会在 dao层捕获异常.
五.一 Service 层public interface ExceptionService {
public void div();
public void tryDiv();
public void throwDivExcepiton() throws Exception;
public void throwBusExcepiton() throws Exception;
}
五.二 SerivceImpl 实现
@Service
public class ExceptionServiceImpl implements ExceptionService {
@Override
public void div() {
int a=10/0;
}
@Override
public void tryDiv() {
try{
int a=10/0;
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void throwDivExcepiton() throws Exception {
try{
int a=10/0;
}catch (Exception e){
throw e;
}
}
@Override
public void throwBusExcepiton() throws Exception {
throw new BusinessException("数据库查询出现异常");
}
}
五.三 Controller 层 ServiceExceptionController
@RestController
@RequestMapping("/exce")
public class ServiceExceptionController {
@Autowired
private ExceptionService exceptionService;
@GetMapping("/div")
public OutputResult div(){
exceptionService.div();
return OutputResult.success();
}
@GetMapping("/tryDiv")
public OutputResult tryDiv(){
exceptionService.tryDiv();
return OutputResult.success();
}
@GetMapping("/throwDivExcepiton")
public OutputResult throwDivExcepiton() throws Exception {
exceptionService.throwDivExcepiton();
return OutputResult.success();
}
@GetMapping("/throwBusExcepiton")
public OutputResult throwBusExcepiton() throws Exception {
exceptionService.throwBusExcepiton();
return OutputResult.success();
}
@GetMapping("/throwDivExcepiton2")
public OutputResult throwDivExcepiton2() {
try{
exceptionService.throwDivExcepiton();
return OutputResult.success();
}catch (Exception e){
e.printStackTrace();
return OutputResult.success("内部补获异常");
}
}
@GetMapping("/throwBusExcepiton2")
public OutputResult throwBusExcepiton2() {
try{
exceptionService.throwBusExcepiton();
return OutputResult.success();
}catch (Exception e){
e.printStackTrace();
return OutputResult.success("内部补获异常");
}
}
}
运行处理展示:
五.三.一 div 方法service 层和 controller 层均没有 捕获异常
五.三.二 tryDivservice 层捕获异常, controller 层没有捕获
没有接收到异常信息
五.三.三 throwDivExcepitonservice 层捕获异常,但往外抛出了, controller 层继续抛出
五.三.四 throwBusExcepitonservice 层抛出异常, controller 层继续抛出
五.三.五 throwDivExcepiton2service 层捕获异常,但继续抛出异常, controller 捕获异常
五.三.六 throwBusExcepiton2service 层抛出异常, controller 捕获异常
总结: 异常捕获,是在 Controller 层再往上一层进行处理的, 如果 Controller 层抛出异常,才能获取到,转换成 json的形式,如果 controller 层不抛出异常,是无法获取到的,也无法转换成 json的形式.
本章节的代码放置在 github 上:
https://github.com/yuejianli/springboot/tree/develop/SpringBoot_Exception
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!



