Springboot ControllerAdvice轻松搞定全局异常处理

本文在 【从零到一 Springboot+Mybatis_Plus示例】 的基础之上,提出问题分析改进。

1 、 问题分析

首先看一段上文的服务是实现类(NewsServiceImpl)的示例代码:

/**
	 * 添加新闻
	 * 
	 * @param title
	 * @param summary
	 * @return
	 */
	public boolean insertNews(String title, String summary) {
		if (title == null || title.length() < 1) {
			return false;
		}
		News record = new News();
		record.setTitle(title);
		record.setSummary(summary);
		record.setCreateTime(LocalDateTime.now());
		;
		return super.save(record);
	}

我们简单分析下上面的代码问题:

  • 上边的代码返回结果只知道添加失败还是成功,如果失败了无法得知具体的错误信息。
  • service方法在执行过程出现异常在哪捕获?在service中需要都加try/catch,如果在controller也需要添加try/catch,代码冗余严重且不易维护。
    基于以上问题提出解决方案:
  • 在Service方法中的编码顺序是先校验判断,有问题则抛出具体的异常信息,最后执行具体的业务操作,返回成功信息。
  • 在统一异常处理类中去捕获异常,无需controller捕获异常,向用户返回统一规范的响应信息。
  • 定义统一返回结果实体等
    例如下面这样:
/**
	 * 添加新闻
	 * 
	 * @param title
	 * @param summary
	 * @return
	 */
	public InsertResult insertNews(String title, String summary) {
		if (title == null || title.length() < 1) {
			//抛出自定义异常
		}
		News record = new News();
		record.setTitle(title);
		record.setSummary(summary);
		record.setCreateTime(LocalDateTime.now());
		;
		return new InsertResult(.....);
	}
2、 异常处理流程

系统对异常的处理使用统一的异常处理流程:

  • 自定义异常类型。
  • 自定义错误代码及错误信息。
  • 对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。
    可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。
  • 对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为RuntimeException类型(运行时异常)。
  • 可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。
    异常抛出及处理流程:
    在这里插入图片描述
  • 在controller、service、dao中程序员抛出自定义异常;springMVC框架抛出框架异常类型
  • 统一由异常捕获类捕获异常,并进行处理
  • 捕获到自定义异常则直接取出错误代码及错误信息,响应给用户。
  • 捕获到非自定义异常类型首先从Map中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误信息并响应给用户,如果从Map中找不到异常类型所对应的错误代码则统一为99999错误代码并响应给用户。
  • 将错误代码及错误信息以Json格式响应给用户。
3、可预知异常处理

新建exception包,定义异常类型。

3.1 自定义异常类
package com.qqxhb.mybatis.exception;

import com.qqxhb.mybatis.model.response.ResultCode;

public class CustomException extends RuntimeException {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	// 错误代码
	ResultCode resultCode;

	public CustomException(ResultCode resultCode) {
		this.resultCode = resultCode;
	}

	public ResultCode getResultCode() {
		return resultCode;
	}

}

3.2 自定义抛出异常类
package com.qqxhb.mybatis.exception;

import com.qqxhb.mybatis.model.response.ResultCode;

public class ExceptionCast {

	public static void cast(ResultCode resultCode) {
		throw new CustomException(resultCode);
	}
}

3.3 自定义异常捕获类
package com.qqxhb.mybatis.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.qqxhb.mybatis.model.response.ResponseResult;
import com.qqxhb.mybatis.model.response.ResultCode;

/**
 * 统一异常捕获类
 * 
 **/
@ControllerAdvice // 控制器增强
public class ExceptionCatch {

	private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);


	// 捕获CustomException此类异常
	@ExceptionHandler(CustomException.class)
	@ResponseBody
	public ResponseResult customException(CustomException customException) {
		// 记录日志
		LOGGER.error("catch exception:{}", customException.getMessage());
		ResultCode resultCode = customException.getResultCode();
		return new ResponseResult(resultCode);
	}

}

3.4 错误码NewsCode
package com.qqxhb.mybatis.model.response;

import lombok.ToString;

@ToString
public enum NewsCode implements ResultCode {
	REQUIRED_PARAMISNULL(false, 3000, "必填参数为空!");
	// 操作代码
	boolean success;
	// 操作代码
	int code;
	// 提示信息
	String message;

	private NewsCode(boolean success, int code, String message) {
		this.success = success;
		this.code = code;
		this.message = message;
	}

	@Override
	public boolean success() {
		return success;
	}

	@Override
	public int code() {
		return code;
	}

	@Override
	public String message() {
		return message;
	}
}

3.5修改业务代码 测试异常
public boolean insertNews(String title, String summary) {
		if (title == null || title.length() < 1) {
			ExceptionCast.cast(NewsCode.REQUIRED_PARAMISNULL);
			return false;
		}
		News record = new News();
		record.setTitle(title);
		record.setSummary(summary);
		record.setCreateTime(LocalDateTime.now());
		return super.save(record);
	}

在这里插入图片描述

4、不可预知异常处理
4.1 不可知异常实例

比如发其一个不存在的请求,这样的响应信息在客户端是无法解析。
在这里插入图片描述

4.2 异常捕获方法

针对上边的问题其解决方案是:

  • 我们在map中配置HttpRequestMethodNotSupportedException的错误为非法请求(CommonCode.INVALID_REQUEST)。
  • 在异常捕获类中对Exception异常进行捕获,并从map中获取异常类型对应的错误代码,如果存在错误代码则返回此错误,否则统一返回99999错误。
    修改异常捕获类:
package com.qqxhb.mybatis.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.common.collect.ImmutableMap;
import com.qqxhb.mybatis.model.response.CommonCode;
import com.qqxhb.mybatis.model.response.ResponseResult;
import com.qqxhb.mybatis.model.response.ResultCode;

/**
 * 统一异常捕获类
 * 
 **/
@ControllerAdvice // 控制器增强
public class ExceptionCatch {

	private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);

	// 定义map,配置异常类型所对应的错误代码
	private static ImmutableMap<Class<? extends Throwable>, ResultCode> EXCEPTIONS;
	// 定义map的builder对象,去构建ImmutableMap
	protected static ImmutableMap.Builder<Class<? extends Throwable>, ResultCode> builder = ImmutableMap.builder();

	// 捕获CustomException此类异常
	@ExceptionHandler(CustomException.class)
	@ResponseBody
	public ResponseResult customException(CustomException customException) {
		// 记录日志
		LOGGER.error("catch exception:{}", customException.getMessage());
		ResultCode resultCode = customException.getResultCode();
		return new ResponseResult(resultCode);
	}

	// 捕获Exception此类异常
	@ExceptionHandler(Exception.class)
	@ResponseBody
	public ResponseResult exception(Exception exception) {
		// 记录日志
		LOGGER.error("catch exception:{}", exception.getMessage());
		if (EXCEPTIONS == null) {
			EXCEPTIONS = builder.build();// EXCEPTIONS构建成功
		}
		// 从EXCEPTIONS中找异常类型所对应的错误代码,如果找到了将错误代码响应给用户,如果找不到给用户响应99999异常
		ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
		if (resultCode != null) {
			return new ResponseResult(resultCode);
		} else {
			// 返回99999异常
			return new ResponseResult(CommonCode.SERVER_ERROR);
		}

	}

	static {
		// 定义异常类型所对应的错误代码
		builder.put(HttpRequestMethodNotSupportedException.class, CommonCode.INVALID_REQUEST);
	}
}

4.3 测试

未设置HttpRequestMethodNotSupportedException的错误码时:
在这里插入图片描述
添加错误码之后测试:
在这里插入图片描述
源码地址:https://github.com/qqxhb/springboot-mybatis-demo
项目名称:springboot-handle-exception

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值