如何处理java异常

我们开发的业务系统,或者是产品,常常面临着这样的问题:

1、系统运行出错,但是完全不知道错误发生的位置。

2、我们找到了错误的位置,但是完全不知道是因为什么。

3、系统明明出了错误,但是就是看不到错误堆栈信息。


什么情况需要自定义异常


经常看到一些项目,在全局定义一个 AppException,然后所有地方都只抛出这个异常,并且把捕获的异常case到这个AppException中。会有如下问题:

1、浪费log日志存储空间,并且栈顶并不是最接近发生异常的代码位置。

2、只有一种异常类,无法精准区分开异常类型。

3、异常类后期难以修改以增加其携带的信息。


什么情况需要手动处理异常

1、你有能力处理异常,并且你知道如何处理。

2、你有责任处理异常。


自定义业务异常


考虑如下场景:系统提供一个API,用于修改用户信息,服务器端采用json数据交互.首先我们定义ServiceException,用来表示业务逻辑受理失败,它仅表示我们处理业务的时候发现无法继续执行下去。

package com.example.demo.exception;

/**
 * @Description TODO
 * @Date 2019/1/21 9:29
 * @Author zsj
 * 业务受理异常
 */
public class ServiceException extends RuntimeException {
    String code;
    //异常原因
    String reason;
    public ServiceException() {
    }

    public ServiceException(String code, String reason) {
        this.code = code;
        this.reason = reason;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }
}

接下来看下Controller层.

package com.example.demo.controller;

/**
 * @Description TODO
 * @Date 2019/1/17 11:08
 * @Author zsj
 */
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
@RestController
public class UserController {
    @Autowired
    UserService userService;


    @GetMapping("update")
    public String update(){
        User user = new User();
        //业务逻辑更新用户
        userService.updateUser(user);
        return "1";

    }

}

一个业务系统不可能不对用户提交的数据进行验证,验证包括两方面:有效性和合法性

     1、有效性: 比如用户所在岗位,是否属于数据库有记录的岗位ID,如果不存在,无效.

     2、合法性: 比如用户名只允许输入最多12个字符,用户提交了20个字符,不合法.

有效性检查,可以交给java的校验框架执行,比如JSR303。假设用户提交的数据经过验证都合法,还是有一些情况是不能调用修改逻辑的。

    1、要修改的用户ID不存在.

    2、用户被锁定,不允许修改.

    3、乐观锁机制发现用户已经被被人修改过.

    4、由于某种原因,我们的程序无法保存到数据库.

    5、一些程序员错误的开发了代码,导致保存过程中出现异常,比如NPE.

对于前3种,我们认为是有效性检查失败,第4种属与我们无法处理的异常,第5种就是程序员bug。

现在的问题是,前三种情况我们如何通知用户呢?

1、在controller 调用userService的checkUserExist()方法.

2、在controller直接书写业务逻辑.

3、在service响应一个状态码机制,比如1 2 3表示错误信息,0 表示没有任何错误.

显然前2种方法都不可取,因为MVC不设计模式告诉我们,controller是用来接收页面参数,并且调用逻辑处理,最后组织页面响应的地方。我们不可以在controller进行逻辑处理,controller只应该负责用户API入口和响应的处理(如若不然,思考一下如果有一天service的代码打包成jar放到另一个平台,没有controller了,该怎么办?)

状态码机制是个不错的选择,可是如此一来,用户保存逻辑变了,比如增加一个情况,不允许修改已经离职的用户,那么我们还需要修改controller的代码,代码量增加,维护成本增高,并且还耦合了service,不符合MVC设计模式。

那么怎么办呢?现在我们来看下service代码如何编写

package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.exception.ServiceException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Description TODO
 * @Date 2019/1/21 9:32
 * @Author zsj
 */
@Service
public class UserService {
   
    public int updateUser(User user){
        if (StringUtils.isBlank(user.getUserName())){
            throw new ServiceException("50001","用户名为空。");
        }
        return 1;
    }

}


这样一来只要我们检查到不允许保存的用户,我们就可以直接throw 一个新的异常,异常机制会帮助我们中断代码执行。

接下来有2种选择:

 1、在controller 使用try-catch进行处理.

 2、直接把异常抛给上层框架统一处理.

第1种方式是不可取的,注意我们抛出的ServiceException,它仅仅逻辑处理异常,并且我们的方法前面没有声明throws ServiceException,这表示他是一个非受查异常.controller也没有关心会发生什么异常。

为什么不定义成受查异常呢?如果是一个受查异常,那么意味着controller必须要处理你的异常。并且如果有一天你的业务逻辑变了,可能多一种检查项,就需要增加一个异常,反之需要删除一个异常,那么你的方法签名也需要改变,controller也随之要改变,这又变成了紧耦合,这和用状态码123表示处理结果没有什么不同。

我们可以为每一种检查项定义一个异常吗?可以,但是那样显得太多余了。因为业务逻辑处理失败的时候,根据我们需求,我们只需要通知用户失败的原因(通常应该是一段字符串),以及服务器受理失败的一个状态码(有时可能不需要状态码,这要看你的设计了),这样这需要一个包含原因属性的异常即可满足我们需求。

最后我们决定这个异常继承自RuntimeException。并且包含一个接受一个错误原因的构造器,这样controller层也不需要知道异常,只要全局捕获到ServiceException做统一的处理即可。

异常不提供无参构造器,因为绝对不允许你抛出一个逻辑处理异常,但是不指明原因,想想看,你是必须要告诉用户为什么受理失败的!

如此一来,我们只需要全局统一处理下 ServiceException 就可以了,很好,spring为我们提供了ControllerAdvice机制,有关ControllerAdvice,可以查阅springMVC使用文档,

下面是一个简单的示例:

package com.example.demo.globalException;

import com.example.demo.exception.DaoException;
import com.example.demo.exception.ServiceException;
import org.springframework.http.HttpStatus;
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;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Date 2019/1/21 9:42
 * @Author zsj
 */
@ControllerAdvice
public class MyControllerAdvice {
   @ResponseBody
   @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
   @ExceptionHandler(ServiceException.class)
    public Map<String,Object> handServiceException(ServiceException serviceException){
        Map<String,Object> objectMap = new HashMap<>();
        objectMap.put("mss",serviceException.getReason());
        return  objectMap;
    }
    
}

 


在这个时候,我们就可以很轻松的处理各种情况了.


如此一来没有任何地方需要关心异常,或者业务逻辑校验失败的情况。用户也可以得到很友好的错误提示。

 

数据访问层sql错误处理

package com.ahies.dit.management.filter;

import org.springframework.http.HttpStatus;
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;

import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Date 2019/1/21 9:42
 * @Author zsj
 */
@ControllerAdvice
public class MyControllerAdvice {
   @ResponseBody
   @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
   @ExceptionHandler(SQLException.class)
    public Map<String,Object> handServiceException(SQLException serviceException){
        Map<String,Object> objectMap = new HashMap<>();
        objectMap.put("mss",serviceException.getMessage());
        return  objectMap;
    }

}

全局异常处理

 

package com.ahies.stm.app.exception;

import org.springframework.http.HttpStatus;
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;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Date 2019/8/29 18:17
 * @Author zsj
 */

@ControllerAdvice
public class StmControllerAdvice {

    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_EXTENDED)
    @ExceptionHandler(ServiceException.class)
    public Map<String, Object> handServiceException(HttpServletRequest request, ServiceException serviceException) {
        Map<String, Object> objectMap = new HashMap<>();
        objectMap.put("msg", serviceException.getReason());
        System.out.println(request.getRequestURL());
        return objectMap;
    }


}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非ban必选

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值