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

项目 - 项目中的异常处理方式

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

项目 - 项目中的异常处理方式

文章目录
  • 1. 异常链
  • 2. catch+throw使用示例
      • 1. 自定义异常
      • 2. Service层抛出IncidentException
      • 3. Controller层继续抛出IncidentException
      • 4. 统一异常处理
  • 3. catch + throw使用示例
      • 1. 自定义异常QueryAlertException
      • 2. Service层抛出IOException
      • 3. Controller层捕获IOException
      • 4. 统一异常处理
  • 4. catch+throw使用示例
      • 1. 自定义异常
      • 2. 异常状态码
      • 3. Service层捕获IOException后抛出CommonException
      • 4. 统一异常处理
  • 5. throw使用示例
      • 1. 自定义异常
      • 2. Service层抛出异常
      • 3. 统一异常处理

1. 异常链

对于真实的企业级应用而言,常常有严格的分层关系,层与层之间有非常清晰的划分,上层功能的实现严格依赖于下层的API,也不会跨层访问。

当业务逻辑层访问持久层出现SQLException异常时,程序不应该把底层的SQLException异常传到用户界面,有如下两个原因:

  • 对于正常用户而言,他们不想看到底层SQLException异常,SQLException异常对他们使用该系统没有任何帮助。
  • 对于恶意用户而言,将SQLException异常暴露出来不安全。

底层的原始异常直接传给用户是一种不负责任的表现。通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译。

public class MyException extends Exception {
    public MyException(){

    }

    public MyException(String message){
        super(message);
    }
}
public class Test {
    public static void main(String[] args) {
        
    }

    public static void  calSal() throws MyException {
       try {
           //实现工资结算
       }catch (SQLException sqle){
           // 把原始异常信息记录下来,留给管理员
           // ...
           // 下面异常中的message就是对用户的提示
           throw new MyException("访问底层数据库出现异常");
       }catch (Exception e){
           // 把原始异常信息记录下来,留给管理员
           // ...
           // 下面异常中的message就是对用户的提示
           throw new MyException("系统未知异常");
       }
    }
}

这种把原始异常信息隐藏起来,仅向上提供必要的异常提示信息的处理方式,可以保证底层异常不会扩散到表现层,可以避免向上暴露太多的实现细节,这完全符合面向对象的封装原则。

2. catch+throw使用示例 1. 自定义异常
@Data
public class IncidentException extends Exception {
    private static final long serialVersionUID = -7888549951554226678L;

    private Integer code;

    private Object data;

    public IncidentException() {
        this("");
    }

    public IncidentException(String message) {
        this(0, message);
    }

    public IncidentException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public IncidentException(Integer code, String message, Object data) {
        super(message);
        this.code = code;
        this.data = data;
    }

    public IncidentException(Integer code, String message, Throwable throwable) {
        super(message, throwable);
        this.code = code;
    }
}

对于Checked异常的处理方式有如下两种:

  • 当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修复该异常。
  • 当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
2. Service层抛出IncidentException
@Override
public BatchOperateResult toBeTransferred(ToBeTransferOperateQo toBeTransferOperateQo) throws IncidentException {
    try {
        // 该方法会抛出IOException
        List cascadeIds = this.alertDao.getCascadeIds(notAddIds, null);
    
        // 该方法会抛出IOException, JsonSerializeException
        List alerts = this.alertDao.getAlerts(notAddIds);

        // 该方法会抛出NotificationException
        riskEventService.saveBatch(alertList), addResult)
            
    } catch (IOException | JsonSerializeException | NotificationException e) {
        // 这里最好打印日志记录异常发生的详细情况,应用后台需要通过日志来记录异常发生的详细情况;
        // 程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息
        throw new IncidentException("标记为待流转失败", e);
    }
}

这种catch和throw结合使用的情况在大型企业级应用中非常常用,企业级应用对异常的处理通常分成两个部分:

① 应用后台需要通过日志来记录异常发生的详细情况;

② 应用还需要根据异常向应用使用者传达某种提示。在这种情形下,所有异常都需要两个方法共同完成,也就必须将catch和throw结合使用。

3. Controller层继续抛出IncidentException
@PostMapping(value = "/transfer")
public ApiResponse toBeTransferred(
    @RequestBody ToBeTransferOperateQo qo) throws IncidentException {
    BatchOperateResult batchOperateResult 
        = this.alertService.toBeTransferred(toBeTransferOperateWithLogQo.getData());
    return this.dealBatchResultOrThrow(batchOperateResult);
}

Controller层并没有对Service层抛出的异常对象进行捕获处理,而是继续抛出IncidentException

4. 统一异常处理
@RestControllerAdvice
@Slf4j
@Priority(1)
public class GlobalExceptionHandler {

    @ExceptionHandler(IncidentException.class)
    public ApiResponse handleIncidentException(IncidentException e) {
        // 打印日志
        log.error(e.getMessage(), e);
        // 给用户的提示信息
        return ApiResponse.newInstance(e.getCode(), e.getMessage(), e.getData());
    }
}
 
3. catch + throw使用示例 
1. 自定义异常QueryAlertException 
public class QueryAlertException extends IncidentRuntimeException {

    public QueryAlertException(String message) {
        super(message);
    }

    public QueryAlertException(String message, Throwable throwable) {
        super(message, throwable);
    }
}
@Data
public class IncidentRuntimeException extends RuntimeException {
    private Integer code;

    private Object data;

    public IncidentRuntimeException() {
        this("");
    }

    public IncidentRuntimeException(String message) {
        this(ApiResponse.CODE_ERR_COMMON, message);
    }

    public IncidentRuntimeException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public IncidentRuntimeException(Integer code, String message, Object data) {
        super(message);
        this.code = code;
        this.data = data;
    }

    public IncidentRuntimeException(String message, Throwable throwable) {
        this(ApiResponse.CODE_ERR_COMMON, message, throwable);
    }

    public IncidentRuntimeException(Integer code, String message, Throwable throwable) {
        super(message, throwable);
        this.code = code;
    }

    public static IncidentRuntimeException fromThrowable(final Throwable t) {
        if (t == null) {
            return null;
        } else {
            return fromThrowableNonNull(t);
        }
    }

    public static IncidentRuntimeException fromThrowableNonNull(final Throwable t) {
        if (t instanceof IncidentRuntimeException) {
            return (IncidentRuntimeException) t;
        } else {
            return new IncidentRuntimeException(t.getMessage(), t);
        }
    }
}
2. Service层抛出IOException
@Override
public CooperatorResultDto getAlertCooperators(String eventId) throws IOException {
	// 该方法会抛出IOException
    Alert alert = this.getAlertById(eventId);
    return cooperatorResultDto;
}
3. Controller层捕获IOException
@GetMapping("/cooperators/{id}")
public CooperatorResultDto getAlertCooperator(@PathVariable("id") String id) {
    try {
        return alertService.getAlertCooperators(id);
    } catch (IOException e) {
        throw new QueryAlertException("协作人信息查询失败");
    }
}

底层的原始异常直接传给用户是一种不负责任的表现。通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译。

4. 统一异常处理
@RestControllerAdvice
@Slf4j
@Priority(1)
public class GlobalExceptionHandler {


    @ExceptionHandler(IncidentRuntimeException.class)
    public ApiResponse handleIncidentRuntimeException(IncidentRuntimeException e) {
        log.error(e.getMessage(), e);
        return ApiResponse.newInstance(e.getCode(), e.getMessage(), e.getData());
    }
}
 
4. catch+throw使用示例 
1. 自定义异常 
public class CommonException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    private BizCodeEnum codeEnum;

    public CommonException() {
    }

    public BizCodeEnum getCodeEnum() {
        return codeEnum;
    }

    public CommonException(BizCodeEnum codeEnum) {
        super(codeEnum.getMessage());
        this.codeEnum = codeEnum;
    }

    public CommonException(BizCodeEnum codeEnum, Throwable cause) {
        super(codeEnum.getMessage(), cause);
        this.codeEnum = codeEnum;
    }

    public CommonException(String message) {
        super(message);
    }

    public CommonException(String message, Throwable cause) {
        super(message, cause);
    }
}
2. 异常状态码
@Slf4j
public enum BizCodeEnum implements BaseCode {
    
    ATTACHMENT_UPLOAD_ERROR(10001, "attachment.upload.failed"),
    GRIDFS_ATTACHMENT_NOT_EXIST(10002, "mongodb.gridFs.attachment.not.exist"),
    ELASTICSEARCH_DOC_SELECT_ERROR(10007, "elasticsearch.document.select.failed"),

    private Integer code;
    private String message;

    BizCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    BizCodeEnum(String message) {
        this(null, message);
    }

    @Override
    public Integer getCode() {
        if (code == null) {
            return 400;
        }
        return code;
    }

    @Override
    public String getMessage() {
        String i18nStr;
        try {
            i18nStr = I18nUtils.i18n(message);
        } catch (NoSuchMessageException e) {
            log.error("Not found Internationalized configuration:{}", message);
            i18nStr = message;
        }
        return i18nStr;
    }
}
3. Service层捕获IOException后抛出CommonException
@Override
public SearchResultRespVo searchDoc(DocSearchReqVo docSearchReqVo) {
    SearchRequest searchRequest = buildSearchRequest(docSearchReqVo);
    long total;
    List docList = null;
    try {
        // 执行搜索
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        total = hits.getTotalHits().value;
        SearchHit[] searchHits = hits.getHits();
        docList = new ArrayList<>();
        for (SearchHit searchHit : searchHits) {
            docList.add(new DocListVo(JSON.parseObject(searchHit.getSourceAsString(), Doc.class)));
        }
    } catch (IOException e) {
        log.error("failed to query documents in elasticsearch:", e);
        throw new CommonException(BizCodeEnum.ELASTICSEARCH_DOC_SELECT_ERROR);
    }
    return new SearchResultRespVo(docList, total);
}

总结:

  • 捕获原始底层异常IOException
  • 打印日志
  • 抛出一个新的异常向用户返回提示信息

注意:

  • 因为CommonException是运行时异常,所以即使抛出了,也不需要显式的去处理。 CommonException extends RuntimeException
  • 如果是Checked Exception,那么就需要显式的处理,try…catch或者throws,比如:IncidentException extends Exception
4. 统一异常处理
@Slf4j
@RestControllerAdvice
public class DocExceptionHandler {

    @ExceptionHandler(CommonException.class)
    public ApiResponse serviceExceptionHandler(CommonException e) {
        log.error("service exception");
        if (Objects.nonNull(e.getCodeEnum())) {
            return ApiResponse.newInstance(e.getCodeEnum().getCode(), e.getMessage());
        } else {
            return ApiResponse.newInstance(400, e.getMessage());
        }
    }
}
 
5. throw使用示例 
1. 自定义异常 
public class DocAttachmentNotFoundException extends RuntimeException {
    public DocAttachmentNotFoundException() {
        super(I18nUtils.i18n("exception.docAttachment.instance.not.exists"));
    }
}
2. Service层抛出异常
@Override
public GridFsResource getById(String id) {
    DocAttachment docAttachment 
        = Optional.ofNullable(knowledgeMongoTemplate.findById(id, DocAttachment.class))
        .orElseThrow(DocAttachmentNotFoundException::new);
}

因为DocAttachmentNotFoundException是运行时异常,所以即使抛出了,也不需要显式的去处理。

3. 统一异常处理
@Slf4j
@RestControllerAdvice
public class DocExceptionHandler {

    @ExceptionHandler({ DocAttachmentNotFoundException.class })
    public ApiResponse exception(DocAttachmentNotFoundException e) {
        log.error(e.getMessage());
        return ApiResponse.newInstance(ApiResponse.CODE_PARAMETER_VALIDATE_FAILED, e.getMessage());
    }
}
 

总结:

对于Checked异常的处理方式有如下两种:

  • 当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修复该异常。
  • 当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

使用Checked异常至少存在如下两大不便之处:

  • 对于程序中的Checked异常,Java要求必须显式捕获并处理该异常,或者显式声明抛出该异常。这样就增加了编程复杂度。
  • 如果在方法中显式声明抛出Checked异常,将会导致方法签名与异常耦合,如果该方法是重写父类的方法,则该方法抛出的异常还会受到被重写方法所抛出异常的限制。

在大部分情况下,推荐使用Runtime异常,而不使用Checked异常。尤其当程序需要自行抛出异常时,使用Runtime异常将更加简洁。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

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

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