如何减少try catch块的使用,精细方法介绍

本文介绍了如何减少Java代码中try-catch块的使用,提高代码可读性和效率。通过使用Spring的@ControllerAdvice和@ExceptionHandler,实现了统一的异常处理。此外,提出使用自定义的Assert接口和枚举类结合,以优雅的方式处理业务异常,避免了大量的异常类定义。通过定义统一异常处理器,可以处理包括ServletException、ServiceException、自定义业务异常在内的多种异常,确保系统稳定运行。
摘要由CSDN通过智能技术生成

软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异常情况,所以代码中就会出现大量的try {…} catch {…} finally {…}代码块,不仅有大量的冗余代码,而且还影响代码的可读性。

比较下面两张图,看看您现在编写的代码属于哪一种风格?然后哪种编码风格您更喜欢?

丑陋的 try catch 代码块

图片
优雅的Controller

图片
上面的示例,还只是在Controller层,如果是在Service层,可能会有更多的try catch代码块。这将会严重影响代码的可读性、“美观性”。

所以如果是我的话,我肯定偏向于第二种,我可以把更多的精力放在业务代码的开发,同时代码也会变得更加简洁。

既然业务代码不显式地对异常进行捕获、处理,而异常肯定还是处理的,不然系统岂不是动不动就崩溃了,所以必须得有其他地方捕获并处理这些异常。公众号(Java后端)还发布过很多编程技巧文章,关注「Java后端」回复 666 下载。

那么问题来了,如何优雅的处理各种异常?

什么是统一异常处理
Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAttribute等注解注解配套使用,对于这几个注解的作用,这里不做过多赘述,若有不了解的,可以参考Spring3.2新注解@ControllerAdvice,先大概有个了解。

不过跟异常处理相关的只有注解@ExceptionHandler,从字面上看,就是异常处理器的意思,其实际作用也是:若在某个Controller类定义一个异常处理方法,并在方法上添加该注解,那么当出现指定的异常时,会执行该处理异常的方法,其可以使用springmvc提供的数据绑定,比如注入HttpServletRequest等,还可以接受一个当前抛出的Throwable对象。

但是,这样一来,就必须在每一个Controller类都定义一套这样的异常处理方法,因为异常可以是各种各样。这样一来,就会造成大量的冗余代码,而且若需要新增一种异常的处理逻辑,就必须修改所有Controller类了,很不优雅。

当然你可能会说,那就定义个类似BaseController的基类,这样总行了吧。

这种做法虽然没错,但仍不尽善尽美,因为这样的代码有一定的侵入性和耦合性。简简单单的Controller,我为啥非得继承这样一个类呢,万一已经继承其他基类了呢。大家都知道Java只能继承一个类。

那有没有一种方案,既不需要跟Controller耦合,也可以将定义的异常处理器应用到所有控制器呢?所以注解@ControllerAdvice出现了,简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,然后在类的签名加上注解@ControllerAdvice,统一对不同阶段的、不同异常进行处理。这就是统一异常处理的原理。

注意到上面对异常按阶段进行分类,大体可以分成:进入Controller前的异常 和Service层异常,具体可以参考下图:

不同阶段的异常
目标
消灭95%以上的try catch代码块,以优雅的Assert(断言) 方式来校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的try catch代码块。

统一异常处理实战
在定义统一异常处理类之前,先来介绍一下如何优雅的判定异常情况并抛异常。

用 Assert(断言) 替换 throw exception
想必Assert(断言)大家都很熟悉,比如Spring家族的org.springframework.util.Assert,在我们写测试用例的时候经常会用到,使用断言能让我们编码的时候有一种非一般丝滑的感觉,比如:

@Test
public void test1() {

User user = userDao.selectById(userId);
Assert.notNull(user, “用户不存在.”);

}

@Test
public void test2() {
// 另一种写法
User user = userDao.selectById(userId);
if (user == null) {
throw new IllegalArgumentException(“用户不存在.”);
}
}
有没有感觉第一种判定非空的写法很优雅,第二种写法则是相对丑陋的if {…}代码块。那么神奇的Assert.notNull()背后到底做了什么呢?下面是Assert的部分源码:

public abstract class Assert {
public Assert() {
}

public static void notNull(@Nullable Object object, String message) {  
    if (object == null) {  
        throw new IllegalArgumentException(message);  
    }  
}  

}
可以看到,Assert其实就是帮我们把if {…}封装了一下,是不是很神奇。虽然很简单,但不可否认的是编码体验至少提升了一个档次。那么我们能不能模仿org.springframework.util.Assert,也写一个断言类,不过断言失败后抛出的异常不是IllegalArgumentException这些内置异常,而是我们自己定义的异常。下面让我们来尝试一下。

Assert
public interface Assert {
/**
* 创建异常
* @param args
* @return
*/
BaseException newException(Object… args);

/**  
 * 创建异常  
 * @param t  
 * @param args  
 * @return  
 */  
BaseException newException(Throwable t, Object... args);  

/**  
 * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常  
 *  
 * @param obj 待判断对象  
 */  
default void assertNotNull(Object obj) {  
    if (obj == null) {  
        throw newException(obj);  
    }  
}  

/**  
 * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常  
 * <p>异常信息<code>message</code>支持传递参数方式,避免在判断之前进行字符串拼接操作  
 *  
 * @param obj 待判断对象  
 * @param args message占位符对应的参数列表  
 */  
default void assertNotNull(Object obj, Object... args) {  
    if (obj == null) {  
        throw newException(args);  
    }  
}  

}
上面的Assert断言方法是使用接口的默认方法定义的,然后有没有发现当断言失败后,抛出的异常不是具体的某个异常,而是交由2个newException接口方法提供。因为业务逻辑中出现的异常基本都是对应特定的场景,比如根据用户id获取用户信息,查询结果为null,此时抛出的异常可能为UserNotFoundException,并且有特定的异常码(比如7001)和异常信息“用户不存在”。所以具体抛出什么异常,有Assert的实现类决定。

看到这里,您可能会有这样的疑问,按照上面的说法,那岂不是有多少异常情况,就得有定义等量的断言类和异常类,这显然是反人类的,这也没想象中高明嘛。别急,且听我细细道来。

善解人意的Enum
自定义异常BaseException有2个属性,即code、message,这样一对属性,有没有想到什么类一般也会定义这2个属性?没错,就是枚举类。且看我如何将Enum和Assert结合起来,相信我一定会让你眼前一亮。如下:

public interface IResponseEnum {
in

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值