关于java:java安全编码指南之异常处理

36次阅读

共计 5419 个字符,预计需要花费 14 分钟才能阅读完成。

简介

异样是 java 程序员无奈防止的一个话题,咱们会有 JVM 本人的异样也有应用程序的异样,对于不同的异样,咱们的解决准则是不是一样的呢?

一起来看看吧。

异样简介

先上个图,看一下常见的几个异样类型。

所有的异样都来自于 Throwable。Throwable 有两个子类,Error 和 Exception。

Error 通常示意的是严重错误,这些谬误是不倡议被 catch 的。

留神这里有一个例外,比方 ThreadDeath 也是继承自 Error,然而它示意的是线程的死亡,尽管不是重大的异样,然而因为应用程序通常不会对这种异样进行 catch,所以也归类到 Error 中。

Exception 示意的是应用程序心愿 catch 住的异样。

在 Exception 中有一个很特地的异样叫做 RuntimeException。RuntimeException 叫做运行时异样,是不须要被显示 catch 住的,所以也叫做 unchecked Exception。而其余非 RuntimeException 的 Exception 则须要显示 try catch, 所以也叫做 checked Exception。

不要疏忽 checked exceptions

咱们晓得 checked exceptions 是肯定要被捕捉的异样,咱们在捕捉异样之后通常有两种解决形式。

第一种就是依照业务逻辑解决异样,第二种就是自身并不解决异样,然而将异样再次抛出,由下层代码来解决。

如果捕捉了,然而不解决,那么就是疏忽 checked exceptions。

接下来咱们来考虑一下 java 中线程的中断异样。

java 中有三个十分类似的办法 interrupt,interrupted 和 isInterrupted。

isInterrupted() 只会判断是否被中断,而不会革除中断状态。

interrupted() 是一个类办法,调用 isInterrupted(true) 判断的是以后线程是否被中断。并且会革除中断状态。

后面两个是判断是否中断的办法,而 interrupt()就是真正触发中断的办法。

它的工作要点有上面 4 点:

  1. 如果以后线程实例在调用 Object 类的 wait(),wait(long)或 wait(long,int)办法或 join(),join(long),join(long,int)办法,或者在该实例中调用了 Thread.sleep(long)或 Thread.sleep(long,int)办法,并且正在阻塞状态中时,则其中断状态将被革除,并将收到 InterruptedException。
  2. 如果此线程在 InterruptibleChannel 上的 I / O 操作中处于被阻塞状态,则该 channel 将被敞开,该线程的中断状态将被设置为 true,并且该线程将收到 java.nio.channels.ClosedByInterruptException 异样。
  3. 如果此线程在 java.nio.channels.Selector 中处于被被阻塞状态,则将设置该线程的中断状态为 true,并且它将立刻从 select 操作中返回。
  4. 如果下面的状况都不成立,则设置中断状态为 true。

看上面的例子:

    public void wrongInterrupted(){
        try{Thread.sleep(1000);
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }

下面代码中咱们捕捉了一个 InterruptedException,然而咱们仅仅是打印出了异样信息,并没有做任何操作。这样程序的体现和没有发送一异样一样,很显著是有问题的。

依据下面的介绍,咱们晓得,interrupted() 办法会革除中断状态,所以,如果咱们本身解决不了异样的状况下,须要从新调用 Thread.currentThread().interrupt() 从新抛出中断,由下层代码负责解决,如下所示。

    public void correctInterrupted(){
        try{Thread.sleep(1000);
        } catch (InterruptedException e) {Thread.currentThread().interrupt();}
    }

不要在异样中裸露敏感信息

遇到异样的时候,通常咱们须要进行肯定水平的日志输入,从而来定位异样。然而咱们在做日志输入的时候,肯定要留神不要裸露敏感信息。

下表能够看到异样信息可能会裸露的敏感信息:

除了敏感信息之外,咱们还要做好日志信息的平安爱护。

在解决捕捉的异样时,须要复原对象的初始状态

如果咱们在解决异样的时候,批改了对象中某些字段的状态,在捕捉异样的时候须要怎么解决呢?

    private int age=30;

    public void wrongRestore(){
        try{
            age=20;
            throw new IllegalStateException("custom exception!");
        }catch (IllegalStateException e){System.out.println("we do nothing");
        }
    }

下面的例子中,咱们将 age 重置为 20,而后抛出了异样。尽管抛出了异样,然而咱们并没有重置 age,最初导致 age 最终被批改了。

整个 restore 的逻辑没有处理完毕,然而咱们局部批改了对象的数据,这是很危险的。

实际上,咱们须要一个重置:

    public void rightRestore(){
        try{
            age=20;
            throw new IllegalStateException("custom exception!");
        }catch (IllegalStateException e){System.out.println("we do nothing");
            age=30;
        }
    }

不要手动实现 finally block

咱们在应用 try-finally 和 try-catch-finally 语句时,肯定不要在 finally block 中应用 return, break, continue 或者 throw 语句。

为什么呢?

依据 Java Language Specification(JLS)的阐明,finally block 肯定会被执行,不论 try 语句中是否抛出异样。

在 try-finally 和 try-catch-finally 语句中,如果 try 语句中抛出了异样 R,而后 finally block 被执行,这时候有两种状况:

  • 如果 finally block 失常执行,那么 try 语句被终止的起因是异样 R。
  • 如果在 finally block 中抛出了异样 S,那么 try 语句被终止的起因将会变成 S。

咱们举个例子:

public class FinallyUsage {public boolean wrongFinally(){
        try{throw new IllegalStateException("my exception!");
        }finally {System.out.println("Code comes to here!");
            return true;
        }
    }

    public boolean rightFinally(){
        try{throw new IllegalStateException("my exception!");
        }finally {System.out.println("Code comes to here!");
        }
    }

    public static void main(String[] args) {FinallyUsage finallyUsage=new FinallyUsage();
        finallyUsage.wrongFinally();
        finallyUsage.rightFinally();}
}

下面的例子中,咱们定义了两个办法,一个办法中咱们在 finally 中间接 return, 另一办法中,咱们让 finally 失常执行结束。

最终,咱们能够看到 wrongFinally 将异样暗藏了,而 rightFinally 保留了 try 的异样。

同样的,如果咱们在 finally block 中抛出了异样,咱们肯定要记得对其进行捕捉,否则将会暗藏 try block 中的异样信息。

不要捕捉 NullPointerException 和它的父类异样

通常来说 NullPointerException 示意程序代码有逻辑谬误,是须要程序员来进行代码逻辑批改,从而进行修复的。

比如说加上一个 null check。

不捕捉 NullPointerException 的起因有三个。

  1. 应用 null check 的开销要远远小于异样捕捉的开销。
  2. 如果在 try block 中有多个可能抛出 NullPointerException 的语句,咱们很难定位到具体的谬误语句。
  3. 最初,如果产生了 NullPointerException,程序基本上不可能失常运行或者复原,所以咱们须要提前进行 null check 的判断。

同样的,程序也不要对 NullPointerException 的父类 RuntimeException, Exception, or Throwable 进行捕获。

不要 throw RuntimeException, Exception, or Throwable

咱们抛出异样次要是为了可能找到精确的解决异样的办法,如果间接抛出 RuntimeException, Exception, 或者 Throwable 就会导致程序无奈精确解决特定的异样。

通常来说咱们须要自定义 RuntimeException, Exception, 或者 Throwable 的子类,通过具体的子类来辨别具体的异样类型。

不要抛出未声明的 checked Exception

一般来说 checked Exception 是须要显示 catch 住,或者在调用办法上应用 throws 做申明的。

然而咱们能够通过某些伎俩来绕过这种限度,从而在应用 checked Exception 的时候不须要恪守上述规定。

当然这样做是须要防止的。咱们看一个例子:

    private static Throwable throwable;

    private ThrowException() throws Throwable {throw throwable;}

    public static synchronized void undeclaredThrow(Throwable throwable) {

        ThrowException.throwable = throwable;
        try {ThrowException.class.newInstance();
            } catch (InstantiationException e) {} catch (IllegalAccessException e) { } finally {ThrowException.throwable = null;}
    }

下面的例子中,咱们定义了一个 ThrowException 的 private 构造函数,这个构造函数会 throw 一个 throwable, 这个 throwable 是从办法传入的。

在 undeclaredThrow 办法中,咱们调用了 ThrowException.class.newInstance() 实例化一个 ThrowException 实例,因为须要调用构造函数,所以会抛出传入的 throwable。

因为 Exception 是 throwable 的子类,如果咱们在调用的时候传入一个 checked Exception,很显著,咱们的代码并没有对其进行捕捉:

    public static void main(String[] args) {
        ThrowException.undeclaredThrow(new Exception("Any checked exception"));
    }

怎么解决这个问题呢?换个思路,咱们能够应用 Constructor.newInstance() 来代替 class.newInstance()。

try {
        Constructor constructor =
                    ThrowException.class.getConstructor(new Class<?>[0]);
            constructor.newInstance();} catch (InstantiationException e) {} catch (InvocationTargetException e) {System.out.println("catch exception!");
        } catch (NoSuchMethodException e) {} catch (IllegalAccessException e) { } finally {ThrowException.throwable = null;}

下面的例子,咱们应用 Constructor 的 newInstance 办法来创建对象的实例。和 class.newInstance 不同的是,这个办法会抛出 InvocationTargetException 异样,并且把所有的异样都封装进去。

所以,这次咱们取得了一个 checked Exception。

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-exception/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0