国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Dubbo 自定義異常,你是怎么處理的?

dingding199389 / 3636人閱讀

摘要:前言記錄對于自定義異常的處理方式實(shí)現(xiàn)目標(biāo)服務(wù)層異常,直接向上層拋出,層統(tǒng)一捕獲處理如果是系統(tǒng)自定義異常,則返回其中對應(yīng)為錯誤碼,對應(yīng)為異常信息如果非系統(tǒng)自定義異常,返回未知錯誤,同時將異常堆棧信息輸出到日志便于定位問題項目架構(gòu)先來張系統(tǒng)架

前言

記錄Dubbo對于自定義異常的處理方式.

實(shí)現(xiàn)目標(biāo)

服務(wù)層異常,直接向上層拋出,web層統(tǒng)一捕獲處理

如果是系統(tǒng)自定義異常,則返回{"code":xxx,"msg":yyy} 其中code對應(yīng)為錯誤碼msg對應(yīng)為異常信息

如果非系統(tǒng)自定義異常,返回{"code":-1,"msg":"未知錯誤"},同時將異常堆棧信息輸出到日志,便于定位問題

項目架構(gòu)

先來張系統(tǒng)架構(gòu)圖吧,這張圖來源自網(wǎng)絡(luò),相信現(xiàn)在大部分中小企業(yè)的分布式集群架構(gòu)都是類似這樣的設(shè)計:

簡要說明下分層架構(gòu):

通常情況下會有專門一臺堡壘機(jī)做統(tǒng)一的代理轉(zhuǎn)發(fā),客戶端(pc,移動端等)訪問由nginx統(tǒng)一暴露的入口

nginx反向代理,負(fù)載均衡到web服務(wù)器,由tomcat組成的集群,web層僅僅是作為接口請求的入口,沒有實(shí)際的業(yè)務(wù)邏輯

web層再用rpc遠(yuǎn)程調(diào)用注冊到zookeeperdubbo服務(wù)集群,dubbo服務(wù)與數(shù)據(jù)層交互,處理業(yè)務(wù)邏輯

前后端分離,使用json格式做數(shù)據(jù)交互,格式可以統(tǒng)一如下:

    {
        "code": 200,            //狀態(tài)碼:200成功,其他為失敗
        "msg": "success",       //消息,成功為success,其他為失敗原因
        "data": object     //具體的數(shù)據(jù)內(nèi)容,可以為任意格式
    }

映射為javabean可以統(tǒng)一定義為:

/**
 * @program: easywits
 * @description: http請求 返回的最外層對象
 * @author: zhangshaolin
 * @create: 2018-04-27 10:43
 **/
@Data
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class BaseResult implements Serializable{

    private static final long serialVersionUID = -6959952431964699958L;

    /**
     * 狀態(tài)碼:200成功,其他為失敗
     */
    public Integer code;

    /**
     * 成功為success,其他為失敗原因
     */
    public String msg;

    /**
     * 具體的內(nèi)容
     */
    public T data;
}

返回結(jié)果工具類封裝:

/**
 * @program: easywits
 * @description: http返回結(jié)果工具類
 * @author: zhangshaolin
 * @create: 2018-07-14 13:38
 **/
public class ResultUtil {

    /**
     * 訪問成功時調(diào)用 包含data
     * @param object
     * @return
     */
    public static BaseResult success(Object object){
        BaseResult result = new BaseResult();
        result.setCode(200);
        result.setMsg("success");
        result.setData(object);
        return result;
    }

    /**
     * 訪問成功時調(diào)用 不包含data
     * @return
     */
    public static BaseResult success(){
        return success(null);
    }

    /**
     * 返回異常情況 不包含data
     * @param code
     * @param msg
     * @return
     */
    public static BaseResult error(Integer code,String msg){
        BaseResult result = new BaseResult();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
    
     /**
     * 返回異常情況 包含data
     * @param resultEnum 結(jié)果枚舉類 統(tǒng)一管理 code msg
     * @param object
     * @return
     */
    public static BaseResult error(ResultEnum resultEnum,Object object){
        BaseResult result = error(resultEnum);
        result.setData(object);
        return result;
    }

    /**
     * 全局基類自定義異常 異常處理
     * @param e
     * @return
     */
    public static BaseResult error(BaseException e){
        return error(e.getCode(),e.getMessage());
    }

    /**
     * 返回異常情況 不包含data
     * @param resultEnum 結(jié)果枚舉類 統(tǒng)一管理 code msg
     * @return
     */
    public static BaseResult error(ResultEnum resultEnum){
        return error(resultEnum.getCode(),resultEnum.getMsg());
    }
}

因此,模擬一次前端調(diào)用請求的過程可以如下:

web層接口

@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Autowired
    UserService mUserService;
    @Loggable(descp = "用戶個人資料", include = "")
    @GetMapping(value = "/info")
    public BaseResult userInfo() {
        return mUserService.userInfo();
    }
}

服務(wù)層接口

 @Override
public BaseResult userInfo() {
    UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo();
    UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId());
    return ResultUtil.success(userInfoVo);
}

自定義系統(tǒng)異常

定義一個自定義異常,用于手動拋出異常信息,注意這里基礎(chǔ)RuntimeException未受檢異常

簡單說明,RuntimeException及其子類為未受檢異常,其他異常為受檢異常,未受檢異常是運(yùn)行時拋出的異常,而受檢異常則在編譯時則強(qiáng)則報錯
public class BaseException extends RuntimeException{

    private Integer code;

    public BaseException() {
    }

    public BaseException(ResultEnum resultEnum) {
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
    }
    ...省略set get方法
}

為了方便對結(jié)果統(tǒng)一管理,定義一個結(jié)果枚舉類:

public enum ResultEnum {
    UNKNOWN_ERROR(-1, "o(╥﹏╥)o~~系統(tǒng)出異常啦!,請聯(lián)系管理員!!!"),
    SUCCESS(200, "success");
    
    private Integer code;
    
    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
web層統(tǒng)一捕獲異常

定義BaseController抽象類,統(tǒng)一捕獲由服務(wù)層拋出的異常,所有新增Controller繼承該類即可。

public abstract class BaseController {
    private final static Logger LOGGER = LoggerFactory.getLogger(BaseController.class);
    
       /**
     * 統(tǒng)一異常處理
     *
     * @param e
     */
    @ExceptionHandler()
    public Object exceptionHandler(Exception e) {
        if (e instanceof BaseException) {
            //全局基類自定義異常,返回{code,msg}
            BaseException baseException = (BaseException) e;
            return ResultUtil.error(baseException);
        } else {
            LOGGER.error("系統(tǒng)異常: {}", e);
            return ResultUtil.error(ResultEnum.UNKNOWN_ERROR);
        }
    }
}
驗證

以上web層接口UserController繼承BaseController,統(tǒng)一捕獲異常

服務(wù)層假設(shè)拋出自定義系統(tǒng)異常BaseException,代碼如下:

 @Override
 public BaseResult userInfo() {
    UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo();
    UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId());
      if (userInfoVo != null) {
        //這里假設(shè)拋個自定義異常,返回結(jié)果{code:10228 msg:"用戶存在!"}
        throw new BaseException(ResultEnum.USER_EXIST);
    }
    return ResultUtil.success(userInfoVo);
}

然而調(diào)用結(jié)果后,上層捕獲到的異常卻不是BaseException,而被認(rèn)為了未知錯誤拋出了.帶著疑問看看Dubbo對于異常的處理

Dubbo異常處理

Dubbo對于異常有統(tǒng)一的攔截處理,以下是Dubbo異常攔截器主要代碼:

 @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        try {
            // 服務(wù)調(diào)用
            Result result = invoker.invoke(invocation);
            // 有異常,并且非泛化調(diào)用
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();

                    // directly throw if it"s checked exception
                    // 如果是checked異常,直接拋出
                    if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return result;
                    }
                    // directly throw if the exception appears in the signature
                    // 在方法簽名上有聲明,直接拋出
                    try {
                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                        Class[] exceptionClassses = method.getExceptionTypes();
                        for (Class exceptionClass : exceptionClassses) {
                            if (exception.getClass().equals(exceptionClass)) {
                                return result;
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        return result;
                    }

                    // 未在方法簽名上定義的異常,在服務(wù)器端打印 ERROR 日志
                    // for the exception not found in method"s signature, print ERROR message in server"s log.
                    logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                            + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

                    // 異常類和接口類在同一 jar 包里,直接拋出
                    // directly throw if exception class and interface class are in the same jar file.
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                        return result;
                    }
                    // 是JDK自帶的異常,直接拋出
                    // directly throw if it"s JDK exception
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
                    // 是Dubbo本身的異常,直接拋出
                    // directly throw if it"s dubbo exception
                    if (exception instanceof RpcException) {
                        return result;
                    }

                    // 否則,包裝成RuntimeException拋給客戶端
                    // otherwise, wrap with RuntimeException and throw back to the client
                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
                } catch (Throwable e) {
                    logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                            + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                    return result;
                }
            }
            // 返回
            return result;
        } catch (RuntimeException e) {
            logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                    + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                    + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            throw e;
        }
    }

簡要說明:

有異常,并且非泛化調(diào)用時,如果是受檢異常,則直接拋出

有異常,并且非泛化調(diào)用時,在方法簽名上有聲明,則直接拋出

有異常,并且非泛化調(diào)用時,異常類和接口類在同一 jar 包里,則直接拋出

有異常,并且非泛化調(diào)用時,是Dubbo本身的異常(RpcException),則直接拋出

有異常,并且非泛化調(diào)用時,剩下的情況,全部都會包裝成RuntimeException拋給客戶端

到現(xiàn)在問題很明顯了,我們自定義的BaseException未受檢異常,況且不符合Dubbo異常攔截器中直接拋出的要求,Dubbo將其包裝成了RuntimeException,所以在上層BaseController中統(tǒng)一捕獲為系統(tǒng)未知錯誤了.

解決辦法

異常類BaseException和接口類在同一 jar 包里,但是這種方式要在每個jar中放置一個異常類,不好統(tǒng)一維護(hù)管理

在接口方法簽名上顯式聲明拋出BaseException,這種方式相對簡單一些,比較好統(tǒng)一維護(hù),只是每個接口都要顯式聲明一下異常罷了,這里我選擇這種方式解決

問題

為什么一定要拋出自定義異常來中斷程序運(yùn)行,用return ResultUtil.error(ResultEnum resultEnum) 強(qiáng)制返回{code:xxx msg:xxx}結(jié)果,不是一樣可以強(qiáng)制中斷程序運(yùn)行?

玩過Spring的肯定都知道,Spring喲聲明式事物的概念,即在接口中添加事物注解,當(dāng)發(fā)生異常時,全部接口執(zhí)行事物回滾..看下方的偽代碼:

@Transactional(rollbackFor = Exception.class)
public BaseResult handleData(){
    
    //1. 操作數(shù)據(jù)庫,新增數(shù)據(jù)表A一條數(shù)據(jù),返回新增數(shù)據(jù)主鍵id
    
    //2. 操作數(shù)據(jù)庫,新增數(shù)據(jù)庫B一條數(shù)據(jù),以數(shù)據(jù)表A主鍵id為外鍵關(guān)聯(lián)
    
    //3. 執(zhí)行成功 返回結(jié)果
}

該接口聲明了異常事物回滾,發(fā)送異常時會全部回滾

步驟1數(shù)據(jù)入庫失敗,理論上是拿不到主鍵id的,此時應(yīng)當(dāng)拋出自定義異常,提示操作失敗

如果步驟1數(shù)據(jù)入庫成功,步驟2中數(shù)據(jù)入庫失敗,那么理論上步驟1中的數(shù)據(jù)應(yīng)當(dāng)也要回滾,如果此時強(qiáng)制返回異常結(jié)果,那么步驟1入庫數(shù)據(jù)則成為臟數(shù)據(jù),此時拋出自定義異常是最合理的

最后的思考

在實(shí)際問題場景中去閱讀源碼是最合適的,帶著問題有目的的看指定源碼會讓人有豁然開朗的感覺.

更多原創(chuàng)文章會第一時間推送公眾號【張少林同學(xué)】,歡迎關(guān)注!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72830.html

相關(guān)文章

  • 前后端分離應(yīng)用——用戶信息傳遞

    摘要:前言記錄前后端分離的系統(tǒng)應(yīng)用下應(yīng)用場景用戶信息傳遞需求緣起照例先看看系統(tǒng)的一張經(jīng)典架構(gòu)圖,這張圖參考自網(wǎng)絡(luò)在自定義異常,你是怎么處理的中已經(jīng)對該架構(gòu)做了簡單說明,這里不再描述。 showImg(https://segmentfault.com/img/remote/1460000017839927?w=1024&h=768); 前言 記錄前后端分離的系統(tǒng)應(yīng)用下應(yīng)用場景————用戶信息傳...

    PAMPANG 評論0 收藏0
  • 瀏覽器如何解析html、css、js

    摘要:全局變量局部變量全局函數(shù)一段也是一塊域。此時打印的自然是,要記住相當(dāng)于,所以這時候改變的是局部變量,并沒有影響到全局變量,所以第二次打印的依然是。 在熟悉了瀏覽器的工作原理之后,今天我們來講講瀏覽器在從服務(wù)器獲取到網(wǎng)頁文件之后是如何解析的。了解了這個基礎(chǔ)知識,對敲出來的代碼,質(zhì)量會有不小的提升。 一、瀏覽器如何解析html html文件在沒有寫入html標(biāo)簽之前和txt文本是一個性質(zhì)的...

    Awbeci 評論0 收藏0
  • 瀏覽器如何解析html、css、js

    摘要:全局變量局部變量全局函數(shù)一段也是一塊域。此時打印的自然是,要記住相當(dāng)于,所以這時候改變的是局部變量,并沒有影響到全局變量,所以第二次打印的依然是。 在熟悉了瀏覽器的工作原理之后,今天我們來講講瀏覽器在從服務(wù)器獲取到網(wǎng)頁文件之后是如何解析的。了解了這個基礎(chǔ)知識,對敲出來的代碼,質(zhì)量會有不小的提升。 一、瀏覽器如何解析html html文件在沒有寫入html標(biāo)簽之前和txt文本是一個性質(zhì)的...

    Panda 評論0 收藏0
  • 瀏覽器如何解析html、css、js

    摘要:全局變量局部變量全局函數(shù)一段也是一塊域。此時打印的自然是,要記住相當(dāng)于,所以這時候改變的是局部變量,并沒有影響到全局變量,所以第二次打印的依然是。 在熟悉了瀏覽器的工作原理之后,今天我們來講講瀏覽器在從服務(wù)器獲取到網(wǎng)頁文件之后是如何解析的。了解了這個基礎(chǔ)知識,對敲出來的代碼,質(zhì)量會有不小的提升。 一、瀏覽器如何解析html html文件在沒有寫入html標(biāo)簽之前和txt文本是一個性質(zhì)的...

    Salamander 評論0 收藏0
  • 深度學(xué)習(xí)研究綜述

    摘要:此原因在一定程度上阻礙了深度學(xué)習(xí)的發(fā)展,并將大多數(shù)機(jī)器學(xué)習(xí)和信號處理研究,從神經(jīng)網(wǎng)絡(luò)轉(zhuǎn)移到相對較容易訓(xùn)練的淺層學(xué)習(xí)結(jié)構(gòu)。深度學(xué)習(xí)算法可以看成核機(jī)器學(xué)習(xí)中一個優(yōu)越的特征表示方法。 摘要:深度學(xué)習(xí)是一類新興的多層神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)算法。因其緩解了傳統(tǒng)訓(xùn)練算法的局部最小性, 引起機(jī)器學(xué)習(xí)領(lǐng)域的廣泛關(guān)注。首先論述了深度學(xué)習(xí)興起淵源, 分析了算法的優(yōu)越性, 并介紹了主流學(xué)習(xí)算法及應(yīng)用現(xiàn)狀,最后總結(jié)當(dāng)前存在的...

    jokester 評論0 收藏0

發(fā)表評論

0條評論

dingding199389

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<