0 本文主要涉及
在基于Spring和SpringMVC的前后端分离的JavaWeb项目中一种异常处理以及接口参数校验方案介绍说明
1 方案简介
网上介绍的全局异常处理一种是老的基于ModelAndView后端返回的是错误页面并不适合前后端分离架构的后端使用,还有一种需要在每个controler里写点代码不是全局的方案,接下来介绍的是一种全局处理的的返回JSON格式数据的后端异常处理方案,顺带也处理了参数校验的逻辑(原理在代码中注释说明了)。
2 配置实现
0,pom.xml依赖配置
<!--Web-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--参数校验-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<!--数据格式化-->
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
1,代码配置实现
@RestControllerAdvice
public class MyGlobalExceptionHandler extends ResponseEntityExceptionHandler
{
//不同环境返回不同的错误信息
@Data
@AllArgsConstructor
class DevExceptionInfoType
{
//返回json数据的格式
Integer exceptionCode;
Date dateTime;
String message;
String exceptionType;
List stackTreace;
}
@Data
@AllArgsConstructor
class ProdExceptionInfoType
{
//返回json数据的格式
Integer exceptionCode;
Date dateTime;
String message;
}
//用户判断运行环境
@Value("${debug.exception.info.show}")
Boolean isDebugInfoShow=false;
@Override
//重写springmvc默认异常处理(查看源码发现各种异常最终在这个函数处理,不包含HttpStatus.INTERNAL_SERVER_ERROR 500
public ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request)
{
//参数校验异常处理
if(ex instanceof BindException)
{
return ValidationExceptionHandler.defaultExceptionHandler((BindException) ex, headers, status, request);
}
//其他异常
List<String> stackTreace = new ArrayList<>();
for(StackTraceElement element : ex.getStackTrace())
{
stackTreace.add(element.toString());
}
if(isDebugInfoShow)
{
return new ResponseEntity<>(new DevExceptionInfoType(ex instanceof MyMessageException ?((MyMessageException)ex).exceptionCode:ExceptionCode.deault, new Date(), ex.toString(), ex.getClass().toString(), stackTreace), headers, status);
}
else
{
return new ResponseEntity<>(new ProdExceptionInfoType(ex instanceof MyMessageException ?((MyMessageException)ex).exceptionCode:ExceptionCode.deault, new Date(), ex.toString()), headers, status);
}
}
//这里示例字处理了MyMessageException异常,实际按照业务可能会有多种异常
//其他异常处理,返回HttpStatus.INTERNAL_SERVER_ERROR 500
@ExceptionHandler(value = Exception.class)
public Object defaultExceptionHandler(Exception ex)
{
List<String> stackTreace = new ArrayList<>();
for(StackTraceElement element : ex.getStackTrace())
{
stackTreace.add(element.toString());
}
//输出错误信息到日志
org.slf4j.LoggerFactory.getLogger(getClass()).error("\n" + ex.toString(), ex);
if(isDebugInfoShow)
{
return new ResponseEntity<>(new DevExceptionInfoType(ex instanceof MyMessageException ?((MyMessageException)ex).exceptionCode:ExceptionCode.deault, new Date(), ex.getMessage(), ex.getClass().toString(), stackTreace), HttpStatus.INTERNAL_SERVER_ERROR);
}
else
{
return new ResponseEntity<>(new ProdExceptionInfoType(ex instanceof MyMessageException ?((MyMessageException)ex).exceptionCode:ExceptionCode.deault, new Date(), ex.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
2,使用示例
异常处理:在Service层只需要直接抛出自定义的的业务异常即可,可以附带一些错误信息定义在业务异常的类中,最后会返回到前端业务异常的数据,前端根据code判断错误类型并做相应处理即可。
参数校验:
简单的,在Controler入参增加@Validated注解,并且在入参的Bean对象中通过注解设置好校验条件(
校验条件写法:https://segmentfault.com/a/1190000006908069)
@Data
@Accessors(chain = true)
public class TestQuery {
@NotEmpty(message = "id不可为空")
private String id;
}
public ResponseResult getTest(@Validated TestQuery testQueryParam) {
return ResponseResult.create();
}
如果同一个参数的Bean要用在不同的方法做入参并且条件不一样的话就需要换一种写法,通过不同的接口进行分组校验
@Data
@Accessors(chain = true)
public class PlanInfoVO {
public interface Add {
}
public interface Update {
}
@NotNull(message = "id不可为空", groups = {Update.class})
@Null(message = "id必须为空", groups = {Add.class})
private String id;
}
//校验的注解还需要加载分组信息
public ResponseResult getTest( @Validated({TestQuery.Add.class}) TestQuery testQueryParam) {
return ResponseResult.create();
}