Spring Boot2统一异常处理

为什么要统一异常处理

项目开发形式为前后端分离,采用Restful接口形式开发,对异常的处理与页面业务数据统一以json形式返回,登录等接口由前端路由实现。

项目框架及异常类型

项目采用Spring Boot 2 + Spring MVC + Redis(单点登录,缓存token) + shiro(安全框架) + Lombok + Swagger2 +Druid开发,其 中异常类型主要包括Shiro异常,Redis异常,其他异常,针对全局异常不能拦截的方法不存在等错误使用继承ErrorController实现,这样就能对整个系统内的所有异常处理掉。

项目代码

pom依赖(只写出该模块涉及依赖)
		<!-- lombok代码生成插件 -->
		<!--提供@Slf4j(类上注解@Slf4j后直接使用log对象) @Data(自动生成get set tostring hashcode equals等方法) @AllArgsConstructor(提供一个全参的构造方法)等注解 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.6</version>
			<scope>provided</scope>
		</dependency>
				<!-- 权限相关 -->
		<!-- shiro spring. -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>${shiro.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>${shiro.version}</version>
		</dependency>
		<!-- shiro+redis缓存插件 优先使用 可以解决集群的单点登录方案 -->
		<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>${shiro-redis.version}</version>
		</dependency>
配置yml(properties基本一样)
  # 全局异常处理:抛出后会被GlobalExceptionHandler.class处理
    mvc:
      #出现错误时, 直接抛出异常
      throw-exception-if-no-handler-found: true
通用返回类
package com.springboot.demo.common.vo;


import com.springboot.demo.common.enums.ResultStatusCode;

/**
 * Restful接口返回的数据模型
 * @author licanfeng
 *
 */
public class Result {
	/**
	 * 返回的代码,200表示成功,其他表示失败
	 */
	private int code;
	/**
	 * 成功或失败时返回的错误信息
	 */
    private String msg;
	/**
	 * 成功时返回的数据信息
	 */
    private Object data;

	public Result(int code, String msg, Object data){
		this.code = code;
		this.msg = msg;
		this.data = data;
	}

	/**
	 * 推荐使用此种方法返回
	 * @param resultStatusCode 枚举信息
	 * @param data 返回数据
	 */
	public Result(ResultStatusCode resultStatusCode, Object data){
		this(resultStatusCode.getCode(), resultStatusCode.getMsg(), data);
	}

    public Result(int code, String msg){
    	this(code, msg, null);
	}

	public Result(ResultStatusCode resultStatusCode){
    	this(resultStatusCode, null);
	}
    
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}

}

package com.springboot.demo.common.enums;


/**
 * 类描述: 主要时用于返回错误码和错误信息
 * @author licanfeng
 * @description 返回码和msg
 * @date 2019/3/12
 * @return
 */
public enum ResultStatusCode {
    OK(200, "OK"),
    HTTP_ERROR_100(100, "1XX错误"),
    HTTP_ERROR_300(300, "3XX错误"),
    HTTP_ERROR_400(400, "4XX错误"),
    HTTP_ERROR_500(500, "5XX错误"),
    SIGN_ERROR(120, "签名错误"),
    TIME_OUT(130, "访问超时"),
    KICK_OUT(300, "您已经在其他地方登录,请重新登录!"),
    BAD_REQUEST(407, "参数解析失败"),
    INVALID_TOKEN(401, "无效的授权码"),
    INVALID_CLIENTID(402, "无效的密钥"),
    REQUEST_NOT_FOUND(404, "访问地址不存在!"),
    METHOD_NOT_ALLOWED(405, "不支持当前请求方法"),
    REPEAT_REQUEST_NOT_ALLOWED(406, "请求重复提交"),
    SYSTEM_ERR(500, "服务器运行异常"),
    NOT_EXIST_USER_OR_ERROR_PWD(10000, "该用户不存在或密码错误"),
    NOT_PARAM_USER_OR_ERROR_PWD(10006, "用户名或密码为空"),
    LOGINED_IN(10001, "该用户已登录"),
    NOT_EXIST_BUSINESS(10002, "该商家不存在"),
    SHIRO_ERROR(10003, "登录异常"),
    UNAUTHO_ERROR(10004, "您没有该权限"),
    REDIS_ERROR(10006, "redis异常"),
    REDIS_CONNECT_ERROR(10007, "redis连接异常"),
    BIND_PHONE(10010, "请绑定手机号"),
    UPLOAD_ERROR(20000, "上传文件异常"),
    INVALID_CAPTCHA(30005, "无效的验证码"),
    USER_FROZEN(40000, "该用户已被冻结");

    private int code;
    private String msg;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    ResultStatusCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

统一异常处理类
package com.springboot.demo.common.exeception;

import com.springboot.demo.common.enums.ResultStatusCode;
import com.springboot.demo.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.ShiroException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;

import javax.validation.ConstraintViolationException;
/**
 * 类描述: 全局异常拦截处理器
 *  1.处理自定义异常
 *  2.未知异常统一返回服务器错误
 *  3.已经catch到的异常不会被捕获
 *  4.异常的体系结构中,哪个异常与目标方法抛出的异常血缘关系越紧密,就会被哪个捕捉到。
 * @see ExceptionHandler:统一处理某一类异常,从而能够减少代码重复率和复杂度
 * @see ControllerAdvice:异常集中处理,更好的使业务逻辑与异常处理剥离开
 * @see ResponseStatus:可以将某种异常映射为HTTP状态码 成功则Status Code: 200
 * @author licanfeng
 * @date 2019/3/11 16:13
 * @version 1.0
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({HttpMessageNotReadableException.class, MissingServletRequestParameterException.class, BindException.class,
            ServletRequestBindingException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class})
    public Result handleHttpMessageNotReadableException(Exception e) {
        log.error("参数解析失败", e);
        if (e instanceof BindException){
            return new Result(ResultStatusCode.BAD_REQUEST.getCode(), ((BindException)e).getAllErrors().get(0).getDefaultMessage());
        }
        return new Result(ResultStatusCode.BAD_REQUEST.getCode(), e.getMessage());
    }

    /**
     * 405 - Method Not Allowed
     * 带有@ResponseStatus注解的异常类会被ResponseStatusExceptionResolver 解析
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.error("不支持当前请求方法", e);
        return new Result(ResultStatusCode.METHOD_NOT_ALLOWED, null);
    }

    /**
     * 其他全局异常在此捕获
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public Result handleException(Throwable e) {
        log.error("服务运行异常", e);
        if (e instanceof ShiroException) {
            return new Result(ResultStatusCode.UNAUTHO_ERROR);
        } else if (e instanceof JedisConnectionException) {
            //redis连接异常
            return new Result(ResultStatusCode.REDIS_CONNECT_ERROR);
        } else if (e instanceof JedisException) {
            //redis异常
            return new Result(ResultStatusCode.REDIS_ERROR);
        }
        return new Result(ResultStatusCode.SYSTEM_ERR, null);
    }

}

一般的错误处理到这里基本上就能拦截掉,但是针对部分异常还是拦截不掉(比如请求接口不存在等)。这是因为Spring MVC的dodispatch前还有一些异常没有处理,此时需要单独对这类异常处理。

实现ErrorController接口
package com.springboot.demo.controller;

import com.springboot.demo.common.enums.ResultStatusCode;
import com.springboot.demo.common.vo.Result;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 类描述: 主要是解决未被全局异常处理的错误
 * 如果设置资源映射文件不存在:spring.resources.add-mappings 会导致swagger druid等web页面不能访问
* @author licanfeng
* @date 2019/3/15 12:05
* @version 1.0
*/
@RestController
@Slf4j
@Api(tags="错误接口")
public class FinalExceptionHandlerController implements ErrorController {
    @Override
    public String getErrorPath() {
        return "/error";
    }

    @GetMapping(value = "/error")
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Object error(HttpServletRequest request, HttpServletResponse response) {

        log.error("response error,httpCode:" + response.getStatus());
        // 错误处理逻辑
        int status = response.getStatus();
        if (status == 404) {
            return new Result(ResultStatusCode.REQUEST_NOT_FOUND, "小伙子你有点调皮哦!(*^▽^*)");
        } else if (status == 500) {
            return new Result(ResultStatusCode.SYSTEM_ERR, "小伙子你麻烦大了!(*^▽^*)");
        } else if (status >= 100 && status < 200) {
            return new Result(ResultStatusCode.HTTP_ERROR_100, null);
        } else if (status >= 300 && status < 400) {
            return new Result(ResultStatusCode.HTTP_ERROR_300, null);
        } else if (status >= 400 && status < 500) {
            return new Result(ResultStatusCode.HTTP_ERROR_400, null);
        } else {
            return new Result(ResultStatusCode.SYSTEM_ERR, "小伙子你麻烦大了!(*^▽^*)");
        }
    }

}

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值