后端异常统一处理解决方案--无需controller单独对每个方法捕获

代码在最后,欢迎批评与改正!

在项目中,特别是前后端分离的项目,后台往往需要向前端返回统一的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进行添加操作的异常模拟。

1. 新建mysql数据库和表

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;

2.导入maven依赖


        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
                    1.8
                    UTF-8
                
            
        
    

3. 新建javabean,字段对应project表

/**
 * @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;

    //其他......
}

4. 定义返回数据格式

结构如下:

后端异常统一处理解决方案--无需controller单独对每个方法捕获_第1张图片

ResultCode接口

/**
 * @Author : laoyog
 * @Date : 2019/2/15 0015
 * @Description :定义返回结果的接口,具体使用它的实现类
 */
public interface ResultCode {
    boolean success();
    int code();
    String message();
}

CommonCode

/**
 * @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; }
}

ResponseResult

@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;
    }
}
 

5. 业务代码 controller-service-dao

@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);
}

下面进行异常的处理操作

一. 可预知异常(自定义异常)

1. 自定义异常处理类

/**
 * @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;
    }
}

2. 异常抛出类

/**
 * @Author : laoyog
 * @Date : 2019/2/19 0019
 * @Description :异常捕获/抛出类
 */
public class ExceptionCast {

    public static void cast(ResultCode resultCode){
        throw new CustomizeException(resultCode);
    }

}

3. 异常捕获类

         @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);
    }

启动项目,并测试:

后端异常统一处理解决方案--无需controller单独对每个方法捕获_第2张图片

模拟异常,参数不变,再次添加,因为idCode商品识别码不能重复,查看是否返回自定义的异常

后端异常统一处理解决方案--无需controller单独对每个方法捕获_第3张图片

说明配置成功了

二、不可预知的异常

在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;
    }

我这里只添加了一种非法参数异常用于测试

后端异常统一处理解决方案--无需controller单独对每个方法捕获_第4张图片

如图,我什么参数也不传,返回信息也是我们自己控制的。

查看控制台日志

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

 

你可能感兴趣的:(springboot,SSM,异常捕获,异常统一处理,SpringBoot)