创建业务 Exception
一般在实际项目中,推荐创建自己的 Exception
类型,这样在后期会更容易处理,也比较方便统一,否则,可能每个人都抛出自己喜欢的异常类型,而造成代码混乱
ServiceException
用于抛出业务逻辑校验异常
UnauthorizedException
用于抛出用户未登录异常
可根据自己的项目需求变化,但简单项目中这两个已经够用
ServiceException
package cn.myesn.exception;
public class ServiceException extends RuntimeException {
/**
* Constructs a new runtime exception with {@code null} as its
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*/
public ServiceException() {
super();
}
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public ServiceException(String message) {
super(message);
}
/**
* Constructs a new runtime exception with the specified detail message and
* cause. <p>Note that the detail message associated with
* {@code cause} is <i>not</i> automatically incorporated in
* this runtime exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new runtime exception with the specified cause and a
* detail message of <tt>(cause==null ? null : cause.toString())</tt>
* (which typically contains the class and detail message of
* <tt>cause</tt>). This constructor is useful for runtime exceptions
* that are little more than wrappers for other throwables.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public ServiceException(Throwable cause) {
super(cause);
}
/**
* Constructs a new runtime exception with the specified detail
* message, cause, suppression enabled or disabled, and writable
* stack trace enabled or disabled.
*
* @param message the detail message.
* @param cause the cause. (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @param enableSuppression whether or not suppression is enabled
* or disabled
* @param writableStackTrace whether or not the stack trace should
* be writable
* @since 1.7
*/
protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
UnauthorizedException
package cn.myesn.exception;
public class UnauthorizedException extends RuntimeException {
/**
* Constructs a new runtime exception with {@code null} as its
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*/
public UnauthorizedException() {
super();
}
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public UnauthorizedException(String message) {
super(message);
}
/**
* Constructs a new runtime exception with the specified detail message and
* cause. <p>Note that the detail message associated with
* {@code cause} is <i>not</i> automatically incorporated in
* this runtime exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public UnauthorizedException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new runtime exception with the specified cause and a
* detail message of <tt>(cause==null ? null : cause.toString())</tt>
* (which typically contains the class and detail message of
* <tt>cause</tt>). This constructor is useful for runtime exceptions
* that are little more than wrappers for other throwables.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public UnauthorizedException(Throwable cause) {
super(cause);
}
/**
* Constructs a new runtime exception with the specified detail
* message, cause, suppression enabled or disabled, and writable
* stack trace enabled or disabled.
*
* @param message the detail message.
* @param cause the cause. (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @param enableSuppression whether or not suppression is enabled
* or disabled
* @param writableStackTrace whether or not the stack trace should
* be writable
* @since 1.7
*/
protected UnauthorizedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
创建异常返回值包装类
package cn.myesn.exception;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class GlobalExceptionResponseResult {
private String message;
}
创建全局异常处理者
创建 GlobalExceptionHandler
处理类,该类处理 spring boot rest controller
中抛出的所有异常
package cn.myesn.handler;
import cn.myesn.exception.GlobalExceptionResponseResult;
import cn.myesn.exception.ServiceException;
import cn.myesn.exception.UnauthorizedException;
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.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 根本就没有传递参数时的异常
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public GlobalExceptionResponseResult handle(HttpMessageNotReadableException e) {
return new GlobalExceptionResponseResult().setMessage("参数不能为空");
}
// 传了参数,但没有通过 validation 时的异常
@ExceptionHandler(BindException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public GlobalExceptionResponseResult handle(BindException e) {
final ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new GlobalExceptionResponseResult().setMessage(objectError.getDefaultMessage());
}
// 代码中抛出的逻辑异常
@ExceptionHandler(ServiceException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public GlobalExceptionResponseResult handle(ServiceException e) {
return new GlobalExceptionResponseResult().setMessage(e.getMessage());
}
// 未登录异常
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public void handle(UnauthorizedException e) {
}
// 但以上几种错误都未能匹配到时,catch 一个基础的异常类型,基本上能捕获所有该捕获的异常类型
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public GlobalExceptionResponseResult handle(RuntimeException e) {
return new GlobalExceptionResponseResult().setMessage(String.format("未处理的异常:%s", e.getMessage()));
}
}
实际使用
新建一个 TestController
文件,在里面添加如下端点:
@GetMapping("check-username")
public ResponseEntity<?> checkUsername(@RequestParam String username) {
if (StringUtils.isBlank(username)) {
throw new ServiceException("用户名不能为空");
}
if (userService.existsUserName(username)) {
throw new ServiceException("用户名已存在");
}
return ResponseEntity.ok().build();
}
这样,写业务代码时,throw
就行了,代码整体更符合语义,也更加简洁明了