zoukankan      html  css  js  c++  java
  • Spring异常统一处理

    在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 
    那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。

    Spring对异常统一处理的方式有两种:

    • 使用 HandlerExceptionResolver 接口,并且 Spring 已经提供默认的实现类 SimpleMappingExceptionResolver。
    • 使用@ExceptionHandler注解实现异常处理; 

    一、基于 HandlerExceptionResolver 接口的方式

    使用这种方式只需要实现 resolveException 方法,该方法返回一个 ModelAndView 对象,在方法内部对异常的类型进行判断,然后返回合适的 ModelAndView 对象,如果该方法返回了 null,则 Spring 会继续寻找其他的实现了 HandlerExceptionResolver 接口的 Bean。换句话说,Spring 会搜索所有注册在其环境中的实现了 HandlerExceptionResolver 接口的 Bean,逐个执行,直到返回了一个 ModelAndView 对象。

    @Component
    public class CustomExceptionHandler implements HandlerExceptionResolver {
      
        @Override  
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object object, Exception exception) {
            if(exception instanceof IOException){
                return new ModelAndView("ioexp");  
            }else if(exception instanceof SQLException){
                return new ModelAndView("sqlexp");  
            }  
            return null;  
        }  
    }

    注意:这个类必须声明到 Spring 配置文件中,或者使用 @Component 标签,让 Spring 管理它。同时 Spring 也提供默认的实现类 SimpleMappingExceptionResolver,需要使用时只需要使用注入到 Spring 配置文件进行声明即可。自定义实现类与默认的实现类,可同时使用。

    二、使用@ExceptionHandler

    提示:当一个Controller中有方法加了@ExceptionHandler之后,这个Controller其他方法中没有捕获的异常就会以参数的形式传入加了@ExceptionHandler注解的那个方法中。

    统一返回数据结构
    定义返回的数据结构

    先定义接口返回数据结构,code为0表示操作成功,非0表示异常。其中data只有在处理成功才会返回,其他情况不会返回,或者那些不需要返回数据的接口(更新、删除…)

    {
         "code": 0,
         "message": "SUCCESS",
         "data": {}
    }
    数据接口字段模型定义
    /**
     * 结果统一返回类, 常用于json格式, 处理json对象的类,数据必须要有相关的get和set方法。
     */
    public final class ResponseResultDto<T> implements Serializable {
    
        private static final long serialVersionUID = 8908073950154134675L;
    
        /**
         * 状态码
         **/
        private Integer code;
    
        /**
         * 状态描述信息
         **/
        private String message;
    
        /**
         * 返回数据
         **/
        private T data;
    
        private ResponseResultDto(Builder<T> builder) {
            this.code = builder.code;
            this.message = builder.message;
            this.data = builder.data;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public static class Builder<T> {
            private Integer code;
            private String message;
            private T data;
    
            public Builder setCode(Integer code) {
                this.code = code;
                return this;
            }
    
            public Builder setMessage(String message) {
                this.message = message;
                return this;
            }
    
            public Builder setData(T data) {
                this.data = data;
                return this;
            }
    
            public ResponseResultDto builder() {
                return new ResponseResultDto(this);
            }
        }
    
    }

    这样创建对象:

    new ResponseResultDto.Builder<String>().setCode(0).setData("xxxx").setMessage("aaaa").builder();
    状态码枚举

    项目用到的状态码、描述信息要有个文件统一去做枚举定义,一方面可以实现复用,另一方面如果状态码、描述有改动只需要在定义枚举的地方改动即可。

    public enum StatusEnum {
        SUCCESS(0, "成功"),
        FAILURE(-99, "失败");
    
        private final Integer code;
        private final String message;
    
        StatusEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    }
    自定义异常类
    /**
     * 系统业务异常
     */
    public class BusinessException extends RuntimeException {
    
        /**
         * serialVersionUID
         */
        private static final long serialVersionUID = 2332608236621015980L;
    
        private Integer code;
    
        public BusinessException() {
            super();
        }
    
        public BusinessException(String message) {
            super(message);
        }
    
        public BusinessException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public BusinessException(Throwable cause) {
            super(cause);
        }
    
        public BusinessException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public BusinessException(Integer code, String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
    }    
    异常统一处理方式
    设计基类

    所有需要异常处理的Controller都继承这个类,从而获取到异常处理的方法。虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。

    public class BaseController {
        /**
         * 处理Controller抛出的异常
         * @param e 异常实例
         * @return Controller层的返回值
         */
        @ExceptionHandler
        @ResponseBody
        public Object expHandler(Exception e){
        
    if(e instanceof BusinessException){ BusinessException ex= (BusinessException) e; return new ResponseResultDto.Builder<String>().setCode(ex.getCode()).setMessage(ex.getMessage()).builder(); }else{ e.printStackTrace(); return new ResponseResultDto.Builder<String>().setCode(StatusEnum.FAILURE.getCode()).setMessage(e.getMessage()).builder();
    } } }
    声明一个default接口(java8开始支持接口方法的默认实现)
    public interface DataExceptionSolver {
    
        @ExceptionHandler
        @ResponseBody
        default Object exceptionHandler(Exception e) {
         if (e instanceof BusinessException) { BusinessException ex = (BusinessException) e; return new ResponseResultDto.Builder<String>().setCode(ex.getCode()).setMessage(ex.getMessage()).builder(); } else { e.printStackTrace(); return new ResponseResultDto.Builder<String>().setCode(StatusEnum.FAILURE.getCode()).setMessage(e.getMessage()).builder(); } } }

    这种方式虽然没有占用继承,但是也不是很优雅,因为几乎所有的Controller都需要进行异常处理,于是我每个Controller都需要去写implement DataExceptionSolver,这显然不是我真正想要的。况且这种方式依赖java8才有的语法,这是一个很大的局限。

    使用加强Controller做全局异常处理。

    所谓加强Controller就是@ControllerAdvice注解,有这个注解的类中的方法的某些注解会应用到所有的Controller里,其中就包括@ExceptionHandler注解。

    于是可以写一个全局的异常处理类:

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        //处理自定义的异常
        @ExceptionHandler(BusinessException.class)
        @ResponseBody
        public Object customHandler(BusinessException e) {
            e.printStackTrace();
         return new ResponseResultDto.Builder<String>().setCode(e.getCode()).setMessage(e.getMessage()).builder(); } //其他未处理的异常 @ExceptionHandler(Exception.class) @ResponseBody public Object exceptionHandler(Exception e) { e.printStackTrace();
         return new ResponseResultDto.Builder<String>().setCode(StatusEnum.FAILURE.getCode()).setMessage(e.getMessage()).builder(); } }

    这个类中只处理了两个异常,但是已经满足了大部分需要,如果还有需要特殊处理的地方,可以再加上处理的方法就行了,推荐使用这种处理方式。

  • 相关阅读:
    redis 高级功能,过期事件监听
    三五个人的技术团队用的上的技术架构
    听说过api,但是你听说过spi吗
    PostgreSQL建表及相关
    shell命令 $(cd `dirname $0`; pwd);[ "$#" -ne "8" ];exit;declare;`date +%s`
    学习Shell命令
    Shell echo命令
    Linux常用命令
    Linux 目录结构
    nohup ./startWebLogic.sh >out.log 2>&1 & 解析
  • 原文地址:https://www.cnblogs.com/myitnews/p/13303598.html
Copyright © 2011-2022 走看看