在项目中,特别是前后端分离的项目,后台往往需要向前端返回统一的json数据格式,如
public Result findAll{
try{
//业务代码...
return new Result("200","操作成功",json数据);
}catch(Exception e){
e.printStackTrace();
return new Result("400","操作失败",e.getMessage());
}
}
这只是一个方法,也会有add()方法,delete()方法等等,在这每个接口方法上可能都需要以这种方式来捕获可能出现的异常。这时会出现这几种问题:
1、上面的代码若发生异常,返回的信息是“操作失败”,详细的是一个异常的名称,如java.lang.IllegalArgumentException,可前端如何识别这个异常信息呢?
2、try/catch在哪里捕获呢?service层往往配置事务,若出现异常就rollBack,向上抛出异常,那么就需要在controller进行try/catch捕获service的异常。若在service都进行try/catch,controller层也可能需要添加try/catch,这样代码会出现严重冗余,且不易维护。
3、如何统一定义返回给页面的json信息(特别是异常信息)?
异常可以分为两类,可预知异常和不可预知异常
可预知异常:如修改某个实体类,但这个实体类在数据库中被删除了,可能不存在,这时可以先进行判断,自定义异常并返回。
不可预知异常:如服务器宕机,前端数据为空或格式错误,可以先将此类异常都放到一个map中,否则返回统一的异常格式。
新建springboot项目,使用SSM框架,对product进行添加操作的异常模拟。
CREATE TABLE `product` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`id_code` varchar(255) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_code` (`id_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
org.springframework.boot
spring-boot-starter-parent
1.5.10.RELEASE
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
5.1.32
runtime
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
org.projectlombok
lombok
1.16.20
provided
com.google.guava
guava
19.0
com.fasterxml.jackson.core
jackson-databind
2.7.9.1
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-compiler-plugin
1.8
UTF-8
/**
* @Author : laoyog
* @Date : 2019/2/15 0015
* @Description :商品
* 字段中,商品识别码idCode唯一且非空
*/
@Data
@ToString
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class Product {
private Long id;
//商品识别码
private String idCode;
//商品名称
private String name;
//商品类型
private String type;
//其他......
}
结构如下:
/**
* @Author : laoyog
* @Date : 2019/2/15 0015
* @Description :定义返回结果的接口,具体使用它的实现类
*/
public interface ResultCode {
boolean success();
int code();
String message();
}
/**
* @Author : laoyog
* @Date : 2019/2/15 0015
* @Description :通用返回代码
* 如返回成功,不可预知的异常等
*/
@ToString
public enum CommonCode implements ResultCode {
/**
* 参数异常
*/
INVALID_PARAM(false,10001,"非法参数"),
/**
* 商品识别码已存在
*/
IdCODE_EXIST(false,20001,"商品识别码已存在"),
//其他异常识别码,自定义......
/**
* 操作成功
*/
SUCCESS(true,10000,"操作成功");
boolean success;
int code;
String message;
CommonCode(boolean success,int code,String message){
this.success = success;
this.code = code;
this.message = message;
}
@Override
public boolean success() { return success; }
@Override
public int code() { return code; }
@Override
public String message() { return message; }
}
@Data
@ToString
@NoArgsConstructor
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ResponseResult {
boolean success = true;
int code = 10000;
String message;
T t;
public ResponseResult(ResultCode resultCode) {
this.success = resultCode.success();
this.code = resultCode.code();
this.message = resultCode.message();
}
public ResponseResult(ResultCode resultCode,T t) {
this.success = resultCode.success();
this.code = resultCode.code();
this.message = resultCode.message();
this.t = t;
}
}
@RestController
@RequestMapping("/product")
public class ProductController implements ProductControllerApi {
@Autowired
private ProductService productService;
@PostMapping(value = "/add")
@Override
public ResponseResult add(@RequestBody Product product) {
return productService.add(product);
}
}
@Service
@Transactional(rollbackFor = Exception.class)
public class ProductService {
@Autowired
private ProductMapper productMapper;
/**
* 添加商品
* @param product
* @return
*/
public ResponseResult add(Product product) {
productMapper.add(product);
return new ResponseResult(CommonCode.SUCCESS,product);
}
}
@Mapper
@Repository
public interface ProductMapper {
@Insert(value = "insert into product(id_code,name,type) values (#{idCode},#{name},#{type})")
int add(Product product);
}
下面进行异常的处理操作
/**
* @Author : laoyog
* @Date : 2019/2/19 0019
* @Description :可预知异常处理 自定义异常类型
*/
public class CustomizeException extends RuntimeException {
private ResultCode resultCode;
public CustomizeException(ResultCode resultCode){
super("错误代码:"+resultCode.code()+",错误信息:"+resultCode.message());
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return this.resultCode;
}
}
/**
* @Author : laoyog
* @Date : 2019/2/19 0019
* @Description :异常捕获/抛出类
*/
public class ExceptionCast {
public static void cast(ResultCode resultCode){
throw new CustomizeException(resultCode);
}
}
@ControllerAdvice:控制器增强,用于将controller的全局配置放在同一个位置
@ExceptionHandler:异常处理,用于全局处理控制器里的异常
/**
* @Author : laoyog
* @Date : 2019/2/19 0019
* @Description :异常捕获类
*/
@ControllerAdvice
public class ExceptionCatch {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);
/**
* 捕获自定义异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(CustomizeException.class)
public ResponseResult customizeException(CustomizeException e) {
LOGGER.error("catch exception:{}\r\nexception", e.getMessage(), e);
ResultCode resultCode = e.getResultCode();
return new ResponseResult(resultCode);
}
}
同时,在service层的add方法中修改
/**
* 添加商品
* @param product
* @return
*/
public ResponseResult add(Product product) {
//先根据识别号查重
Long id = productMapper.findByIdCode(product.getIdCode());
//说明该识别号已存在,抛出异常
if(id != null){
ExceptionCast.cast(CommonCode.IdCODE_EXIST);
}
int count= productMapper.add(product);
System.out.println(count);
return new ResponseResult(CommonCode.SUCCESS,product);
}
启动项目,并测试:
模拟异常,参数不变,再次添加,因为idCode商品识别码不能重复,查看是否返回自定义的异常
说明配置成功了
在ExceptionCatch类中添加以下代码
/**
* 异常类型,错误代码的映射
* ImmutableMap 一旦创建不可改变,且线程安全
*/
private static ImmutableMap,ResultCode> exceptions;
/**
* 使用builder创建一个异常类型和错误代码的异常
*/
protected static ImmutableMap.Builder,ResultCode> builder = ImmutableMap.builder();
//加入一些基础的异常类型判断
static {
builder.put(HttpMessageNotReadableException.class,CommonCode.INVALID_PARAM);
//...其他
}
/**
* 用于捕获不可预知的异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public ResponseResult exception(Exception e){
LOGGER.error("catch exception:{}\r\nexception",e.getMessage(),e);
if(exceptions == null){
exceptions = builder.build();
}
final ResultCode resultCode = exceptions.get(e.getClass());
final ResponseResult responseResult;
if(resultCode != null){
responseResult = new ResponseResult(resultCode);
}else{
responseResult = new ResponseResult(CommonCode.INVALID_PARAM);
}
return responseResult;
}
我这里只添加了一种非法参数异常用于测试
如图,我什么参数也不传,返回信息也是我们自己控制的。
查看控制台日志
2019-02-19 15:16:15.889 ERROR 22956 --- [nio-8080-exec-5] com.alibaba.exception.ExceptionCatch : catch exception:错误代码:20001,错误信息:商品识别码已存在
exception
com.alibaba.exception.CustomizeException: 错误代码:20001,错误信息:商品识别码已存在
at com.alibaba.exception.ExceptionCast.cast(ExceptionCast.java:13) ~[classes/:na]
at com.alibaba.service.ProductService.add(ProductService.java:35) ~[classes/:na]
......
2019-02-19 15:19:20.809 ERROR 22956 --- [nio-8080-exec-8] com.alibaba.exception.ExceptionCatch : catch exception:Required request body is missing: public com.alibaba.bean.result.ProductResult com.alibaba.controller.ProductController.add(com.alibaba.bean.domain.Product)
exception
......
至此,完成了对异常的处理。
其中,关键在于对不可预知异常的处理,这里用到了谷歌的ImmutableMap,用它来存储<异常,异常代码>键值对,在加载这个捕获类时,使用static代码块向此map存入自定义的异常信息,发生异常时即可从此map中取出并返回。这里能识别此异常,关键在于两个注解@ControllerAdvice和@ExceptionHandler(Exception.class)。二者详情可以查看官方文档:
@ControllerAdvice:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html,
@ExceptionHandler:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html
可以从文档中看出@ControllerAdvice注解将作用在所有注解了@RequestMapping的controller的方法上,同时注解了@ControllerAdvice的类可以使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,因此二者往往一起使用。
代码github:https://github.com/laoyog/exception-catch.git
csdn下载:https://download.csdn.net/download/bytearr/10965086