一、Java异常简介
异常是程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行。异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。
Java提供了更加优秀的解决办法:异常处理机制。异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,它才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,当然我们也可以自定义异常。
JDK内建异常体系
二、异常分类和异常罗列
异常分类
根据现有异常情况,将项目中的所有异常划分为三类:
JAVA内建异常:指程序运行过程发生非预料的错误,比如数据库连接异常、空指针等人为未检测出的异常。
业务自定义异常:指模块中阻碍正常逻辑进行的对外抛出的错误信息。囊括现有项目设计出的所有的异常状态码
模块 |
异常码 |
异常描述 |
wave-odin-workflow |
MAX_NUM_ERROR |
最大负载数错误 |
MISSION_EXIST |
任务已存在 |
|
MAX_NUM_ERROR |
最大负载数错误 |
|
PROCESSOR_NOT_FOUND |
处理机不存在 |
|
PROCESSOR_NOT_EXIST |
处理机信息不存在 |
|
DISCERN_RECORD_NOT_FOUNT |
识别记录无法找到 |
|
EXIST_ACTIVE_WORKFLOW |
存在激活的工作流 |
|
MISSION_PERSONNEL_ASSOCIATE_EXIST |
任务人员关系已经存在 |
|
MISSION_CAMERA_ASSOCIATE_EXIST |
任务摄像机关系已经存在 |
|
MONITOR_DOOR_GROUP_PERSONNEL_ASSOCIATE_EXIST |
门禁组人员关系已经存在 |
|
CAMERA_NOT_FOUNT |
摄像机信息无法找到 |
|
MONITOR_DOOR_GROUP_MISSION_ASSOCIATE_EXIST |
门禁组任务关系已经存在 |
|
MONITOR_DOOR_GROUP_NOT_FOUNT |
门禁组信息无法找到 |
|
MISSION_NOT_FOUNT |
无法找到任务信息 |
|
JOB_RECORD_EXIST |
工作记录已存在 |
|
NOT_FOUND_PROCESSOR |
处理机超负载 |
|
PERSONNEL_GROUP_NOT_FOUNT |
人员组信息无法找到 |
|
PERSONNELGROUP_PERSONNEL_ASSOCIATE_EXIST |
人员组和人员关联关系已经存在 |
|
PERSONNEL_GROUP_MISSION_ASSOCIATE_EXIST |
人员组任务关联已存在 |
|
MONITOR_DOOR_GROUP_ALREADY_ACTIVE |
门禁组已经激活 |
|
MONITOR_DOOR_GROUP_ALREADY_BACKOUT |
门禁组已经挂起 |
|
WORK_FLOW_JOB_RECORD_UNFOUND |
没有找到工作记录 |
|
NOT_FOUND_CAMERA_DEVEICE |
该任务未分配摄像机 |
|
PERSONNEL_EXIST |
人员信息已存在 |
|
PERSONNEL_GROUP_ALREADY_BACKOUT |
人员组已经挂起 |
|
PERSONNEL_GROUP_ALREADY_ACTIVE |
人员组已经激活 |
模块 |
异常码 |
异常描述 |
wave-odin-sentry |
MISSION_PERSONNEL_ASSOCIATE_EXIST |
任务人员关系已经存在 |
MISSION_CAMERA_ASSOCIATE_EXIST |
任务摄像机关系已经存在 |
|
MONITOR_DOOR_GROUP_MISSION_ASSOCIATE_EXIST |
门禁组任务关系已经存在 |
|
MONITOR_DOOR_GROUP_NOT_FOUNT |
门禁组信息无法找到 |
|
MONITOR_DEVICE_EXIST |
控制器信息已存在 |
|
MONITOR_DEVICE_NOT_EXIST |
门禁控制器不存在 |
|
MONITOR_DOOR_DEVICE_NOT_EXIST |
门禁设备不存在 |
|
|
|
模块 |
异常码 |
异常描述 |
wave-odin-vidicon |
CAMERA_DEVICE_EXIST |
摄像机设备已经存在 |
CAMERA_DEVICE_NOT_EXIST |
设备信息不存在 |
|
CAMERA_ATTENDANCE_NO_RELATION |
设备考勤没有关联信息 |
|
CAMERA_DEVICE_EXIST_RELATION |
设备存在关联关系 |
|
CAMERA_NOT_FOUNT |
摄像机信息无法找到 |
模块 |
异常码 |
异常描述 |
wave-centimani-storage |
STORAGE_FAILED |
存储失败 |
IMAGE_NOT_FOUNT |
图片未找到 |
|
STORAGE_FASTDFS_FAILED |
FASTDFS存储失败 |
|
OSS_DATA_READ_FAILED |
OSS读取异常 |
模块 |
异常码 |
异常描述 |
wave-centimani-authority |
CONNECT_TIME_OUT |
连接超时 |
NOT_FOUND |
api_key和api_secret不匹配 |
|
STORAGE_OSS_FAILED |
OSS存储异常 |
|
AUTHENTICATION_ERROR |
api_key和api_secret不匹配 |
|
AUTHORIZATION_ERROR |
api_key 没有调用本 API 的权限 |
|
API_NOT_FOUND |
所调用的 API 不存在 |
模块 |
异常码 |
异常描述 |
wave-argus-faceDetect |
|
|
|
|
模块 |
异常码 |
异常描述 |
wave-argus-faceSet |
FACESET_UPDATED |
FaceSet更新成功 |
FACESET_CREATED |
FACESET创建成功 |
|
FACESET_ADDED |
FACESET添加成功 |
|
FACESET_UPDATED |
FaceSet更新成功 |
|
FACESET_CREATED |
FACESET添加重复! |
|
FACESET_ADDED |
FACESET添加重复! |
|
QUOTA_EXCEEDED |
FaceSet存储上限 |
|
FACESET_NOT_EMPTY |
FaceSet不为空,不能删除 |
|
INVALID_OUTER_ID |
outer_id无效 |
|
INVALID_FACESET_TOKEN |
faceset_token无效 |
|
NEW_OUTER_ID_EXIST |
提供的new_outer_id与已有outer_id重复 |
|
INVALID_FACE_TOKENS_SIZE |
face_token数量达到上限 |
|
FACE_TOKEN_QUOTA_EXCEEDED |
face_token数量达到上线 |
|
FACESET_QUOTA_EXCEEDED |
FaceSet数量达到上限,不能继续创建FaceSet |
|
FACESET_EXIST |
FACESET_EXIST已经存在 |
|
FACETOKEN_EXIST |
face_token已经存在不能重复添加 |
|
EMPTY_FACESET |
Faceset中没有face_token |
|
EMPTY_FACESET_FEATURE |
请确保传入的face_token和Faceset中face_token具有定位信息 |
|
FEATURE_RESULT_UNFOUND |
请确保传入的face_token具有定位信息 |
|
START_OUT_OF_BOUND |
请确保传入的start具有信息 |
|
INVALID_FACE_TOKEN |
face_token无效 |
模块 |
异常码 |
异常描述 |
wave-argus-bioAssay |
|
|
|
|
模块 |
异常码 |
异常描述 |
wave-argus-faceFeature |
|
|
模块 |
异常码 |
异常描述 |
wave-argus-faceRecognize |
|
|
网关参数校验异常:对于网关传入参数的校验所引起的异常。
异常码 |
异常描述 |
COEXISTENCE_ARGUMENTS |
同时传入了要求是二选一或多选一的参数。 |
BAD_ARGUMENTS |
某个参数解析出错 |
MISSING_ARGUMENTS |
缺少某个必选参数 |
INVALIDPARAMETER |
无效参数 |
IMAGE_ERROR_UNSUPPORTED_FORMAT |
对应的图像无法正确解析 |
IMAGE_FILE_TOO_LARGE |
上传的图片文件太大 |
IMAGE_DOWNLOAD_TIMEOUT |
下载图片超时 |
CONCURRENCY_LIMIT_EXCEEDED |
并发数超过限制 |
INVALID_IMAGE_URL |
无法从指定的image_url下载图片 |
INVALID_IMAGE_SIZE |
客户上传的图像像素尺寸太大或太小 |
三、自定义异常类设计
基于该异常方案的流程讲解图:
自定义业务异常类是继承于RuntimeException,指程序运行时不符合业务逻辑时,抛出的自定义错误信息。目前只有BusinessException。
校验异常类同样是继承RuntimeException,目前有MethodArgumentNotValidExcption。
实施步骤:
网关处理步骤:
1. 添加网关校验配置:
1.1.添加pom依赖: 网关校验的时候因为是Spring源生框架需要依赖hibernate-validator的api进行校验,所以需要在pom中增加依赖(如是springboot则不需要增加)
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.5.Final</version> </dependency> |
1.2添加注解:因为引用了validator依赖,所以需要通过@Validated、@Valid注解开启校验。
package com.wave.odin.net.controller;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController;
import com.wave.odin.net.service.PersonnelService;
/** * @author Mango * @date 2018年6月14日 * @version 1.0 Description:人员数据管理 */ @Validated @RestController @RequestMapping("/personnel") public class testController {
/** * 人员数据管理 */ @Autowired PersonnelService personnelService;
/** * 1.异常效验 * * @param query * @param request * @param response * @return * @throws Exception */ @RequestMapping(value = { "/test" }, method = RequestMethod.POST, produces = { "text/html;charset=UTF-8" }) @ResponseBody public String getInfoFoa(@RequestBody @Valid TestVo vo, HttpServletRequest request, HttpServletResponse response) throws Exception {
return personnelService.test(); } }
|
注:①@Validated、@Valid 注解开启hibernate-validator校验功能;
2.添加异常返回类/全局异常捕获类:
2.1.异常返回类:BussinessException
<dependency> <groupId>com.wave.centimani</groupId> <artifactId>wave-centimani-tool</artifactId> <version>1.0.1</version> </dependency> |
2.2.全局捕获类:
通过@ControllerAdvice,我们可以将控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,这对所有注解了@RequestMapping的控制器内的方法有效。
@ExceptionHandler:配置全局异常处理。
全局异常捕获类。 package com.wave.odin.net.controller;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus;
/** * 全局异常拦截 * @author B250M-J * 日期:2018年7月10日 */ @ControllerAdvice public class ExceptionHandle { private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
/** * 处理exception类型异常以及其子类,起到兜底的作用,主要是非自定义异常 * @param e * @return */ @ExceptionHandler(value = Exception.class) @ResponseBody public String handler(Exception e) { if (e instanceof Exception) {
String excption = e.toString(); String[] excptions = excption.split(":");
return ResultUtil.errorInfo(excptions[1].replace("/", ""), excptions[0], "0000000000000000000000000000000");
} else { logger.info("[系统异常] {}", e); return ResultUtil.errorInfo("-1", "服务出差了,程序员小哥哥正在召唤!", "0000000000000000000000000000000"); } }
/** * 处理业务异常,属于自定义异常范围 * @param e * @return */ @ExceptionHandler(value = BussinessException.class) @ResponseBody public String handlers(BussinessException e) { if (e instanceof BussinessException) {
BussinessException bussinessException = (BussinessException) e;
return ResultUtil.errorInfo(bussinessException.getDescption(), bussinessException.getName(), bussinessException.getRequestId());
} else { logger.info("[系统异常] {}", e);
return ResultUtil.errorInfo("-1", "未知错误", "0000000000000000000000000000000"); } }
/** * @Validated + @RequestBody 注解但是没有在绑定的数据对象后面跟上 Errors 类型的参数声明的话,Spring MVC * 框架会抛出 MethodArgumentNotValidException 异常。 捕捉方法参数的校验异常 * @param e * @return */ @ExceptionHandler(ConstraintViolationException.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleValidationException(ConstraintViolationException e) {
return ResultUtil.errorInfo(e.getConstraintViolations().stream() .map(message -> message.getInvalidValue() + ":" + message.getMessage() + "<br/>") .reduce("", (s0, s1) -> s0 + s1), "BAD_Arguments", "0000000000000000000000000000000");
}
/** * 捕捉方法参数的校验异常 * @param e * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleValidationException(MethodArgumentNotValidException e) {
e.getBindingResult().getAllErrors().stream().map(message -> message.getDefaultMessage() + "\n").reduce("", (s0, s1) -> s0 + s1); // return ResultUtil.errorInfo(e.getBindingResult().getAllErrors().stream() .map(message -> message.getDefaultMessage() + "\n").reduce("", (s0, s1) -> s0 + s1), "BAD_Arguments", "0000000000000000000000000000000");
}
}
|
各个模块处理步骤:
在需要业务逻辑判断出错的地方抛出异常类:BussinessException(errorCode,requestId)。
例如:判断,如果age<14则抛出异常
/** * 异常测试 * * @return */ public String test() throws Exception { int age = 1; if (age < 14) {
// int a =1/0; 抛java内建异常
// 抛自定义异常 throw new BussinessException(EnumErrorsDesBase.API_NOT_FOUND, "1524654321575241241212");
} else if (age > 20) { System.out.println("333333333"); } return "555555555"; }
|
四、[1] 基于该异常方案的流程讲解
图:常规请求流程
网关配置:
- pom中添加依赖jar;[2] 网关校验的时候因为是Spring源生框架需要依赖hibernate-validator的api进行校验,所以需要在pom中增加依赖(如是springboot则不需要增加);
- 创建异常配置类;[3] 对于异常的捕获需要创建异常返回类、全局异常捕获类。
- 网关配置注解;[4] 因为引用了validator依赖,所以需要通过@Validated、@Valid注解开启校验。
模块配置:
效果:
- 实验效果;
步骤如下:
步骤1:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.5.Final</version> </dependency> |
步骤2:
异常类: package com.wave.odin.net.controller; /** * 自定义:业务返回异常类 * @author loaf *2018年7月10日14 */ public class BussinessException extends RuntimeException {
private String descption; private String name; private String requestId;
public BussinessException(EnumErrorsDesBase resultEnum, String requestId) {
this.descption = resultEnum.getDescription(); this.name = resultEnum.getName(); this.requestId = requestId;
}
public String getDescption() { return descption; }
public void setDescption(String descption) { this.descption = descption; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getRequestId() { return requestId; }
public void setRequestId(String requestId) { this.requestId = requestId; }
}
|
异常返回类 package com.wave.odin.net.controller;
import com.fasterxml.jackson.core.JsonProcessingException; import com.wave.centimani.tool.tojson.JacksonJsonUntil;
/** * http请求返回处理类 * 2018年7月10日14 * @author loaf * */ public class ResultUtil {
/** * 异常时才会调用此类的方法 * @param descption * @param name * @param requestId * @return */ public static String errorInfo(String descption, String name, String requestId) { FaildResultVo result = new FaildResultVo(); result.setErrorCode(name); result.setErrorCodeDesc(descption); result.setRequestId(requestId); result.setRequestStatus("Fail"); result.setTimeUsed(100);
String results = ""; try { results = JacksonJsonUntil.objectToJson(result); } catch (JsonProcessingException e) { e.printStackTrace(); }
return results; } }
|
全局异常捕获类。 package com.wave.odin.net.controller;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus;
/** * 全局异常拦截 * @author B250M-J * 日期:2018年7月10日 */ @ControllerAdvice public class ExceptionHandle { private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
/** * 处理exception类型异常以及其子类,起到兜底的作用,主要是非自定义异常 * @param e * @return */ @ExceptionHandler(value = Exception.class) @ResponseBody public String handler(Exception e) { if (e instanceof Exception) {
String excption = e.toString(); String[] excptions = excption.split(":");
return ResultUtil.errorInfo(excptions[1].replace("/", ""), excptions[0], "0000000000000000000000000000000");
} else { logger.info("[系统异常] {}", e); return ResultUtil.errorInfo("-1", "服务出差了,程序员小哥哥正在召唤!", "0000000000000000000000000000000"); } }
/** * 处理业务异常,属于自定义异常范围 * @param e * @return */ @ExceptionHandler(value = BussinessException.class) @ResponseBody public String handlers(BussinessException e) { if (e instanceof BussinessException) {
BussinessException bussinessException = (BussinessException) e;
return ResultUtil.errorInfo(bussinessException.getDescption(), bussinessException.getName(), bussinessException.getRequestId());
} else { logger.info("[系统异常] {}", e);
return ResultUtil.errorInfo("-1", "未知错误", "0000000000000000000000000000000"); } }
/** * @Validated + @RequestBody 注解但是没有在绑定的数据对象后面跟上 Errors 类型的参数声明的话,Spring MVC * 框架会抛出 MethodArgumentNotValidException 异常。 捕捉方法参数的校验异常 * @param e * @return */ @ExceptionHandler(ConstraintViolationException.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleValidationException(ConstraintViolationException e) {
return ResultUtil.errorInfo(e.getConstraintViolations().stream() .map(message -> message.getInvalidValue() + ":" + message.getMessage() + "<br/>") .reduce("", (s0, s1) -> s0 + s1), "BAD_Arguments", "0000000000000000000000000000000");
}
/** * 捕捉方法参数的校验异常 * @param e * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleValidationException(MethodArgumentNotValidException e) {
e.getBindingResult().getAllErrors().stream().map(message -> message.getDefaultMessage() + "\n").reduce("", (s0, s1) -> s0 + s1); // return ResultUtil.errorInfo(e.getBindingResult().getAllErrors().stream() .map(message -> message.getDefaultMessage() + "\n").reduce("", (s0, s1) -> s0 + s1), "BAD_Arguments", "0000000000000000000000000000000");
}
}
|
步骤3:
package com.wave.odin.net.controller;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController;
import com.wave.odin.net.service.PersonnelService;
/** * @author Mango * @date 2018年6月14日 * @version 1.0 Description:人员数据管理 */ @Validated @RestController @RequestMapping("/personnel") public class testController {
/** * 人员数据管理 */ @Autowired PersonnelService personnelService;
/** * 1.异常效验 * * @param query * @param request * @param response * @return * @throws Exception */ @RequestMapping(value = { "/test" }, method = RequestMethod.POST, produces = { "text/html;charset=UTF-8" }) @ResponseBody public String getInfoFoa(@RequestBody @Valid TestVo vo, HttpServletRequest request, HttpServletResponse response) throws Exception {
return personnelService.test(); } }
|
注:①@Validated、@Valid 注解开启hibernate-validator校验功能;
步骤4:
/** * 异常测试 * * @return */ public String test() throws Exception { int age = 1; if (age < 14) {
// int a =1/0; 抛java内建异常
// 抛自定义异常 throw new BussinessException(EnumErrorsDesBase.API_NOT_FOUND, "1524654321575241241212");
} else if (age > 20) { System.out.println("333333333"); } return "555555555"; }
|
|
步骤5:
1.自定义业务异常类捕获:
2.网关校验异常捕获:
3.java内建异常捕获:
换个标题,可以类似于:该异常方案基于Spring源生框架的全流程讲解
什么原因
为什么需要创建异常配置类
目的是什么
目的是什么