java异常分类

  • java把异常当做对象来处理。Throwable是所有错误或异常的超类。Throwable类有两个直接子类:Error类和Exception类。
  1. Error:java运行时系统的内部错误和资源耗尽错误,是程序无法处理的异常,应用程序不会抛出该类对象。
  2. Exception:程序本身可以处理的异常,应尽可能去处理这些异常。Exception分两类,一个是运行时异常RuntimeException,一个是检查异常CheckedException。

    • CheckedException:可以理解为在代码编写阶段就会被编译器检查到并标示的异常。一般是外部错误,这种异常发生在编译阶段,java编译器会强制程序去捕获此类异常。这类异常必须进行处理(捕获或向上抛出),如果不处理,程序将会出现编译错误。一般情况下,API中写了throws的Exception都是CheckedException。
    • RuntimeException:那些在java虚拟机正常运行期间抛出的异常的超类。这种错误是由程序员引起的错误,可以修正代码解决。由于这类异常要么是系统异常,无法处理,如网络问题;要么是程序逻辑异常,如空指针异常,JVM必须停止以改进这种错误,所以可以不进行处理(比如向main外抛出),而由JVM自行处理。Java Runtime会自动catch到程序throw的RuntimeException,然后停止线程,打印异常。
    • 常见CheckedException:

    • 常见RuntimeException:

      throws、throw new XxxException和try/catch的区别

  • throws: 出现在方法头,可以单独使用,表示可能会出现某种异常,而不是一定会出现异常。
  • throw: 出现在方法体,不可以单独使用,要和throws或try/catch之一配套使用,表示已经出现了某种异常

    public void function() throws Exception1, Exception3 {
        try {
            ……
        } catch(Exception1 e) {
            throw e;
        } catch(Exception2 e) {
            System.out.println("出错了");
        }
        if (a != b)
            throw new Exception3("自定义异常");
    }
    
  • 如果产生Exception1,则捕获之后由方法的调用者去处理,,所以这个方法不能是最外层的方法,不能没人调用。
  • 如果产生Exception2,则方法自己打印异常信息,所以不会再向外抛出Exception2异常
  • 如果产生Exception3,则调用者处理。
  • 如果在controller层throw new的话,那么异常堆栈就会被返回给浏览器,出现下面这种情况,给用户造成了非常不好的体验

    用@ControllerAdvice+@ExceptionHandler实现全局异常处理

  • 通常在Controller层需要去捕获service层的异常,防止返回一些不友好的信息到客户端,但如果controller层每个方法都用模块化的try/catch去捕获异常,会很难看也很难维护。
  • 异常处理最好是解耦的,并且都放在一个地方集中管理。Spring能够较好的处理这种问题。核心如下:
    • @ControllerAdvice:定义全局异常处理类
    • @ExceptionHandler:定义异常处理方法
    • @ResponseStatus:可以将异常映射为http状态码,如@ResponseStatus(HttpStatus.UNAUTHORIZED)
  • 单独使用@ExceptionHandler,只能在当前Controller中处理异常,与@ControllerAdvice组合使用,则可以实现全局异常处理,不用每个controller都配置。
  • 优缺点
    • 优点:实现全局异常处理后,所有声明或未声明的异常,只要是在controller层抛出的,都会转到全局处理类处理。减少模板代码,提升扩展性和可维护性。
    • 缺点:只能处理controller层未捕获(往外抛)的异常,对于Interceptor(拦截器)层、Spring框架的异常无能为力。

      全局异常处理类

  • @ControllerAdvice:定义全局异常处理类GlobalExceptionHandler
  • @ExceptionHandler:声明异常处理方法,使用value指定异常类

    /**
     * @Author haien
     * @Description 全局异常,对多个异常进行处理
     * @Date 2018/9/12
     **/
    @ControllerAdvice
    public class GlobalExceptionHandler {
        private static final Logger logger=LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        @ResponseBody //返回json格式的数据
        @ExceptionHandler(value=Exception.class) 
        //处理Controller层抛出的Exception及其子类,出异常会自动找到词类中对应的方法执行
        public Object defaulterrorHandler(HttpServletRequest request, Exception e){
    
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("message",e.getMessage()); //不用专门定义一个字段(如errMsg)也能返回事先填入的信息,未填入返回null
            //jsonObject.put("message","哎呀出错了");
    
            //自定义的登录异常(目前没有应用)
            if(e instanceof AuthenticationException){
                jsonObject.put("result",((AuthenticationException) e).getErrCode()); //获取抛出异常时塞进去的状态码
            }
            //自定义的参数异常(目前没有应用)
            else if(e instanceof BadRequestException){
                jsonObject.put("result",ReturnCode.INVALID_PARAM); //其实状态码在这里塞进去好一点吧
            }
            /*错误的请求方式||输入的参数类型不正确||输入参数不全。
            MissingServletRequestParameterException:
            参数缺失异常,第一个参数:parameterName;第二个:parameterType;
            其getMessage()返回:"Required " + this.parameterType + 
            " parameter '" + this.parameterName + "' is not present";*/
            else if(e instanceof HttpRequestMethodNotSupportedException 
                    || e instanceof TypeMismatchException
                    || e instanceof MissingServletRequestParameterException){
                jsonObject.put("result",ReturnCode.INVALID_PARAM);
            }
            //输入参数不满足约束(是配合@Valid才会抛出地异常;好像都是抛的BindException异常)
            else if(e instanceof ValidationException 
                    || e instanceof MethodArgumentNotValidException  //接口数据验证异常
                    || e instanceof BindException){
                jsonObject.put("result",ReturnCode.INVALID_PARAM);
            }
            else{
                //其他情况均以系统异常处理
                jsonObject.put("result",ReturnCode.SYSTEM_ERROR);
                //e.printStackTrace(); //打印
            }
    
            return jsonObject; //直接返回到前端显示
        }
    
    }
    
  • 就算不明着写打印堆栈,异常也会自动在后台打印,全局异常只是让前端接收到的信息比较友好。
  • 还可以分开方法来处理这些异常

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        /**
         * 处理所有不可知的异常
         * @param e
         * @return
         */
        @ExceptionHandler(Exception.class)
        @ResponseBody
        AppResponse handleException(Exception e){
            LOGGER.error(e.getMessage(), e);
    
            AppResponse response = new AppResponse();
            response.setFail("操作失败!");
            return response;
        }
    
        /**
         * 处理所有业务异常
         * @param e
         * @return
         */
        @ExceptionHandler(BusinessException.class)
        @ResponseBody
        AppResponse handleBusinessException(BusinessException e){
            LOGGER.error(e.getMessage(), e);
    
            AppResponse response = new AppResponse();
            response.setFail(e.getMessage());
            return response;
        }
    
        /**
         * 处理所有接口数据验证异常
         * @param e
         * @return
         */
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ResponseBody
        AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
            LOGGER.error(e.getMessage(), e);
    
            AppResponse response = new AppResponse();
            response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
            return response;
        }
    }
    
  • 自定义异常类,继承Exception
  • 在Service层抛出AuthenticationException:
  • 代码中使用@Valid进行数据校验

    @Controller
    @RequestMapping("/")
    public class LoginController {
        private static final Logger logger=LoggerFactory.getLogger(LoginController.class);
    
        private UserRepository userRepository;
    
        @RequestMapping(value = "login")
        @ResponseBody
        public Map<String,Object> login(@Valid User user){ //自动封装;@Valid可以验证字段,不符合抛出异常并由GlobalExceptionHandler处理
            Map<String,Object> map =new HashMap<String,Object>();
            String userName=user.getUserName();
            String password=user.getPassword();
            //System.out.println(userName);
            //System.out.println(password);
    
            User user1=userRepository.findByUserNameAndPassword(userName,password);
            if(user!=null){
                map.put("result",1);
                map.put("role",user.getRole());
            }else{
                map.put("result",0);
            }
            return map;
        }
    }
    
  • 数据校验失败时,SpringMvc框架会抛出MethodArgumentNotValidException,在GlobalExceptionHandler中加上对该异常的声明和处理,就可以全局处理数据校验的异常了。
  • 测试