你好,我是田哥
在实际开发过程中,不可避免的是需要处理各种异常,异常处理方法随处可见,所以代码中就会出现大量的try {...} catch {...} finally {...}
代码块,不仅会造成大量的冗余代码,而且还影响代码的可读性,所以对异常统一处理非常有必要。为此,我们定义了一个统一的异常类BusinessException
。
自定义异常和统一校验参数已用于 充电桩项目 中。
/**
* @author tianwc 公众号:java后端技术全栈、面试专栏
* @version 1.0.0
* @date 2023年06月11日 22:41
* 博客地址:<a href="http://woaijava.cc/">博客地址</a>
* <p>
* 自定义异常
*/
@Getter
public class BusinessException extends RuntimeException {
/**
* http状态码
*/
private int code;
private Object object;
public BusinessException(String message, int code, Object object) {
super(message);
this.code = code;
this.object = object;
}
public BusinessException(ResultCode resultCode) {
this.code = resultCode.getCode();
this.object = resultCode.getMessage();
}
public BusinessException(ResultCode resultCode, String message) {
this.code = resultCode.getCode();
this.object = message;
}
public BusinessException(String message) {
super(message);
}
}
一些常见需要对参数进校验,项目中自定义了一个校验工具类ParamValidate
;
public class ParamValidate {
public static void isNull(Object object, String message) {
if (object == null) {
throw new BusinessException(ResultCode.PARAMETER_EMPTY, message);
}
}
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new BusinessException(ResultCode.PARAMETER_ERROR, message);
}
}
public static void notEmpty(Collection collection, String message) {
isNull(collection, message);
if (collection.size() == 0) {
throw new BusinessException(ResultCode.PARAMETER_ERROR, message);
}
}
}
工具使用:
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private MessageTemplateSingleton messageTemplateSingleton;
@GetMapping("/index1")
public CommonResult<MessageTemplateSingleton.Template> index() {
ParamValidate.isNull(null, "参数为空");
MessageTemplateSingleton.Template template = messageTemplateSingleton.getTemplate(1);
return CommonResult.success(template);
}
}
自定义异常统一处理:
@RestControllerAdvice
@Slf4j
public class ChargeStationAdvice {
@ExceptionHandler(Exception.class)
public CommonResult<String> doException(Exception e) {
log.error("统一处理自定义异常机制,触发异常 msg ", e);
String message = null;
int errorCode = ResultCode.FAILED.getCode();
//自定义异常
if (e instanceof BusinessException) {
BusinessException exception = (BusinessException) e;
message = exception.getMessage();
errorCode = exception.getCode();
} else if (e instanceof HttpRequestMethodNotSupportedException) {
message = "不支持GET/POST方法";
} else if (e instanceof NoHandlerFoundException) {
message = "请求接口不存在";
} else{
message = "系统异常";
}
return CommonResult.failed(errorCode, message);
}
}
测试:
GET http://localhost:9001/test/index1
返回:
{
"code": 400007,
"message": null,
"data": null
}
到此我们的自定义异常的定义、处理、测试就搞定了。
但是,我们在上面使用到的是统一异常处理,我们在方法参数验证时候,也会用到统一异常处理。
我们后台使用spring
为我们提供好的统一校验的工具spring-boot-starter-validation
对请求进行校验。
pom依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
这里通过注解封装了几种常用的校验
@NotNull
不能为null@NotEmpty
不能为null、空字符串、空集合@NotBlank
不能为null、空字符串、纯空格的字符串@Min
数字最小值不能小于x@Max
数字最大值不能大于x@Email
字符串为邮件格式@Max
数字最大值不能大于x@Size
字符串长度最小为x、集合长度最小为x@Pattern
正则表达式因为校验不通过时,会抛出各种异常,所以,我们需要对这些异常进行统一处理。
@RestControllerAdvice
@Slf4j
public class ChargeStationAdvice {
@ExceptionHandler(Exception.class)
public CommonResult<String> doException(Exception e) {
log.error("统一异常处理机制,触发异常 msg ", e);
String message = null;
int errorCode = ResultCode.FAILED.getCode();
//自定义异常
if (e instanceof BusinessException) {
BusinessException exception = (BusinessException) e;
message = exception.getMessage();
errorCode = exception.getCode();
} else if (e instanceof HttpRequestMethodNotSupportedException) {
message = "不支持GET/POST方法";
} else if (e instanceof NoHandlerFoundException) {
message = "请求接口不存在";
} else if (e instanceof MissingServletRequestParameterException) {
errorCode = ResultCode.PARAMETER_EMPTY.getCode();
message = String.format("缺少必要参数[%s]", ((MissingServletRequestParameterException) e).getParameterName());
} else if (e instanceof MethodArgumentNotValidException) {
BindingResult result = ((MethodArgumentNotValidException) e).getBindingResult();
FieldError error = result.getFieldError();
errorCode = ResultCode.PARAMETER_EMPTY.getCode();
message = error == null ? ResultCode.PARAMETER_ERROR.getMessage() : error.getDefaultMessage();
} else if (e instanceof BindException) {
errorCode = ResultCode.PARAMETER_EMPTY.getCode();
message = e.getMessage();
} else if (e instanceof ConstraintViolationException) {
Optional<ConstraintViolation<?>> first = ((ConstraintViolationException) e).getConstraintViolations().stream().findFirst();
errorCode = ResultCode.PARAMETER_EMPTY.getCode();
message = first.get().getMessage();
}
return CommonResult.failed(errorCode, message);
}
}
上面几个异常进行解释说明:
MissingServletRequestParameterException
:加了@RequestParam注解,但是接口调用时没有传指定的参数(注意:是没有传,而不是传了,但是值是null)。
MethodArgumentNotValidException
:经过测试,当校验的参数放在对象中,接口的请求方式是post请求,用@Valid @RequestBody方式接受参数时,如果报错,会被该捕获器捕获。
BindException
:经过测试,当校验参数写在类中,接口请求方式是get请求时,报错会被该捕获器捕获。
ConstraintViolationException
:传了值,但是不符合要求。@NotNull(message = “最大值不能为空”)
,@Min(value = 10,message = "参数必须大于10")
,要求传非null值,且值必须大于10,否则会返回错误信息。经过测试,当校验参数直接写在接口上,而不是写在类中,报错会被该捕获器捕获。
参数校验实体类:
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* @author tianwc 公众号:java后端技术全栈、面试专栏
* @version 1.0.0
* @date 2023年05月09日 20:03
* 博客地址:<a href="http://woaijava.cc/">博客地址</a>
*/
@Data
public class TestDto implements Serializable {
@NotNull
@Min(value = 10,message = "参数必须大于10")
private Long id;
@NotEmpty(message = "name参数不能为空")
private String name;
}
注意:每个注解对应的包路径。
我们在Controller层使用TestDto
,并使用@Valid
注解,使校验的注解生效:
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private MessageTemplateSingleton messageTemplateSingleton;
@PostMapping("/index2")
public CommonResult<MessageTemplateSingleton.Template> index2(@RequestBody @Valid TestDto testDto) {
log.info("入参={}", testDto);
MessageTemplateSingleton.Template template = messageTemplateSingleton.getTemplate(1);
return CommonResult.success(template);
}
}
注意:注解
@Valid
的全路径为:javax.validation.Valid;
测试:
POST http://localhost:9001/test/index2
Content-Type: application/json; charset=UTF-8
{ "id": 11, "name": "" }
返回:
{
"code": 400007,
"message": "name参数不能为空",
"data": null
}
到此,我们统一参数校验也搞定了。
联系客服