SpringBoot异常处理的方法

SpringBoot异常处理的方法

关于SpringMVC框架统一处理异常,需要自定义方法来处理,该方法:

  • 【注解】必须添加@ExceptionHandler注解;
  • 【访问权限】应该使用public权限;
  • 【返回值类型】与处理请求的方法的设计思路完全相同,即:如果需要转发或重定向,在使用@Controller注解的情况下,使用String作为返回值类型即可,则返回的字符串就是视图名,如果需要重定向,则返回的字符串必须以redirect:作为前缀并拼接目标路径,如果需要响应正文,在使用@Controller的情况下,当前处理异常的方法还需要添加@ResponseBody,或直接将@Controller替换为@RestController,则方法之前就不必添加@ResponseBody了,如果需要响应的正文是JSON格式的,则需要添加jackson依赖并将返回值设计为自定义类型;
  • 【方法名称】自定义;
  • 【参数列表】必须添加1个异常类型的参数,表示SpringMVC捕获并用于调用当前方法的异常对象,为了保证调用时不出错,该参数类型对于即将抛出的异常来说,“只能大不能小”!另外,不可以像处理请求的方法一样随心所欲的添加参数,但是,可选择性的添加HttpServletRequestHttpServletResponse类型的参数;

例如,可以在UserController中添加:

@ExceptionHandler
public R handleException(Throwable e) {
    if (e instanceof InviteCodeException) {
        return R.failure(2).setMessage("InviteCodeException");
    } else if (e instanceof PhoneDuplicateException) {
        return R.failure(3).setMessage("PhoneDuplicateException");
    } else if (e instanceof InsertException) {
        return R.failure(4).setMessage("InsertException");
    } else {
        return R.failure(998).setMessage("未知错误!");
    }
}

关于@ExceptionHandler注解,其源代码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

	/**
	 * Exceptions handled by the annotated method. If empty, will default to any
	 * exceptions listed in the method argument list.
	 */
	Class<? extends Throwable>[] value() default {};

}

通过以上源代码可以看到:@ExceptionHandler注解存在value属性,是默认属性(在配置时,不需要显式的声明属性名称,可以直接在注解中填写参数值),其类型是“异常类的数组”,表示“添加了注解的方法将处理的异常类型,如果没有配置该属性值,将按照方法的参数列表中的异常进行处理”。简单来说“当需要指定需要处理的某些异常种类时,可以在@EXceptionHandler注解中添加参数,或者将处理异常的方法的参数指定为某种异常,而其它的异常将不会被当前方法所处理!”。

另外,以上处理异常的方法,只作用于当前控制器类!如果需要将处理异常的方法作用于整个项目的任何控制类中的请求,可以:

  • 将处理异常的方法定义在控制器类的基类中;
  • 将处理异常的方法定义在任意类中,并在该类的声明之前添加@ControllerAdvice@RestControllerAdvice注解;

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

所以,可以在controller子包中创建GlobalExceptionHandler类,在类的声明之前添加@RestControllerAdvice,并将处理异常的方法移动到该类中:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ServiceException.class)
    public R handleException(Throwable e) {
        if (e instanceof InviteCodeException) {
            return R.failure(2).setMessage("InviteCodeException");
        } else if (e instanceof PhoneDuplicateException) {
            return R.failure(3).setMessage("PhoneDuplicateException");
        } else if (e instanceof InsertException) {
            return R.failure(4).setMessage("InsertException");
        } else {
            return R.failure(998).setMessage("未知错误!");
        }
    }

}

一旦使用了“统一处理异常”的机制,也就意味了同一种异常,无论在哪种情景下被抛出,都会是相同的处理方式,按照以上代码,处理异常时,封装的“错误描述信息”也是完全相同的!这是极为不合理的,关于“错误信息描述”,应该是由抛出异常的那一方进行描述,而不是处理异常的一方进行描述!

所以,应该在“注册”的业务中,抛出异常时进行描述:

@Override
public void regStudent(StudentRegisterDTO studentRegisterDTO) {
    // ...
    // 原有其它代码
    // ...

    if (classInfo == null) {
        throw new InviteCodeException("注册失败!邀请码错误!");
    }

    // ...
    // 原有其它代码
    // ...

    if (result != null) {
        throw new PhoneDuplicateException("注册失败!手机号码已经被占用!");
    }

    // ...
    // 原有其它代码
    // ...
    
    if (rows != 1) {
        throw new InsertException("注册失败!服务器忙,请稍后再次尝试!");
    }
}

然后,在处理异常时,调用异常对象的getMessage()方法就可以获取以上抛出时封装的异常描述信息:

@ExceptionHandler(ServiceException.class)
public R handleException(Throwable e) {
    if (e instanceof InviteCodeException) {
        return R.failure(2).setMessage(e.getMessage());
    } else if (e instanceof PhoneDuplicateException) {
        return R.failure(3).setMessage(e.getMessage());
    } else if (e instanceof InsertException) {
        return R.failure(4).setMessage(e.getMessage());
    } else {
        return R.failure(998).setMessage("未知错误!");
    }
}

由于以上处理异常时,每次处理时都使用的相同的做法,所以,还可以将R类再次调整,以便于管理代码!

R类中添加新的方法:

public static R failure(Integer failureState, Throwable e) {
    return new R().setState(failureState).setMessage(e.getMessage());
}

另外,关于创建R对象时使用到的状态码(state),其编号应该是有一定规律的,不应该就是1 > 2 > 3 ……这样编号!同时,这些编号应该使用静态常量来表示,以增加程序代码的可阅读性!

最终,R类应该调整为:

package cn.tedu.straw.commons.vo;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * 响应到客户端的JSON数据的封装类
 */
@Data
@Accessors(chain = true)
public class R<T> {

    /**
     * 响应状态码
     */
    private Integer state;
    /**
     * 出错时的错误提示信息
     */
    private String message;
    /**
     * 成功时响应给客户端的数据
     */
    private T data;
    
    /**
     * 操作成功
     *
     * @return 状态码已经标记为“成功”的对象
     */
    public static R ok() {
        return new R().setState(State.SUCCESS);
    }

    /**
     * 操作失败
     *
     * @param failureState 操作失败的状态码,取值推荐使用#link{R.State}
     * @param e            操作失败时抛出并被捕获的异常对象
     * @return 已经封装了操作失败的状态码、错误描述信息的对象
     * @see R.State
     */
    public static R failure(Integer failureState, Throwable e) {
        return new R().setState(failureState).setMessage(e.getMessage());
    }

    /**
     * 状态码
     */
    public static interface State {
        /**
         * 成功
         */
        int SUCCESS = 2000;
        /**
         * 邀请码错误
         */
        int ERR_INVITE_CODE = 4000;
        /**
         * 手机号码冲突
         */
        int ERR_PHONE_DUPLICATE = 4001;
        /**
         * 插入数据失败
         */
        int ERR_INSERT_FAIL = 4002;
        /**
         * 未知错误
         */
        int ERR_UNKNOWN = 9000;
    }

}

关于处理异常的代码:

package cn.tedu.straw.api.controller;

import cn.tedu.straw.api.ex.InsertException;
import cn.tedu.straw.api.ex.InviteCodeException;
import cn.tedu.straw.api.ex.PhoneDuplicateException;
import cn.tedu.straw.api.ex.ServiceException;
import cn.tedu.straw.commons.vo.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ServiceException.class)
    public R handleException(Throwable e) {
        if (e instanceof InviteCodeException) {
            return R.failure(R.State.ERR_INVITE_CODE, e);
        } else if (e instanceof PhoneDuplicateException) {
            return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
        } else if (e instanceof InsertException) {
            return R.failure(R.State.ERR_INSERT_FAIL, e);
        } else {
            return R.failure(R.State.ERR_UNKNOWN, e);
        }
    }

}

关于控制器的代码:

package cn.tedu.straw.api.controller;


import cn.tedu.straw.api.dto.StudentRegisterDTO;
import cn.tedu.straw.api.service.IUserService;
import cn.tedu.straw.commons.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author tedu.cn
 * @since 2020-08-11
 */
@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    @Autowired
    private IUserService userService;

    // http://localhost:8080/api/v1/users/student/register?phone=13100131001&password=1234&inviteCode=JSD1912-876840
    @RequestMapping("/student/register")
    public R regStudent(StudentRegisterDTO studentRegisterDTO) {
        userService.regStudent(studentRegisterDTO);
        return R.ok();
    }

}

最后,还应该在application.properties中添加:

# 将响应的JSON数据设置为“不为null”时显示,反之,为null的属性将不会出现在JSON数据中
spring.jackson.default-property-inclusion=non_null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值