异常与日志

异常

       当程序出现了错误,而不对它进行处理时,程序将会终止并报错,为了不影响后续程序的执行,可以采用异常处理机制 Exception Handler。将异常进行抛出throw,并将控制权交给处理器,这样程序就不会终止了。
       异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
       在 Java 中,如果某个方法不能采用正确的途径完成它的任务,就可以通过另外一个路径退出方法。在这种情况下,方法并不返回任何值,而是抛出一个封装了错误信息的对象。
       遇到异常就会入异常栈,Throwable 类有个方法:printStackTrace就是打印栈轨迹。
       异常是个类:Throwable。其下有两个子类:Error 致命异常 类和 Exception 非致命异常 类, ;其中 Error 类描述了 Java 运行时系统的内部错误和资源耗尽错误,比如虚拟机错误和系统崩溃 等,是不需要捕获或抛出的,我们处理不了的。任何程序都具有出现这种错误的潜能,而也是不能控制的。

在这里插入图片描述

        而Exception类有两个子类:CheckedException 和 RuntimeException。
       CheckedException 受查异常,是在编译运行之前,就要知道是否发生异常,这类异常必须进行捕获,否则无法通过编译。常见子类有 SQLException 和 IOException。要求对受查异常必须进行捕获或是抛出处理。编译器会检查是否为受查异常提供了处理器。
       checked 异常可以进一步细分为两类:

  • 无能为力、引起注意型。针对此类异常,程序无法处理,如 字段超长导致的 SQLException ,即使做再多的重试对解决异常也没有任何帮助 ,一般处理此类异常的做法是完整地保存异常现场,供开发工程师介入解决。
  • 力所能及、坦然处置型。如发生 未授权异常 UnAuthorizedException ,程序可跳转至权限申请页面。

       除了 受查异常 以外的异常都属于 非受查异常,非受查异常可以不被捕获和声明。
       非受查异常有两种:一种是无法处理的——Error类,一种是希望被避免的(如编写程序时就有检查) ——RuntimeException类。       RuntimeException的常见子类有 ClassCastException,(类型转换异常),ArrayIndexOutOfBoundsException(数组下标越界),ArithmeticException(运算异常),NullPointException(空指针异常) 。
        RuntimeException 可以进一步细分为 3 类:

  • 可预测异常。常见的可预测异常包括 NullPointException(空指针异常)、IndexOutOfBoundsException 等,基于对代码的性能和稳定性要求,此类异常不应该被产生或抛出,而应该提前做好边界检查、空指针判断等处理。显示的声明或者捕获此类异常会对程序的可读性和效率产生很大的影响。

  • 需捕捉异常。例如在使用 Dubbo 框架进行 RPC 调用时产生的远程服务超时异常 DubboTimeoutException ,此类异常是客户端必须显式处理的异常,不能因服务端的异常导致客户端不可用,此时处理方案可以是重试或者降级处理。

  • 可透出异常。主要是 框架 或 系统 产生的且会自行处理的异常,而程序无须关心。例如针对 Spring 框架中抛出的 NoSuchRequestHandingMethodException 异常,Spring 框架会自己完成异常的处理,默认将自身抛出的异常自动映射到合适的状态码,比如 启动防护机制 跳转到 404 页面。

    “RuntimeException”遇到的问题是代码(你)的问题。“ 😟

在这里插入图片描述
       如果异常在当前方法的处理能力范围之内且没有必要对外透出,那么就之间捕获异常并做相应处理;否则向上抛出,由上层方法或者框架来处理。(向上声明只是告诉调用者,在使用该方法时,应当对那些异常进行处理。)
       子类如果覆写了父类的方法,它抛出的异常类型必须小于等于父类方法中抛出的异常。如果父类没有抛出任何受查异常,那么子类也不能抛出任何受查异常。
        try-catch-finally 是处理程序异常的三部曲。当存在 try 时,可以只有 catch 代码块,也可以只有 finally 代码块,就是不能单独只有 try 这个光杆司令。

try { 可能出现异常的代码块 }
catch ( 可能出现的异常) { 处理 }
finally {最终执行的}

       如果在 try 块中没有遇到 catch() 中捕获的异常,那么就会在执行完 try 块后,返回主调函数 , 即调用了该方法,需要求该方法返回值的地方 前,执行 finally 块,执行完 finally 块,才返回 try 块中的值, 调用结束。因此,如果 finally 中有 return 语句,就会覆盖掉原来 try 块中的返回值,因此 需要避免在finally 块中写 return。
       如果 try 中遇到了 catch() 中捕获的异常,那么就会停止try 中的执行,跳转去执行 catch 块中的内容,而在该方法返回主调函数前,仍会执行finally块的内容。

       如果 finally 代码块没有执行,那么有三种可能:

  • 没有进入 try 代码块。
  • 进入 try 代码块,但是代码运行中出现了死循环 或 死锁状态。
  • 进入 try 代码块,但是执行了 System.exit() 操作。

        推荐对外提供的开放接口使用错误码;公司内部跨应用远程服务调用优先考虑使用 Result 对象来封装错误码、错误描述信息;而应用内部则推荐直接抛出异常对象。


👀例:

InputStream in=new FileInputStream( ... );

//1
code that might throw exception
//2

}
catch(IOException e)
{
//3
show error message

//4
}
finally
{
//5
in.clos();

}
//6

在上面这段代码中,有下列 3 种情况会执行finally子句:
1)代码没有抛出异常。
(不管是否有异常被捕获,finally子句中的代码都被执行。)
在这种情况下,程序首先执行 try 语句块种的全部代码,然后执行 finally 子句中的代码。随后,继续执行 try 语句块之后的第一条语句。
1、2、5、6。

2)抛出一个在catch子句中捕获的异常。
比如上述例子的 IOException。在这种情况下,程序将执行try语句块中的所有代码,直到发生异常为止。此时,将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句:
1、3、4、5、6。
如果catch子句有抛出异常,异常将会被抛回到这个方法的调用者。
1、3、5。
( 当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。)
所以不执行6。

3)代码抛出了一个异常,但这个异常不是由catch块捕获的。
在这种情况下,程序将执行try语句块中的所有语句,直到有异常被抛出为止。此时,将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法的调用者。
1、5。

(2)用户可以创建自己的异常类。

(3)
关于在finally块种修改return值的问题:
上代码:

public class SubClass
{
    public static int testFinally1()
    {
        int result=1;
        try
        {
            result=2;
            return result;
        }
        finally {
            result=3;
            System.out.println("execute finally1");
        }
    }
    public static StringBuffer testfinally2()
    {StringBuffer s=new StringBuffer("Hello");
    try
    {
        return s;
    }

    catch (Exception e)
    {return null;}
    finally {
        s.append("World");
        System.out.println("execute finally2");
    }
    }



    public static void main(String[] args) {
        int resultVal = testFinally1();
        System.out.println(resultVal);
        StringBuffer resultRef = testfinally2();
        System.out.println(resultRef);
    }}

运行结果为
控制台输出:
execute finally1
2
execute finally2
HelloWorld

       分析: 通过 Javap 分析生成的字节码可以知道,在正常代码块执行完成后,会将需要返回的值 result=2 存储到单独的局部变量中,而finally操作的局部变量仍是返回值最初存入的局部变量 这时那个 result=3 。由于返回值 result=2 做了备份,因此,如果是修改基础类型,不会对方法的返回值造成影响;如果是修改引用类型,会修改程序的返回结果。

日志

  • 记录应用系统日志主要有三个原因:记录操作轨迹、监控系统运行状况、回溯系统故障。
  • 代码规约推荐日志文件至少保存 15 天。可以根据日志文件的重要程度、文件大小 及 磁盘空间 再自行延长保存时间。
  • 记录异常时一定要输出异常堆栈,例如 logger.error(“xxx”+e.getMessage(),e)。
  • 日志框架分为三大部分,包括 日志门面、日志适配器 和 日志库。
  • 日志是有级别的。针对不同场景,日志被分为 五种 不同的级别,按照重要程度 由低到高 排序:
    (1)DEBUG 级别:记录对调试程序有帮助的信息。
    (2)INFO 级别:用来记录程序运行现场,虽然此处并未发生错误,但是对排查其他错误具有指导意义。

🙌 生产环境禁止输出 DEBUG 日志 且有选择地输出 INFO 日志。


(3)WARN 级别:也可以用来记录程序运行现场,但是更偏向于表明此处有出现潜在错误的可能。
(4)ERROR 级别:表明当前程序运行发生了错误,需要被关注。但是当前发生的错误,没有影响系统的继续运行。


🙌 ERROR 级别只记录系统逻辑错误、异常 或者 违反重要的业务规则,其他错误都可以归为 WARN 级别。


(5)FATAL 级别:表明当前程序运行出现了严重的错误事件,并且将会导致应用程序中断 。
       

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值