概述:该类是为了让在业务层,能够返回错误信息,而不涉及控制层的返回封装类,让业务层和控制层能有效隔离开来。
BusinessException类
@Data
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "结果码 200:成功 500:具体异常信息 400:参数异常 401:未授权的请求 403:无权访问")
private Integer statusCode =500;
@ApiModelProperty(value = "错误码")
private int errCode;
@ApiModelProperty(value = "错误提示信息")
private String msg;
@ApiModelProperty(value = "数据体")
private T data;
public BusinessException() {
super();
}
public BusinessException(String message) {
super();
this.msg = message;
}
public BusinessException(Integer status, int code, String message) {
super();
this.statusCode = status;
this.errCode = code;
this.msg = message;
}
}
在业务层Service中,下列错误反馈的例子
//例子一
try {
int a =id/0;
} catch (Exception e) {
throw new BusinessException(ReturnCode.server_exception_code,ReturnCode.server_exception_message,
ReturnCode.err_other_code,"计算出错");
}
//例子二
if (id==null){
throw new BusinessException(ReturnCode.param_false_code,ReturnCode.param_false_message
ReturnCode.err_notexit_code,"参数不存在");
}
异常抛出之后,捕获异常的方式有两种:
方式一:由控制层进行局部捕获,返回结果
try {
calculateService.count(id);
return PacketHttpRes_V32.makeOKResponse();
} catch (BusinessException be){
log.error("出错:", be.getResultCode_Msg());
return PacketHttpRes_V32.makeResponse(be.getReturnCode(), be.getReturnCode_Msg(),
be.getResultCode(), be.getResultCode_Msg(),ex.getData());
}
方式二:进行全局捕获,返回结果
@ExceptionHandler(BusinessException.class)
@ResponseBody
public PacketHttpRes_V32 processBusinessException(BusinessException ex) {
log.warn("捕获 BusinessException: ReturnCode:{} , ReturnCodeMsg:{} ResultCode:{} , ResultMsg:{}",
ex.getReturnCode(),ex.getReturnCode_Msg(),ex.getResultCode(),ex.getResultCode_Msg());
return PacketHttpRes_V32.makeResponse(ex.getReturnCode(), ex.getReturnCode_Msg(),
ex.getResultCode(), ex.getResultCode_Msg(),ex.getData());
}
2、校验异常
概述:该异常由于接口参数传入错误,使得异常抛出
常规校验:| 注解名称 | 作用描述 |
|---|---|
| @Null | 限制只能为null |
| @NotNull | 限制不能为null |
| @AssertFalse | 限制必须为false |
| @AssertTrue | 限制必须为true |
| @DecimalMax(value) | 限制必须为一个不大于value的数字(小数) |
| @DecimalMin(value) | 限制必须为一个不小于value的数字(小数) |
| @Max(value) | 限制必须是一个不大于value的数字(整数) |
| @Min(value) | 限制必须是一个不小于value的数字(整数) |
| @Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不超过fraction |
| @Future | 限制必须是一个将来的日期 |
| @Past | 限制必须是一个过去的日期 |
| @Pattern(value) | 限制必须符合指定的正则表达式 |
| @Size(max,min) | 限制字符长度必须在min到max之间 |
| @NotEmpty | 验证注解的元素值不为null且不为空(字符串长度不为0,集合大小不为0) |
| @NotBlank | 验证注解的元素值不为空,仅应用于字符串且比较时会去除字符串空格 |
| 验证注解的元素值是Email |
IntegerValidator注解:
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IntegerValidatorOfParams.class)
public @interface IntegerValidator {
int[] params() default {};
String message() default "参数不合法";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
IntegerValidatorOfParams实现类
public class IntegerValidatorOfParams implements ConstraintValidator{ private int[] params; @Override public void initialize(IntegerValidator integerValidator) { this.params=integerValidator.params(); } @Override public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { List list = Arrays.stream(params).boxed().collect(Collectors.toList()); return list.contains(o); } }
使用案例:
@RestController
@RequestMapping("/TeachingPlan/ShareTpMgr")
@Validated
@Slf4j
@Api(tags = "教师端端共享教案库管理")
public class ShareTpMgrOfTeaController extends BaseController {
@ApiOperation(value = "获取共享教材信息列表(中小学)", notes = "获取可访问的教材信息列表以及历史访问信息")
@RequestMapping(value = "/GetCanVisBookList", method = RequestMethod.GET)
public PacketHttpRes_V32> GetCanVisBookList(
@ApiParam("用户ID") @NotBlank(message = "--UserID不能为空--") String UserID,
@ApiParam("是否查询自己共享的,0或不传查询全部,1为查询自己共享的,2为查询非自己共享的")
@RequestParam(value = "IsQueryBySelf", required = false, defaultValue = "0")
@NotNull(message = "--IsQueryBySelf不能为空--")
@IntegerValidator(params = {0, 1, 2}, message = "--IsQueryBySelf参数错误--")
Integer IsQueryBySelf,
HttpServletResponse response
)
}
全局捕获,返回结果
@ExceptionHandler(value = Exception.class) public PacketHttpRes
注意:因为全局捕获处理中有"–“的判断,所以所有校验注解的message值前后都得添加”–"
//正确例子 @NotNull(message = "--IsQueryBySelf不能为空--") //错误例子 @NotNull(message = "IsQueryBySelf不能为空")3、异常处理规范
-
【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不
通过 catch NumberFormatException 来实现。
正例:
if (obj != null) { ... }反例:
try { obj.method(); } catch (NullPointerException e) { … } -
【强制】异常不要用来做流程控制,条件控制。
说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
-
【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题, 这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程 序上作出分门别类的判断,并提示给用户。
-
【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。
-
【强制】事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。
-
【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
try-with-resources语句能够帮你自动调用资源的close()函数关闭资源不用到finally块。static String readFirstLineFromFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } } -
【强制】不要在 finally 块中使用 return。
说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存 在 return 语句,则在此直接返回,无情丢弃掉try 块中的返回点。
反例:
private int x = 0; public int checkReturn() { try { // x 等于 1,此处不返回 return ++x; } finally { // 返回的结果是 2 return ++x; } } -
【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
-
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 (SLF4J、JCL–Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和 各个类的日志处理方式统一。
说明:日志框架(SLF4J、JCL–Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)
// SLF4J: import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Test.class); // JCL: import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; private static final Log log = LogFactory.getLog(Test.class);
-
【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于 当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下, 过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
说明:以 mppserver 应用为例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史日志 名称为 mppserver.log.2016-08-01
-
【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
appName_logType_logName.log。logType:日志类型,如 stats/monitor/access 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系 统进行及时监控。
正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log
-
【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅 是替换动作,可以有效提升性能
正例:
logger.debug("Processing trade with id: {} and symbol: {}", id, symbol); -
【强制】对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。 说明:虽然在 debug(参数)的方法体内第一行代码 isDisabled(Level.DEBUG_INT)为真时(Slf4j 的常见实现 Log4j 和 Logback),就直接 return,但是参数可能会进行字符串拼接运算。此外,如果 debug(getName()) 这种参数内有 getName()方法调用,无谓浪费方法调用的开销。
正例:
// 如果判断为真,那么可以输出 trace 和 debug 级别的日志 if (logger.isDebugEnabled()) { logger.debug("Current ID is: {} and name is: {}", id, getName()); } -
【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例:
-
【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws 往上抛出。
正例:
logger.error(各类参数或者对象toString() + "_" + e.getMessage(), e);



