java异常分类
- java把异常当做对象来处理。Throwable是所有错误或异常的超类。Throwable类有两个直接子类:Error类和Exception类。
- Error:java运行时系统的内部错误和资源耗尽错误,是程序无法处理的异常,应用程序不会抛出该类对象。
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都配置。
- 优缺点
- @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中加上对该异常的声明和处理,就可以全局处理数据校验的异常了。
- 测试