简介
程序员必定是不缺对象的,因为随时都能够构建一个,对象多了必定会呈现点平安问题,一起来看看在java的对象构建中怎么保障对象的安全性吧。
构造函数的异样
思考上面的一个例子:
public class SensitiveOperation { public SensitiveOperation(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); }}
下面的例子中,咱们在构造函数中做了一个securityCheck,因为这个securityCheck返回的值是false,所以会抛出SecurityException。
看下调用的例子:
public static void main(String[] args) { SensitiveOperation sensitiveOperation = new SensitiveOperation(); sensitiveOperation.storeMoney(); }
这个调用会抛出上面的异样:
Exception in thread "main" java.lang.SecurityException: Security check failed! at com.flydean.SensitiveOperation.<init>(SensitiveOperation.java:11) at com.flydean.SensitiveUsage.main(SensitiveUsage.java:10)
那么问题来了,下面的这个class是不是平安的呢?
Finalizer Attack
下面的class不是final的,所以咱们能够结构一个class去继承它。而后思考这样一个问题,当构造函数抛出异样之后,会执行什么操作呢?
如果该对象曾经被构建了,那么这个对象在GC的时候须要执行finalize办法。那么咱们是不是能够在finalize办法中绕过安全检查呢?
看上面的例子:
public class SensitiveOperationFinalizer extends SensitiveOperation{ public SensitiveOperationFinalizer(){ } @Override protected void finalize() { System.out.println("We can still do store Money action!"); this.storeMoney(); System.exit(0); }}
上的例子中,咱们继承了SensitiveOperation,并且实现了finalize办法,在finalize中,咱们调用了storeMoney。看下运行的代码:
public void testFinalizer() throws InterruptedException { try { SensitiveOperation sensitiveOperation = new SensitiveOperationFinalizer(); sensitiveOperation.storeMoney(); }catch (Exception e){ System.out.println(e.getMessage()); } System.gc(); Thread.sleep(10000); }
运行后果:
Security check failed!We can still do store Money action!Store 1000000 RMB!
能够看到,尽管咱们构造函数抛出了异样,然而storeMoney的操作还是被执行了!
这个操作就叫做Finalizer Attack。
解决Finalizer Attack
怎么解决这个构造函数抛出异样的问题呢?这里给大家介绍几种解决办法。
应用final class
如果应用final class,那么类是不可能被继承的,问题天然就解决了。
public final class SensitiveOperationFinal { public SensitiveOperationFinal(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); }}
应用final finalize办法
因为子类想要重写finalize办法,如果咱们的父类中finalize办法定义为final,也能够解决这个问题。
public final class SensitiveOperationFinal { public SensitiveOperationFinal(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); } final protected void finalize() { }}
应用flag变量
咱们能够在对象构建结束的时候设置一个flag变量,而后在每次平安操作的时候都去判断一下这个flag变量,这样也能够防止之前提到的问题:
public class SensitiveOperationFlag { private volatile boolean flag= false; public SensitiveOperationFlag(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } flag=true; } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ if(!flag){ System.out.println("Object is not initiated yet!"); return; } System.out.println("Store 1000000 RMB!"); }}
留神,这里flag须要设置为volatile,只有这样能力保障构造函数在flag设置之前执行。也就是说须要保障happens-before个性。
应用this或者super
在JDK6或者更高版本中,如果对象的构造函数在java.lang.Object构造函数退出之前引发异样,则JVM将不会执行该对象的finalize办法。
因为Java确保java.lang.Object构造函数在任何构造函数的第一条语句之上或之前执行。如果构造函数中的第一个语句是对超类的构造函数或同一个类中的另一个构造函数的调用,则java.lang.Object构造函数将在该调用中的某个地位执行。否则,Java将在该构造函数的代码中的任何一个执行之前执行超类的默认构造函数,并且将通过隐式调用执行java.lang.Object构造函数。
也就是说如果异样产生在构造函数中的第一条this或者super中的时候,JVM将不会调用对象的finalize办法:
public class SensitiveOperationThis { public SensitiveOperationThis(){ this(doSecurityCheck()); } private SensitiveOperationThis(boolean secure) { } //Security check return false private static boolean doSecurityCheck(){ throw new SecurityException("Security check failed!"); } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); }}
本文的例子:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-object/最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!