⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/songguopeng/article/details/98961787 「songguopeng」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

一,为什么要用全局异常处理?

在日常开发中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,且不美观,不利于后期维护。

为解决该问题,计划将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端的String、Map、JSONObject、ModelAndView等结果类型。

二,应用场景是什么?

  • 非常方便的去掉了try catch这类冗杂难看的代码,有利于代码的整洁和优雅
  • 自定义参数校验时候全局异常处理会捕获异常,将该异常统一返回给前端,省略很多if else代码
  • 当后端出现异常时,需要返回给前端一个友好的界面的时候就需要全局异常处理
  • 因为异常时层层向上抛出的,为了避免控制台打印一长串异常信息

三、如何进行全局异常捕获和处理?

一共有两种方法:

  • Spring的AOP(较复杂)
  • @ControllerAdvice结合@ExceptionHandler(简单)

四、@ControllerAdvice和@ExceptionHandler怎么用?

1、Controller Advice字面上意思是“控制器通知”,Advice除了“劝告”、“意见”之外,还有“通知”的意思。

可以将@ExceptionHandler(标识异常类型对应的处理方法)标记的方法提取出来,放到一个类里,并将加上@ControllerAdvice,这样,所有的控制器都可以用了

@ControllerAdvice
public class ControllerHandlers(){
@ExceptionHandler
public String errorHandler(Exception e){
return "error";
}
}

2、 因为@ControllerAdvice@Componen标记,所以他可以被组件扫描到并放入Spring容器

3、 如果只想对一部分控制器通知,比如某个包下边的控制器,就可以这样写:

@ControllerAdvice("com.labor")
public class ControllerHandlers(){}

也可以直接写类名

@ControllerAdvice(basePackageClasses = ***.class)
public class ControllerHandlers(){}

也可以传多个类

@ControllerAdvice(assignableTypes = {***.class,***.class})
public class ControllerHandlers(){}

4、 控制器通知还有一个兄弟,@RestControllerAdvice,如果用了它,错误处理方法的返回值不会表示用的哪个视图,而是会作为HTTP body处理,即相当于错误处理方法加了@ResponseBody注解。

@RestControllerAdvice
public class ControllerHandlers(){
@ExceptionHandler
public String errorHandler(Exception e){
return "error";
}
}

5、@ExceptionHandler注解的方法只能返回一种类型,在前后端分离开发中我们通常返回,统一返回类型和优化错误的提示,我们可以封装我们自己的返回Map

public class AjaxResult extends HashMap<String, Object> {

private static final long serialVersionUID = 1L;

public static final String CODE_TAG = "code";

public static final String MSG_TAG = "msg";

public static final String DATA_TAG = "data";

/**
* 状态类型
*/
public enum Type {
/**
* 成功
*/
SUCCESS(1),
/**
* 警告
*/
WARN(2),
/**
* 错误
*/
ERROR(0),
/**无权限*/
UNAUTH(3),
/**未登录、登录超时*/
UNLOGIN(4);
private final int value;

Type(int value) {
this.value = value;
}

public int value() {
return this.value;
}
}

/**
* 状态类型
*/
private Type type;

/**
* 状态码
*/
private int code;

/**
* 返回内容
*/
private String msg;

/**
* 数据对象
*/
private Object data;

/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult() {
}

/**
* 初始化一个新创建的 AjaxResult 对象
* @param type 状态类型
* @param msg 返回内容
*/
public AjaxResult(Type type, String msg) {
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
}

/**
* 初始化一个新创建的 AjaxResult 对象
* @param type 状态类型
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(Type type, String msg, Object data) {
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
/* 数据为空的时候,还是需要把参数传给前台 huangqr @2019.7.19
if (StringUtils.isNotNull(data)) {
super.put(DATA_TAG, data);
}*/
super.put(DATA_TAG, data);
}

/**
* 返回成功消息
* @return 成功消息
*/
public static AjaxResult success() {
return AjaxResult.success("操作成功");
}

/**
* 返回成功数据
* @return 成功消息
*/
public static AjaxResult success(Object data) {
return AjaxResult.success("操作成功", data);
}

/**
* 返回成功消息
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg) {
return AjaxResult.success(msg, null);
}

/**
* 返回成功消息
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data) {
return new AjaxResult(Type.SUCCESS, msg, data);
}

/**
* 返回警告消息
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult warn(String msg) {
return AjaxResult.warn(msg, null);
}

/**
* 返回警告消息
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data) {
return new AjaxResult(Type.WARN, msg, data);
}

/**
* 返回错误消息
* @return
*/
public static AjaxResult error() {
return AjaxResult.error("操作失败");
}

/**
* 返回错误消息
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
}

/**
* 返回错误消息
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data) {
return new AjaxResult(Type.ERROR, msg, data);
}

/**
* 无权限返回
* @return
*/
public static AjaxResult unauth() {
return new AjaxResult(Type.UNAUTH, "您没有访问权限!", null);
}
/**
* 无权限
*
* @param msg
* @return com.wanda.labor.framework.web.domain.AjaxResult
* @exception
*/
public static AjaxResult unauth(String msg) {
return new AjaxResult(Type.UNAUTH, msg, null);
}
/**
* 未登录或登录超时。请重新登录
*
* @param
* @return com.wanda.labor.framework.web.domain.AjaxResult
* @exception
*/
public static AjaxResult unlogin() {
return new AjaxResult(Type.UNLOGIN, "未登录或登录超时。请重新登录!", null);
}

public Type getType() {
return type;
}

public void setType(Type type) {
this.type = type;
}

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;
}


public static class SUCCESS{

public static AjaxResult data(Object data){
return new AjaxResult(Type.SUCCESS, "操作成功 Operation Successful", data);
}

public static AjaxResult iMessagesg(String msg){
return new AjaxResult(Type.SUCCESS, msg, null);
}

public static AjaxResult imsgAndData(String msg,Object data){
return new AjaxResult(Type.SUCCESS, msg, data);
}
}



@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("code", getCode())
.append("msg", getMsg()).append("data", getData()).toString();
}
}

6、 完善全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

/**
* 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
*/
@ExceptionHandler(AuthorizationException.class)
public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e) {
//log.error(e.getMessage(), e);
if (ServletUtils.isAjaxRequest(request)) {
return AjaxResult.unauth(PermissionUtils.getMsg(e.getMessage()));
} else {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error/unauth");
return modelAndView;
}
}

/**
* 请求方式不支持
*/
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public AjaxResult handleException(HttpRequestMethodNotSupportedException e) {
log.error(e.getMessage(), e);
return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
}

/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult notFount(RuntimeException e) {
log.error("运行时异常:", e);
return AjaxResult.error("运行时异常:" + e.getMessage());
}

/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e) {
log.error(e.getMessage(), e);
return AjaxResult.error("服务器错误,请联系管理员");
}

/**
* 校验异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public AjaxResult exceptionHandler(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getDefaultMessage() + "!";
}
return AjaxResult.error(errorMesssage);
}

/**
* 校验异常
*/
@ExceptionHandler(value = BindException.class)
public AjaxResult validationExceptionHandler(BindException e) {
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getDefaultMessage() + "!";
}
return AjaxResult.error(errorMesssage);
}

/**
* 校验异常
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public AjaxResult ConstraintViolationExceptionHandler(ConstraintViolationException ex) {
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
List<String> msgList = new ArrayList<>();
while (iterator.hasNext()) {
ConstraintViolation<?> cvl = iterator.next();
msgList.add(cvl.getMessageTemplate());
}
return AjaxResult.error(String.join(",",msgList));
}

/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public AjaxResult businessException(BusinessException e) {
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}

/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult demoModeException(DemoModeException e) {
return AjaxResult.error("演示模式,不允许操作");
}

}

六、@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了

pom文件引入validation的jar包。

<!-- 校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

等待校验的object

public class Person {
/**
* @PersonName(prefix = "song"):自定义注解
*/
@NotNull
@PersonName(prefix = "song")
private String name;
@Min(value = 18)
@Max(value = 30, message = "超过30岁的不要!")
private Integer age;
}

使用

/**
* 开启校验注解:@Valid
*/
@RestController
public class PersonController {
@PostMapping("/person")
public Person savePerson(@Valid @RequestBody Person person){
return person;
}
}

全局异常处理里有相应的处理方法

/**
* 校验异常
*/
@ExceptionHandler(value = BindException.class)
public AjaxResult validationExceptionHandler(BindException e) {
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getDefaultMessage() + "!";
}
return AjaxResult.error(errorMesssage);
}

@RequestBody@RequestParam注解的请求实体,校验异常类是不同的

七、自定义异常以及事务回滚

public class MyException extends RuntimeException {
//这个地方不要写exception,因为Spring是只对运行时异常进行事务回滚,
//如果抛出的是exception是不会进行事务回滚的。

}

如果是在service层里捕获异常统一去处理,那为了保证事务的回滚,需要抛出RuntimeException

try {
} catch (Exception e) {
e.printStackTrace();
logger.error("发生异常");
throw new RuntimeException();
}

关于try-catch-finally中,finally的作用,finally设计之初就是为了关闭资源,如果在finally中使用return语句,会覆盖try或者catch的返回值,最常见的就是覆盖异常,即便catch往上抛了异常,也会被覆盖,返回finally中return语句的返回值。

文章目录
  1. 1. 一,为什么要用全局异常处理?
  2. 2. 二,应用场景是什么?
  3. 3. 三、如何进行全局异常捕获和处理?
  4. 4. 四、@ControllerAdvice和@ExceptionHandler怎么用?
  5. 5. 六、@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了
  6. 6. 七、自定义异常以及事务回滚