Spring Boot配置Spring MVC自定义404等异常

Spring Boot

前言

在如今前后端开发完全分离的当下,通常后端会返回统一的自定义json数据格式(全局异常也需要返回相同的格式),前端也会封装全局请求的回调处理。在写这篇文章时,我自己也正在单独开发了一套前后端完全分离的项目,用于巩固前后端知识。在这期间遇到的问题和解决方式会在下面进行展示,,温馨提醒,文章因为将代码都贴上了,所以比较长,并且代码都是从微服务各个模块中拷贝的,可能包名会不一样,建议先在web单体项目中进行完整复制测试。

总览

文章解决了哪些问题:

  1. 统一接口的返回格式
  2. 自定义异常
  3. 全局异常捕获
  4. springMVC的404,405异常自定义

代码

1.接口统一返回数据格式工具类

package com.goudong.commons.pojo;

import com.goudong.commons.enumerate.ExceptionEnumInterface;
import com.goudong.commons.exception.BasicException;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.http.HttpStatus;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 类描述:
 *  统一API响应结果封装
 * @ClassName Result
 * @Author msi
 * @Date 2020/10/5 18:42
 * @Version 1.0
 */
@Data
@ApiModel(value = "Result", description = "统一结果返回结构封装类")
public class Result<T> implements Serializable {
    /**
     * 成功
     */
    public static final String SUCCESS = "1";
    /**
     * 失败
     */
    public static final String FAIL = "0";
    private static final long serialVersionUID = -212122772006061476L;

    /**
     * 状态码
     */
    @ApiModelProperty(value = "响应码", required = true, example = "404")
    private String code;
    /**
     * 客户端状态码对应信息
     */
    @ApiModelProperty(value = "状态码对应描述", required = true, example = "用户不存在")
    private String clientMessage;

    /**
     * 服务器状态码对应信息
     */
    @ApiModelProperty(value = "状态码对应描述", required = true, example = "用户不存在")
    private String serverMessage;

    /**
     * 数据
     */
    @ApiModelProperty(value = "额外自定义数据")
    private T data;

    /**
     * 数据
     */
    @ApiModelProperty(value = "扩展额外的数据")
    private Map dataMap = new HashMap();

    /**
     * 时间戳
     */
    @ApiModelProperty(value = "时间戳")
    private Date timestamp = new Date();

    public Result() {
    }
    public Result(String code) {
        this.code = code;
    }
    public Result(String code, String clientMessage, String serverMessage) {
        this.code = code;
        this.clientMessage = clientMessage;
        this.serverMessage = serverMessage;
    }
    public Result(String code, String clientMessage, String serverMessage, T t) {
        this.code = code;
        this.clientMessage = clientMessage;
        this.serverMessage = serverMessage;
        this.data = t;
    }


    /**
     * 返回成功
     * @return
     */
    public static Result<Object> ofSuccess() {
        return new Result(Result.SUCCESS);
    }
    /**
     * 返回成功,带数据
     * @return
     */
    public static <T> Result<T> ofSuccess(T t) {
        return new Result(Result.SUCCESS, null, null, t);
    }

    /**
     * 返回失败
     * @return
     */
    public static Result ofFail() {
        return new Result(Result.FAIL);
    }
    /**
     * 返回失败,带数据
     * @return
     */
    public static <T> Result<T> ofFail(T t) {
        return new Result(Result.FAIL, null, null, t);
    }

    /**
     * 只返回失败信息,不抛额异常
     * @return
     */
    public static Result ofFail(ExceptionEnumInterface enumInterface) {
        return  new Result(enumInterface.getCode(), enumInterface.getClientMessage(), enumInterface.getServerMessage(), null);
    }

    /**
     * 只返回失败信息,不抛额异常
     * @return
     */
    public static Result ofFail(BasicException basicException) {
        return  new Result(basicException.getCode(), basicException.getClientMessage(), basicException.getServerMessage(), null);
    }

    /**
     * 400 Bad Request
     * @param clientMessage 客户端显示错误
     * @param serverMessage 服务端错误
     * @return
     */
    public static Result ofFailByBadRequest(String clientMessage, String serverMessage) {
        return  new Result("400", clientMessage, HttpStatus.BAD_REQUEST.getReasonPhrase() + " - " + serverMessage);
    }

    /**
     * 404 Not Found
     * @param url 访问的资源地址
     * @return
     */
    public static Result ofFailByNotFound(String url) {
        return  new Result("404", "当前请求资源不存在,请稍后再试", HttpStatus.NOT_FOUND.getReasonPhrase() + " - 目标资源资源不存在:" + url);
    }

    /**
     * 405 Method Not Allowed
     * @param url 访问的资源地址
     * @return
     */
    public static Result ofFailByMethodNotAllowed(String url) {
        return  new Result("405", "当前资源请求方式错误,请稍后再试", HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase() + " - 目标资源资源不存在:" + url);
    }

}

2.自定义异常及全局异常配置

BasicException基类用于定义格式,ClientException、ServerException分别代表客户端异常和服务端异常。

  1. 自定义异常基类 BasicException
package com.goudong.commons.exception;

import com.goudong.commons.enumerate.ClientExceptionEnum;
import com.goudong.commons.enumerate.ServerExceptionEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:
 * 自定义异常的基类,其它模块的异常继承进行扩展
 * @ClassName BaseicException
 * @Author msi
 * @Date 2020/6/10 19:41
 * @Version 1.0
 */
@Slf4j
@Getter
@Setter
@ApiModel(value = "BasicException", description = "出现异常,返回的消息")
public class BasicException extends RuntimeException{
    /**
     * http 响应码
     */
    public int status;
    /**
     * 错误代码
     */
    public String code;
    /**
     * 客户端状态码对应信息
     */
    @ApiModelProperty(value = "状态码对应描述", required = true, example = "用户不存在")
    public String clientMessage;

    /**
     * 服务器状态码对应信息
     */
    @ApiModelProperty(value = "状态码对应描述", required = true, example = "用户不存在")
    public String serverMessage;



    /**
     * 构造方法
     * @param status http状态码
     * @param code 自定义状态码
     * @param clientMessage 客户端显示信息
     * @param serverMessage 服务端日志显示信息
     */
    public BasicException(int status, String code, String clientMessage, String serverMessage) {
        super(clientMessage+"\t"+serverMessage);
        this.status = status;
        this.code = code;
        this.clientMessage = clientMessage;
        this.serverMessage = serverMessage;
    }

    /**
     * 客户端误操作造成异常
     * @param exceptionEnum
     */
    public BasicException(ClientExceptionEnum exceptionEnum) {
        this(exceptionEnum.getStatus(), exceptionEnum.getCode(), exceptionEnum.getClientMessage(), exceptionEnum.getServerMessage());
    }

    /**
     * 服务端异常
     * @param exceptionEnum
     */
    public BasicException(ServerExceptionEnum exceptionEnum) {
        this(exceptionEnum.getStatus(), exceptionEnum.getCode(), exceptionEnum.getClientMessage(), exceptionEnum.getServerMessage());
    }

    /**
     * 快速抛出服务端通用异常
     * @param exceptionEnum 服务端异常枚举
     * @return
     */
    public static BasicException exception (ServerExceptionEnum exceptionEnum) {
        throw new ServerException(exceptionEnum);
    }

    /**
     * 快速抛出客户端通用异常
     * @param exceptionEnum 客户端异常枚举
     * @return
     */
    public static BasicException exception (ClientExceptionEnum exceptionEnum) {
        throw new ClientException(exceptionEnum);
    }

}

  1. 定义两个异常子类 ClientException(用户单方面原因引发的异常)、ServerException(服务端的异常):
  • ClientException
package com.goudong.commons.exception;

import com.goudong.commons.enumerate.ClientExceptionEnum;
import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:
 * 客户端内部错误
 * @Author e-Feilong.Chen
 * @Date 2021/8/10 16:12
 */
@Slf4j
public class ClientException extends BasicException {
    /**
     * 资源不存在
     * 404 Not Found
     * @param clientMessage 客户端提示信息
     * @return
     */
    public static BasicException resourceNotFound(String clientMessage){
        log.error("资源不存在:{}", clientMessage);
        throw new BasicException(404, "404", clientMessage, "Not Found - 请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。");
    }

    public ClientException(ClientExceptionEnum clientExceptionEnum) {
        super(clientExceptionEnum);
    }
}

  • ServerException
package com.goudong.commons.exception;

import com.goudong.commons.enumerate.ServerExceptionEnum;
import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:
 * 服务器内部错误
 * @Author e-Feilong.Chen
 * @Date 2021/8/10 16:13
 */
@Slf4j
public class ServerException extends BasicException{
    public static final String clientMessage = "服务器内部错误";

    public ServerException(ServerExceptionEnum serverExceptionEnum) {
        super(serverExceptionEnum);
    }

    public static BasicException exception(String serverMessage){
        log.error("服务器内部错误:{}", serverMessage);
        throw new BasicException(500, "500500", clientMessage, serverMessage);
    }

    /**
     * 服务之间方法调用 参数错误
     * @param serverMessage
     * @return
     */
    public static BasicException methodParamError(String serverMessage){
        log.error("服务器内部方法传参错误:{}", serverMessage);
        throw new BasicException(400, "500400", clientMessage, serverMessage);
    }
}

  1. 全局异常捕获配置

该类目前捕获的异常有:

  • UndeclaredThrowableException:当sentinel资源未配置 blockHandler、fallback 和 defaultFallback 异常
  • BasicException:自定义抛出的异常
  • Throwable:其它异常
  • BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class, HttpMessageNotReadableException.class : 使用@Validated注解验证的异常
  • NoHandlerFoundException:404异常
  • HttpRequestMethodNotSupportedException:405异常(请求方式不对,如get接口用post请求)

根据需要进行修改和删除
注意:404异常还需要配置spring mvc,继续看下面的介绍。

package com.goudong.commons.exception;


import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.goudong.commons.enumerate.ServerExceptionEnum;
import com.goudong.commons.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.List;

/**
 * 全局捕获异常
 * 1.捕获返回json格式
 * 2.捕获返回页面
 * @ClassName GlobalExceptionHandler
 * @Author msi
 * @Date 2019/7/28 21:51
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 错误日志模板
     */
    public static final String LOG_ERROR_INFO = "http响应码:{},错误代码:{},客户端错误信息:{},服务端错误信息:{}";

    /**
     * 请求对象
     */
    @Resource
    private HttpServletRequest request;

    /**
     * 响应对象
     */
    @Resource
    private HttpServletResponse response;

    /**
     * 当sentinel资源未配置 blockHandler、fallback 和 defaultFallback   异常
     * @param e
     * @return
     */
    @ExceptionHandler(UndeclaredThrowableException.class)
    public Result blockExceptionDispose(BlockException e){
        e.printStackTrace();
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, 200, 200, "服务器繁忙,请重试", e.getRule().toString());
        return Result.ofFail();
    }

    /**
     * 全局处理自定义异常
     * @param exception
     * @return
     */
    @ExceptionHandler(BasicException.class)
    public Result<BasicException> clientExceptionDispose(BasicException exception){
        // 设置响应码
        response.setStatus(exception.getStatus());
        // 打印错误日志
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, exception.getStatus(), exception.getCode(), exception.getClientMessage(), exception.getServerMessage());
        // 堆栈跟踪
        exception.printStackTrace();

        return Result.ofFail(exception);
    }

    /**
     * 捕获意料之外的异常Exception
     * @param e
     * @return
     */
    @ExceptionHandler(Throwable.class)
    public Result<Throwable> otherErrorDispose(Throwable e){
        BasicException serverException = new ServerException(ServerExceptionEnum.SERVER_ERROR);
        this.response.setStatus(serverException.status);
        // 打印错误日志
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, serverException.status, serverException.code, serverException.clientMessage, e.getMessage());
        // 堆栈跟踪
        e.printStackTrace();
        serverException.setServerMessage(e.getMessage());
        return Result.ofFail(serverException);
    }

    /*==========================================================================
        常见自定义异常
    ==========================================================================*/

    /**
     * 400 Bad Request
     * 因发送的请求语法错误,服务器无法正常读取.
     * @param e
     * @return
     */
    @ExceptionHandler(value = {
            BindException.class,
            ConstraintViolationException.class,
            MethodArgumentNotValidException.class,
            HttpMessageNotReadableException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Throwable> ValidExceptionDispose(Exception e){
        List<String> messages = new ArrayList<>();
        if (e instanceof BindException) {
            List<ObjectError> list = ((BindException) e).getAllErrors();
            for (ObjectError item : list) {
                messages.add(item.getDefaultMessage());
            }
        } else if (e instanceof ConstraintViolationException) {
            for (ConstraintViolation<?> constraintViolation : ((ConstraintViolationException)e).getConstraintViolations()) {
                messages.add(constraintViolation.getMessage());
            }
        } else if (e instanceof HttpMessageNotReadableException) {
            messages.add("请求参数丢失");
        } else {
            messages.add(((MethodArgumentNotValidException)e).getBindingResult().getFieldError().getDefaultMessage());
        }

        String message = String.join(",", messages);
        // 打印错误日志
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, HttpStatus.BAD_REQUEST.value(), "VALIDATION", message, e.getMessage());
        // 堆栈跟踪
        e.printStackTrace();

        return Result.ofFailByBadRequest(message, e.getMessage());
    }

    /**
     * 404 Not Found
     * 请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。出现这个错误的最有可能的原因是服务器端没有这个页面。
     * @return
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Result notFound() {
        return Result.ofFailByNotFound(request.getRequestURL().toString());
    }

    /**
     * 405 Method Not Allowed
     * 请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。
     *
     * 鉴于 PUT,DELETE 方法会对服务器上的资源进行写操作,因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法,对于此类请求均会返回405错误。
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public Result methodNotAllowed() {
        return Result.ofFailByMethodNotAllowed(request.getRequestURL().toString());
    }

}

3.SpringMVC配置

问题:

  • 当访问某个不存在的页面时,springMVC会返回一个异常页面,而现在我们需要让它返回JSON数据
  1. 配置application.yml
spring.mvc.throw-exception-if-no-handler-found: true #出现错误时, 直接抛出异常
spring.resources.add-mappings: false #不要为我们工程中的静态资源文件建立映射

解释:
配置一(throw-exception-if-no-handler-found: true):当请求不存在时,让程序抛出异常
配置二(add-mappings: false):当后台有集成swagger时,这里要么注释,要么值只能为false,当设置为true时,swagger不能正常使用。

  1. 配置mvc
package com.goudong.commons.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

/**
 * 类描述:
 * swagger 允许访问
 * @Author e-Feilong.Chen
 * @Date 2021/8/12 9:46
 */
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    /**
     * mvc 添加静态资源
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html");
    }

    /**
     * 不根据url后缀匹配返回指定的数据类型
     * @param configurer
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
    }
}

如果不配置WebMvcConfiguration,只配置spring.resources.add-mappings,在接口404时就会返回springmvc默认的json格式不友好。(这里通过反复测试,最终都需要配置才能达到理想)

4. 配置/error接口

我们知道,当mvc访问404,405时,会跳转到框架默认的/error接口,然后返回默认数据,我们需要重新定义/error接口,让他返回我们想笑的格式:

  • ErrorAttributes
package com.goudong.commons.config;

import cn.hutool.core.bean.BeanUtil;
import com.goudong.commons.enumerate.ClientExceptionEnum;
import com.goudong.commons.exception.BasicException;
import com.goudong.commons.pojo.Result;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.Resource;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 自定义异常逻辑,返回自定义格式的json错误信息
 * @Author msi
 * @Date 2021-05-25 9:53
 * @Version 1.0
 */
@Component
public class ErrorAttributes extends DefaultErrorAttributes {
    @Resource
    private HttpServletRequest request;

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Throwable error = super.getError(webRequest);
        if (error instanceof BasicException) {
            BasicException be = (BasicException) error;
            Result result = Result.ofFail(be);
            Map<String, Object> map = BeanUtil.beanToMap(result);

            // 设置响应码值
            request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, be.getStatus());
            return map;
        } else if (error instanceof NoHandlerFoundException) { // 静态资源404
            NoHandlerFoundException exception = (NoHandlerFoundException) error;
            Result result = Result.ofFailByNotFound(exception.getRequestURL());
            Map<String, Object> map = BeanUtil.beanToMap(result);

            // 设置响应码值
            request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.NOT_FOUND.value());
            return map;
        }

        return super.getErrorAttributes(webRequest, options);
    }

}

  • ErrorController
package com.goudong.commons.config;

import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;

import javax.annotation.Resource;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 自定义 /error 映射
 * @Author msi
 * @Date 2021-05-25 10:53
 * @Version 1.0
 */
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class ErrorController extends AbstractErrorController {

    @Resource
    private ErrorAttributes errorAttributes;

    public ErrorController(org.springframework.boot.web.servlet.error.ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    /**
     * mvc 错误时,跳转到/error请求,并自定义返回状态码和对应的json数据
     * @param request
     * @return
     */
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = errorAttributes.getErrorAttributes(new ServletWebRequest(request), ErrorAttributeOptions.defaults());

        int code = (int)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = HttpStatus.resolve(code);
        return new ResponseEntity<>(body, status);
    }

    /**
     * @deprecated
     */
    @Override
    public String getErrorPath() {
        return null;
    }
}

其它代码

在上面的代码,可能会引用下面的一些class,所以也必须拷贝到项目

  1. ExceptionEnumInterface
package com.goudong.commons.enumerate;

/**
 * 接口描述:
 * 定义异常枚举的方法
 * @Author msi
 * @Date 2021-05-15 10:41
 * @Version 1.0
 */
public interface ExceptionEnumInterface {
    /**
     * 响应码
     */
    int getStatus();
    /**
     * 错误代码
     */
    String getCode();
    /**
     * 客户看见的提示信息
     */
    String getClientMessage();
    /**
     * 服务器日志信息
     */
    String getServerMessage();
}

  1. ClientExceptionEnum
package com.goudong.commons.enumerate;

import com.goudong.commons.utils.JwtTokenUtil;
import lombok.Getter;
import org.springframework.http.HttpStatus;

/**
 * 类描述:
 *  客户端错误
 *  400~417
 * @See https://www.restapitutorial.com/httpstatuscodes.html
 * @Author msi
 * @Date 2020/10/17 16:29
 * @Version 1.0
 */
@Getter
public enum ClientExceptionEnum implements ExceptionEnumInterface {
    /**
     * 400 Bad Request
     * 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
     * 2、请求参数有误。
     */
    BAD_REQUEST(400, "400", "参数错误", "Bad Request - 语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求;请求参数有误"),

    /**
     * 401 Unauthorized
     * 当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。客户端可以重复提交一个包含恰当的 Authorization 头信息的请求。如果当前请求已经包含了 Authorization 证书,那么401响应代表着服务器验证已经拒绝了那些证书。如果401响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展示响应中包含的实体信息,因为这个实体信息中可能包含了相关诊断信息。
     */
    UNAUTHORIZED(401, "401", "请登录认证", "Unauthorized - 用户未认证,或认证信息过期"),

    NOT_AUTHORIZATION(403, "403", "无权访问", "用户没有权限"),
    NAME_OR_PWD_ERROR(400, "400002", "无权访问", "用户名与密码错误"),


    /**
     * 404 Not Found
     * 请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。
     * 假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。
     * 404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。
     */
    NOT_FOUND(404, "404", "资源不存在", "Not Found - 请求失败,请求所希望得到的资源未被在服务器上发现。"),


    /**
     * 406 Not Acceptable
     * 请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
     */
    TOKEN_ERROR(406, "406", "请登录认证", "Not Acceptable - 缺少请求头 " + JwtTokenUtil.TOKEN_HEADER + "或 token格式错误"),

    /**
     * 429 Too Many Requests
     * 用户在给定的时间内发送了太多请求(“限制请求速率”)。
     */
    TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS.value(), "429", "服务器繁忙,请稍后重试", "Too Many Requests - 该资源限制用户重复提交请求"),

    ;
    /**
     * 响应码
     */
    private int status;
    /**
     * 错误代码
     */
    private String code;

    /**
     * 客户看见的提示信息
     */
    private String clientMessage;
    /**
     * 服务器日志信息
     */
    private String serverMessage;

    ClientExceptionEnum(int status, String code, String clientMessage, String serverMessage){
        this.status = status;
        this.code = code;
        this.clientMessage = clientMessage;
        this.serverMessage = serverMessage;
    }
}

  1. ServerExceptionEnum
package com.goudong.commons.enumerate;

import lombok.Getter;

/**
 * 类描述:
 *  服务端错误
 *
 * @See https://www.restapitutorial.com/httpstatuscodes.html
 * @Author msi
 * @Date 2020/10/17 16:29
 * @Version 1.0
 */
@Getter
public enum ServerExceptionEnum implements ExceptionEnumInterface {
    SERVER_ERROR(500, "500", "服务器内部错误,请联系网管", "未捕获的未知异常"),
    ;
    /**
     * 响应码
     */
    private int status;
    /**
     * 错误代码
     */
    private String code;

    /**
     * 客户看见的提示信息
     */
    private String clientMessage;
    /**
     * 服务器日志信息
     */
    private String serverMessage;

    ServerExceptionEnum(int status, String code, String clientMessage, String serverMessage){
        this.status = status;
        this.code = code;
        this.clientMessage = clientMessage;
        this.serverMessage = serverMessage;
    }
}

展示

完全不配置springMVC返回的404的错误信息

在这里插入图片描述
或者是:

{
  "timestamp": 1628739842454,
  "status": 404,
  "error": "Not Found",
  "message": "",
  "path": "/api/message/code/demo1"
}

当配置 spring.mvc.throw-exception-if-no-handler-found=truespring.resources.add-mappings=false

html等静态资源访问会404,不存在的接口也会404
在这里插入图片描述
在这里插入图片描述
但是swagger 就用不了了,所以还需要继续配置

配置 WebMvcConfiguration

在上面的配置不变的情况下,根据上文配置WebMvcConfiguration后:
swagger 能正常访问
在这里插入图片描述
但是不存在的静态资源访问如下:
在这里插入图片描述

请求接口依然是自定义异常
在这里插入图片描述

唯一问题:静态资源访问404还未捕获到异常自定义输出。

配置 /error 接口,修改默认的json格式

保持上述配置不变,配置好 ErrorAttributes 和 ErrorController

除了静态资源404 返回了json,其它跟上面一样。
在这里插入图片描述

结语

前前后后花了挺长的时间,本想不写出来的,后面直接在代码中看,但是想着可以增加下记忆,于是就有了这篇文章。还是要多实践,至于上面的 /error接口为什么要配置,可以看下面文章:spring boot ErrorMvcAutoConfiguration

最后如果贴的代码不完整可以评论留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值