SpringBoot全局异常准备说明:如果想直接获取项目,可以跳到最下面,通过链接下载项目代码。开发准备环境要求:JDK:1.8SpringBoot:1.5.17.RELEASE首先还是Maven的依赖:UTF-81.81.81.8org.springframework.bootspring-boot-starter-parent1.5.17.RELEASEorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-testtestcom.alibabafastjson1.2.41配置文件基本不用变了。全局异常的处理只需要在代码中实现即可。写代码的SpringBoot项目已经有了一定的异常处理,但是可能不适合我们开发者,所以我们需要对这些异常进行统一的捕获和处理。SpringBoot中有一个ControllerAdvice注解,使用这个注解表示开启了全局异常的捕获。我们只需要在自定义方法中使用ExceptionHandler注解,定义捕获异常的类型,就可以统一处理这些捕获的异常。让我们根据下面的例子看看这个注解是如何使用的。SpringBoot的基础就不介绍了,看这个:https://github.com/javastacks/spring-boot-best-practice示例代码:@ControllerAdvicepublicclassMyExceptionHandler{@ExceptionHandler(value=Exception.class)publicStringexceptionHandler(Exceptione){System.out.println("未知异常!原因是:"+e);return.getMessage();}}在上面的例子中,我们只是对捕捉到的异常进行了简单的重新处理,并返回了异常信息,虽然这样可以让我们知道异常的原因,但是在很多时候,可能不够人性化满足我们的要求。所以这里我们可以通过自定义异常类和枚举类来实现我们想要的那种数据。自定义基础接口类首先定义一个基础接口类,自定义错误描述枚举类需要实现这个接口。代码如下:publicinterfaceBaseErrorInfoInterface{/**错误码*/StringgetResultCode();/**错误描述*/StringgetResultMsg();}自定义枚举类那么我们这里就是自定义一个枚举类,实现这个接口。代码如下:publicenumCommonEnumimplementsBaseErrorInfoInterface{//数据操作错误定义SUCCESS("200","Success!"),BODY_NOT_MATCH("400","请求的数据格式不匹配!"),SIGNATURE_NOT_MATCH("401","请求的数字签名不匹配!"),NOT_FOUND("404","找不到资源!"),INTERNAL_SERVER_ERROR("500","内部服务器错误!"),SERVER_BUSY("503","服务器正忙,请稍后重试!");/**错误代码*/privateStringresultCode;/**错误描述*/privateStringresultMsg;CommonEnum(StringresultCode,StringresultMsg){this.resultCode=resultCode;this.resultMsg=resultMsg;}@OverridepublicStringgetResultCode(){returnresultCode;}@OverridepublicStringgetResultMsg(){returnresultMsg;}}自定义异常类然后我们定义一个异常类来处理我们的业务异常。代码如下:publicclassBizExceptionextendsRuntimeException{privatestaticfinallongserialVersionUID=1L;/***错误代码*/protectedStringerrorCode;/***错误信息*/protectedStringerrorMsg;publicBizException(){super();}publicBizException(BaseErrorInfoInterfaceerrorInfoInterface){super(errorInfoInterface().getResultCodeInterface().getResultCodeInterface));this.errorCode=errorInfoInterface.getResultCode();this.errorMsg=errorInfoInterface.getResultMsg();}publicBizException(BaseErrorInfoInterfaceerrorInfoInterface,Throwablecause){super(errorInfoInterface.getResultCode(),原因);this.errorCode=errorInfoInterface.getResultCode();this.errorMsg=errorInfoInterface.getResultMsg();}publicBizException(StringerrorMsg){super(errorMsg);this.errorMsg=errorMsg;}publicBizException(StringerrorCode,StringerrorMsg){super(errorCode);this.errorCode=errorCode;this.errorMsg=errorMsg;}publicBizException(StringerrorCode,StringerrorMsg,Throwablecause){super(errorCode,cause);this.errorCode=errorCode;this.errorMsg=errorMsg;}publicStringgetErrorCode(){returnerrorCode;}publicvoidsetErrorCode(StringerrorCode){this.errorCode=errorCode;}publicStringgetErrorMsg(){returnerrorMsg;}publicvoidsetErrorMsg(StringerrorMsg){this.errorMsg=errorMsg;}publicStringgetMessage(){returnerrorMsg;}@OverridepublicThrowablefillInStackTrace(){returnthis;}}自定义数据格式对了,这里定义数据传输格式代码如下:publicclassResultBody{/***响应代码*/privateStringcode;/***响应消息*/privateStringmessage;/***响应结果*/privateObjectresult;publicResultBody(){}publicResultBody(BaseErrorInfoInterfaceerrorInfo){this.code=errorInfo.getResultCode();this.message=errorInfo.getResultMsg();}publicStringgetCode(){returncode;}publicvoidsetCode(Stringcode){this.code=code;}publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicObjectgetResult(){returnresult;}publicvoidsetResult(Objectresult){this.result=result;}/***成功**@return*/publicstaticResultBodysuccess(){returnsuccess(null);}/***成功*@paramdata*@return*/publicstaticResultBodysuccess(Objectdata){ResultBodyrb=newResultBody();rb.setCode(CommonEnum.SUCCESS.getResultCode());rb.setMessage(CommonEnum.SUCCESS.getResultMsg());rb.setResult(数据);returnrb;}/***失败*/publicstaticResultBodyerror(BaseErrorInfoInterfaceerrorInfo){ResultBodyrb=newResultBody();rb.setCode(errorInfo.getResultCode());rb.setMessage(errorInfo.getResultMsg());rb.setResult(null);returnrb;}/***失败*/publicstaticResultBodyerror(Stringcode,Stringmessage){ResultBodyrb=newResultBody();rb.setCode(code);rb.setMessage(message);rb.setResult(null);returnrb;}/***failure*/publicstaticResultBodyerror(Stringmessage){ResultBodyrb=newResultBody();rb.setCode("-1");rb.setMessage(message);rb.setResult(null);returnrb;}@OverridepublicStringtoString(){returnJSONObject.toJSONString(this);}}自定义全局异常处理类最后我们来写一个自定义全局异常处理类代码如下:@ControllerAdvicepublicclassGlobalExceptionHandler{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(GlobalExceptionHandler.class);/***处理自定义业务异常*@paramreq*@parame*@return*/@ExceptionHandler(value=BizException.class)@ResponseBodypublicResultBodybandizException(HttpServletRequestreq,BizExceptione){logger.error("发生业务异常!原因是:{}",e.getErrorMsg());returnResultBody.error(e.getErrorCode(),e.getErrorMsg());}/***异常处理空指针*@paramreq*@parame*@return*/@ExceptionHandler(value=NullPointerException.class)@ResponseBodypublicResultBodyexceptionHandler(HttpServletRequestreq,NullPointerExceptione){logger.error("发生空指针异常!原因是:",e);returnResultBody.error(CommonEnum.BODY_NOT_MATCH);}/***处理其他异常*@paramreq*@parame*@return*/@ExceptionHandler(value=Exception.class)@ResponseBodypublicResultBodyexceptionHandler(HttpServletRequestreq,Exceptione){logger.error("未知异常!原因是:",e);returnResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);}}因为在这里我它们只是用于全局异常处理的功能实现和测试,所以这里我们只需要添加一个实体类和一个控制层类即可。实体类也是通用用户表(▽)代码如下:){}publicintgetId(){returnid;}publicvoidsetId(intid){this.id=id;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}publicStringtoString(){returnJSONObject.toJSONString(this);}}Controller控制层控制层也比较简单,使用Restful风格实现的CRUD功能。不同的是,这里我特意做了一些异常,让这些异常可以被捕获然后处理。在这些异常中,有自定义的异常抛出,空指针异常抛出,当然还有不可预知的异常抛出(这里我使用类型转换异常来代替),那么我们写完代码之后看看这些异常是否可以被捕获和处理成功!代码如下:@RestController@RequestMapping(value="/api")publicclassUserRestController{@PostMapping("/user")publicbooleaninsert(@RequestBodyUseruser){System.out.println("开始添加...");//If如果名字为空,则手动抛出自定义异常!if(user.getName()==null){thrownewBizException("-1","用户名不能为空!");}returntrue;}@PutMapping("/user")publicbooleanupdate(@RequestBodyUseruser){System.out.println("Startupdating...");//这里故意造成空指针异常,不处理Stringstr=null;str.equals("111");returntrue;}@DeleteMapping("/user")publicbooleandelete(@RequestBodyUseruser){System.out.println("Startdeleting...");//这里故意引发异常,不处理Integer.parseInt("abc123");returntrue;}@GetMapping("/user")publicListfindByUser(Useruser){System.out.println("开始查询...");ListuserList=newArrayList<>();Useruser2=newUser();user2.setId(1L);user2.setName("xuwujing");user2.setAge(18);userList.add(user2);returnuserList;}}App入口和普通的SpringBoot项目基本一样。代码如下:@SpringBootApplicationpublicclassApp{publicstaticvoidmain(String[]args){ SpringApplication.run(App.class,args); System.out.println("程序正在运行...");}}功能测试我们成功了启动程序后,使用Postman工具进行界面测试。先查询看程序是否正常运行,使用GET方式进行请求。GEThttp://localhost:8181/api/user返回参数为:{"id":1,"name":"xuwujing","age":18}示例图:可以看到程序正常返回,并且没有理由Affectedbycustomglobalexceptions。那我们来测试下自定义异常是否能被正确捕获和处理。使用POST方式请求POSThttp://localhost:8181/api/userBody参数为:{"id":1,"age":18}返回参数为:{"code":"-1","message":"用户名不能为空!","re??sult":null}示例图:可以看出我们抛出的异常是封装在data中,然后返回异常。然后我们会测试是否能正确捕获并处理空指针异常。在自定义全局异常中,除了定义空指针的异常处理外,我们还定义了一个最高级别的Exception异常。那么这里出现空指针异常后,应该先使用哪个呢?下面我们来测试一下。使用PUT方法发出请求。PUThttp://localhost:8181/api/userBody的参数为:{"id":1,"age":18}返回参数为:{"code":"400","message":"TherequesteddataformatDoesnotmatch!","re??sult":null}例图:我们可以看到这确实是返回空指针的异常关怀,由此可以得出全局异常处理优先处理子类异常。那我们来试试未指定异常的处理,看看能不能捕获异常。使用DELETE方法发出请求。DELETEhttp://localhost:8181/api/userBody参数为:{"id":1}返回参数为:{"code":"500","message":"Internalservererror!","re??sult":null}这里可以看到它使用了我们自定义的全局异常处理类中的Exception异常处理方法。至此,测试结束。对了,自定义全局异常处理除了可以处理以上数据格式外,还可以处理页面跳转。只需要在新增的异常方法的返回处理中填写跳转路径即可,不要使用ResponseBody注释即可。细心的同学可能会发现,在GlobalExceptionHandler类中使用了ControllerAdvice注解,而不是RestControllerAdvice注解。如果使用RestControllerAdvice注解,它会自动将数据转换成JSON格式。这类似于Controller和RestController,所以我们在使用全局异常处理后,可以进行灵活的选择处理。