這樣搞——保證你的代碼沒有try-catch

常常聽到有技術圈的朋友抱怨,尤其是從其他語言轉到Java語言的同行,說Java的try-catch語言讓代碼顯得很凌亂。的確,作爲一個Java Follower,筆者也覺得Java中的try-catch會導致代碼很不整齊,易讀性變差。那麼有什麼好辦法讓Java工程中儘量不出現try-catch語法塊呢?辦法還真有,請聽我娓娓道來!

首先,筆者帶領大家回顧一下Java的異常處理機制。如下圖,Java中有個類叫做Throwable。該Throwable類有兩個子類,一個是Error,一個是Exception。Error代表JVM系統級別的錯誤,通常是程序員不可控的,因此不需要程序員過多的關注。因此,在Java中提起異常時,通常指的是Exception類及其子類。

Java異常結構圖

從上圖中也可以看出,Exception類的子類分爲RuntimeException類和其他子類。這是需要我們重點關注的。其中RuntimeException類是unchecked異常類(是指那些不需要try-catch捕獲或者顯式拋出的異常類),而Exception類的其他子類是checked異常類(指代那些需要try-catch捕獲或者顯示拋出的類)。因此,我們的程序中之所以有太多try-catch代碼塊的原因,就在於我們拋出了太多的checked異常類。


因此,筆者覺得有一種方案可以考慮,我們是否可以將一個軟件系統中的所有自定義異常類都定義爲RuntimeException類的子類,比如可以叫做SystemException和LogicException。SystemException的子類代表是真的系統運行時錯誤引起的異常,而LogicException的子類代表那些並非是系統運行時錯誤引起的異常,比如“用戶不存在”這種邏輯異常。


這樣在我們的軟件系統中,所有的異常都是RuntimeException。即便有些方法會拋出checked異常,我們可以在他的try-catch中二次拋出一個自定義的SystemException或者LogicException。通過這樣的方式,我們的系統中就基本上不需要感知異常的處理模塊了。


但畢竟異常處理是一個系統健壯性必不可少的一部分。那麼我們怎麼來處理我們自定義的SystemException和LogicException呢?相比熟練使用Spring框架的童鞋一定知道SpringMVC中有這樣一個類:HandlerExceptionResolver。這個類的作用是捕獲Spring在運行過程中的所有異常。網上關於這個類的介紹有很多,在此不再贅述。

本來我們寫一個HandlerExceptionResolver的實現類,然後在Spring的配置文件中聲明該bean即可。但是爲了實現可插拔的設計,使用同博客玩轉Spring——從拒絕filter開始相同的方式。我們來看代碼:

public class DefaultExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        if (e instanceof BusinessException) {
            System.out.println("this is a businessException");
            BusinessException businessException = (BusinessException) e;
            putErrorEnumToResponse(businessException.getErrorEnum(), httpServletResponse);
        } else if (e instanceof SystemException) {
            System.out.println("this is a businessException");
            SystemException systemException = (SystemException) e;
            putErrorEnumToResponse(systemException.getErrorEnum(), httpServletResponse);
        } else {
            System.out.println("this is a unknownException");
            putErrorEnumToResponse(ErrorEnum.UNKNOWN_EXCEPTION, httpServletResponse);
        }
        return null;
    }

    private static void putErrorEnumToResponse(ErrorEnum errorEnum, HttpServletResponse response) {
        response.setContentType("application/json;charset=utf-8");
        response.addHeader("Content-Length", String.valueOf(errorEnum.toString().length()));
        String result= JSON.toJSONString(HttpResult.failedResult(errorEnum));
        try {
            ServletOutputStream servletOutputStream = response.getOutputStream();
            servletOutputStream.print(result);
            servletOutputStream.flush();
        } catch (IOException e) {
            System.out.println("put error msg to response exception");
            e.printStackTrace();
        }
    }

}

其中的ErrorEnum是一個枚舉,它定義了系統中所有出現的異常信息。

瞧,僅僅上面 一個類,就解決了整個Web系統中的異常處理。業務Developer僅僅需要在異常發生的地方判斷該異常是屬於SystemException還是BusinessException,然後定義一個說明該Exception的ErrorEnum作爲SystemException或者BusinessException的參數,然後直接使用throw語句拋出即可。由於SystemException和BusinessException都是RuntimeException的子類,因此他們不再需要使用try-catch捕獲或者顯示在方法定義中聲明,十分方便。而且HandlerExceptionResolver爲我們提供了統一的異常處理入口,可以讓我們方便快捷,輕鬆愉快的完成整個異常系統的處理任務。下面是一個業務代碼使用異常的例子:

@Service("userService")
public class UserServiceImpl implements IUserService {


    @Resource(name = "userDao")
    private UserDao userDao;

    @Override
    public HttpResult<UserDTO> getUserByAccount(String account) {
        UserDO userDO = userDao.getUserByAccount(account);
        if (userDO == null) {
            throw new BusinessException(ErrorEnum.USER_NOT_EXIST);
        }
        UserDTO userDTO = UserConvent.conventToUserDTO(userDO);
        return HttpResult.successResult(userDTO);
    }
}

上面是UserController對應的邏輯層的Service類,當用戶不存在時,Service層直接拋出邏輯異常,這個異常會被DefaultExceptionHandler捕獲,而UserController完全不需要感知該異常的存在。在來看一個Controller層拋出異常的例子:

@Controller
@RequestMapping(value = CommonUrl.UrlConstant.TEST_PREFIX)
public class TestExceptionController {
    @RequestMapping(value = CommonUrl.UrlConstant.TEST_CONTROLLER_EXCEPTION, produces = CommonUtils.CONTENT_TYPE)
    @ResponseBody
    public String getUserByAccount(HttpServletRequest request, HttpServletResponse response) {
        return CommonExecutor.execute(request, response, BaseParam.class, new CommonExecute() {
            @Override
            public HttpResult execute(BaseParam param) {
                throw new SystemException(ErrorEnum.UNKNOWN_EXCEPTION);
            }
        });
    }
}

可見,通過這樣的方式解決系統中的異常處理還是很好的。

覺得本文好的話,別忘了關注我哦。

代碼參見本人的github。點擊打開鏈接

筆者開設了一個知乎live,詳細的介紹的JAVA從入門到精通該如何學,學什麼?

提供給想深入學習和提高JAVA能力的同學,歡迎收聽https://www.zhihu.com/lives/932192204248682496




 
發佈了140 篇原創文章 · 獲贊 92 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章