栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

公司项目JAVA开发规范总结(三)——异常处理篇

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

公司项目JAVA开发规范总结(三)——异常处理篇

三、异常处理篇 1、自定义业务异常

概述:该类是为了让在业务层,能够返回错误信息,而不涉及控制层的返回封装类,让业务层和控制层能有效隔离开来。

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验证注解的元素值是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[] 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 exception(Exception e){

    String eMessage = e.toString();

    log.error("全局异常捕获:{}", e);

    PacketHttpRes packetHttpRes = new PacketHttpRes<>();
    if(e instanceof ValidationException || e instanceof ConstraintViolationException
       || e instanceof HttpMessageNotReadableException
       || e instanceof NumberFormatException
       || e instanceof MethodArgumentNotValidException) {
        packetHttpRes.setStatusCode(ReturnCode.param_false_code);
        packetHttpRes.setErrCode(ReturnCode.err_nomean_code);
        packetHttpRes.setMsg(ReturnCode.param_false_message+StringUtils.substringBetween(StringUtils.substringAfter(eMessage, ":"), "--", "--"));

    }else{
        ...................
	}
    packetHttpRes.setData(null);

    return packetHttpRes;
}
 

注意:因为全局捕获处理中有"–“的判断,所以所有校验注解的message值前后都得添加”–"

//正确例子
@NotNull(message = "--IsQueryBySelf不能为空--")

//错误例子
@NotNull(message = "IsQueryBySelf不能为空")
3、异常处理规范
  1. 【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。

    说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不

    通过 catch NumberFormatException 来实现。

    正例:

    if (obj != null) {
        ...
    } 
    

    反例:

    try { 
        obj.method(); 
    } 
    catch (NullPointerException e) {
        …
    } 
    
  2. 【强制】异常不要用来做流程控制,条件控制。

    说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

  3. 【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。

    说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题, 这是一种不负责任的表现。

    正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程 序上作出分门别类的判断,并提示给用户。

  4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。

  5. 【强制】事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。

  6. 【强制】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();
          }
    }
    
  7. 【强制】不要在 finally 块中使用 return。

    说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存 在 return 语句,则在此直接返回,无情丢弃掉try 块中的返回点。

    反例:

    private int x = 0; 
    public int checkReturn() { 
        try {
            // x 等于 1,此处不返回 
            return ++x; 
        } finally { 
            // 返回的结果是 2 
            return ++x; 
        } 
    }
    
  8. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。

    说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。

4、日志规范
  1. 【强制】应用中不可直接使用日志系统(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);
    
  2. 【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于 当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下, 过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd

    说明:以 mppserver 应用为例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史日志 名称为 mppserver.log.2016-08-01

  3. 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:

    appName_logType_logName.log。logType:日志类型,如 stats/monitor/access 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。

    说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系 统进行及时监控。

    正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log

  4. 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

    说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅 是替换动作,可以有效提升性能

    正例:

    logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
    
  5. 【强制】对于 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()); 
    }
    
  6. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。

    正例:

    
    
  7. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws 往上抛出。

    正例:

    logger.error(各类参数或者对象toString() + "_" + e.getMessage(), e);
    
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号