JKD源碼分析之AutoCloseable

AutoCloseable.java
通過測試案例分析AutoCloseable功能,測試代碼如下
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

import java.io.IOException;

public class AutoCloseableTest {

    private static final Logger log = LogManager.getLogger(AutoCloseableTest.class);

    /**
     * try-with-resource 寫法
     * 問題1:實現AutoCloseable接口
     * 如果方法沒有處理異常而是將異常拋出,由調用者處理,那麼
     * 調用者捕獲的是最先異常的地方(RuntimeException),同時也會指出抑制的異常信息(Suppressed: java.io.IOException)
     * 異常信息:
     * java.lang.RuntimeException: Resource 邏輯執行異常。。。
     *
     * 	at Resource.doSomething(AutoCloseableTest.java:123)
     * 	......
     * 	Suppressed: java.io.IOException: Resource 關閉資源出現異常。。。
     * 		at Resource.close(AutoCloseableTest.java:131)
     * 		at AutoCloseableTest.newStyle(AutoCloseableTest.java:23)
     * 		... 22 more
     *
     * @throws Exception
     */
    @Test
    public void newStyle() throws Exception {
        try (Resource resource = new Resource();
             HttpConn httpConn = new HttpConn()) {
            resource.doSomething();
            httpConn.doSomething();
        }
    }

    /**
     * 總結:
     * 1. try-with-resource 寫法與 AutoCloseable 接口的使用;
     * 2. 資源關閉的順序與資源定義的順序相反,所以定義時需要考慮關閉的先後順序來決定定義的先後順序;
     * 3. 無論是否拋出異常,(資源必須存在,如果不存在就沒有所謂關閉)系統都會自動關閉實現AutoCloseable接口的資源
     * 4. 使用try-catch-resources結構,try-block塊拋出異常後先執行所有資源(try的()中聲明的)
     *    的close方法然後在執行catch裏面的代碼然後纔是finally.
     * @throws Exception
     */
    @Test
    public void newStyle1() {
        try (Resource resource = new Resource();
             HttpConn httpConn = new HttpConn()) {
            resource.doSomething();
            httpConn.doSomething();
        } catch (IOException e) {
            log.info("try-with-resource執行到IOException...");
            e.printStackTrace();
        } catch (Exception e) {
            log.info("try-with-resource執行到Exception...");
            e.printStackTrace();
        } finally {
            log.info("try-with-resource執行到finally塊...");
        }
    }

    /**
     * try-catch-finally 寫法
     * 問題2:
     * 如果方法沒有處理異常而是將異常拋出,由調用者處理,那麼
     * 本來代碼應該在 resource.doSomething();的時候拋出 RuntimeException,
     * 但是查看結果發現finally中拋出的異常IOException,這就導致根據異常信息定位不到準確的異常位置。
     * 執行結果:
     * [11:28:22:964] [INFO] - Resource1.init(AutoCloseableTest.java:200) - Resource1初始化。。。
     * [11:28:22:968] [INFO] - HttpConn1.init(AutoCloseableTest.java:235) - HttpConn1初始化。。。
     * [11:28:22:968] [INFO] - Resource1.doSomething(AutoCloseableTest.java:207) - Resource1邏輯執行。。。
     * [11:28:22:969] [INFO] - HttpConn1.close(AutoCloseableTest.java:249) - HttpConn1 資源關閉中。。。
     * [11:28:22:969] [INFO] - Resource1.close(AutoCloseableTest.java:214) - Resource1 資源關閉中。。。
     *
     * java.io.IOException: Resource1 關閉資源出現異常。。。
     *
     * 	at Resource1.close(AutoCloseableTest.java:216)
     * 	at AutoCloseableTest.oldStyle(AutoCloseableTest.java:75)
     * @throws Exception
     */
    @Test
    public void oldStyle() throws Exception {
        Resource1 resource = null;
        HttpConn1 httpConn = null;
        try {
            resource = new Resource1();
            httpConn = new HttpConn1();
            resource.doSomething();
            httpConn.doSomething();
        } finally {
            if (httpConn != null) {
                httpConn.close();
            }
            if (resource != null) {
                resource.close();
            }
        }
    }

    /**
     * try-catch-finally 寫法
     * 弊端:可能忘了關閉系統資源,或者關閉的順序不對,代碼不夠優雅等等
     */
    @Test
    public void oldStyle1() {
        Resource1 resource = null;
        HttpConn1 httpConn = null;
        try {
            resource = new Resource1();
            httpConn = new HttpConn1();
            resource.doSomething();
            httpConn.doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (httpConn != null) {
                try {
                    httpConn.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (resource != null) {
                try {
                    resource.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


class Resource implements AutoCloseable {

    private static final Logger log = LogManager.getLogger(Resource.class);

    private boolean flag1 = false;
    private boolean flag2 = false;
    private boolean flag3 = false;

    public Resource() throws Exception {
        init();
    }

    public void init() throws Exception {
        log.info(this.getClass().getName() + "初始化。。。");
        if (flag1) {
            throw new Exception(this.getClass().getName() + " 初始化異常。。。");
        }
    }

    public void doSomething() throws RuntimeException {
        log.info(this.getClass().getName() + "邏輯執行。。。");
        if (flag2) {
            throw new RuntimeException(this.getClass().getName() + " 邏輯執行異常。。。");
        }
    }

    @Override
    public void close() throws IOException {
        log.info(this.getClass().getName() + " 資源關閉中。。。");
        if (flag3) {
            throw new IOException(this.getClass().getName() + " 關閉資源出現異常。。。");
        }
    }
}


class HttpConn implements AutoCloseable {

    private static final Logger log = LogManager.getLogger(HttpConn.class);

    private boolean flag1 = false;
    private boolean flag2 = false;
    private boolean flag3 = false;

    public HttpConn() throws Exception {
        init();
    }

    public void init() throws Exception {
        log.info(this.getClass().getName() + "初始化。。。");
        if (flag1) {
            throw new Exception(this.getClass().getName() + " 初始化異常。。。");
        }
    }

    public void doSomething() throws RuntimeException {
        log.info(this.getClass().getName() + "邏輯執行。。。");
        if (flag2) {
            throw new RuntimeException(this.getClass().getName() + " 邏輯執行異常。。。");
        }
    }

    @Override
    public void close() throws IOException {
        log.info(this.getClass().getName() + " 資源關閉中。。。");
        if (flag3) {
            throw new IOException(this.getClass().getName() + " 關閉資源出現異常。。。");
        }
    }
}

class Resource1 {

    private static final Logger log = LogManager.getLogger(Resource1.class);

    private boolean flag1 = false;
    private boolean flag2 = false;
    private boolean flag3 = false;

    public Resource1() throws Exception {
        init();
    }

    public void init() throws Exception {
        log.info(this.getClass().getName() + "初始化。。。");
        if (flag1) {
            throw new Exception(this.getClass().getName() + " 初始化異常。。。");
        }
    }

    public void doSomething() throws RuntimeException {
        log.info(this.getClass().getName() + "邏輯執行。。。");
        if (flag2) {
            throw new RuntimeException(this.getClass().getName() + " 邏輯執行異常。。。");
        }
    }

    public void close() throws IOException {
        log.info(this.getClass().getName() + " 資源關閉中。。。");
        if (flag3) {
            throw new IOException(this.getClass().getName() + " 關閉資源出現異常。。。");
        }
    }
}


class HttpConn1 {

    private static final Logger log = LogManager.getLogger(HttpConn1.class);

    private boolean flag1 = false;
    private boolean flag2 = false;
    private boolean flag3 = false;

    public HttpConn1() throws Exception {
        init();
    }

    public void init() throws Exception {
        log.info(this.getClass().getName() + "初始化。。。");
        if (flag1) {
            throw new Exception(this.getClass().getName() + " 初始化異常。。。");
        }
    }

    public void doSomething() throws RuntimeException {
        log.info(this.getClass().getName() + "邏輯執行。。。");
        if (flag2) {
            throw new RuntimeException(this.getClass().getName() + " 邏輯執行異常。。。");
        }
    }

    public void close() throws IOException {
        log.info(this.getClass().getName() + " 資源關閉中。。。");
        if (flag3) {
            throw new IOException(this.getClass().getName() + " 關閉資源出現異常。。。");
        }
    }
}
說明:沒有提及的flag均爲false
1. 驗證問題1
   修改Resource的flag2和flag3爲true,執行newStyle()結果如下
[11:59:13:149] [INFO] - Resource.init(AutoCloseableTest.java:150) - Resource初始化。。。
[11:59:13:154] [INFO] - HttpConn.init(AutoCloseableTest.java:186) - HttpConn初始化。。。
[11:59:13:154] [INFO] - Resource.doSomething(AutoCloseableTest.java:157) - Resource邏輯執行。。。
[11:59:13:154] [INFO] - HttpConn.close(AutoCloseableTest.java:201) - HttpConn 資源關閉中。。。
[11:59:13:154] [INFO] - Resource.close(AutoCloseableTest.java:165) - Resource 資源關閉中。。。

java.lang.RuntimeException: Resource 邏輯執行異常。。。

	at Resource.doSomething(AutoCloseableTest.java:159)
	at AutoCloseableTest.newStyle(AutoCloseableTest.java:32)
	Suppressed: java.io.IOException: HttpConn 關閉資源出現異常。。。
		at HttpConn.close(AutoCloseableTest.java:203)
		at AutoCloseableTest.newStyle(AutoCloseableTest.java:34)
		... 22 more
	Suppressed: java.io.IOException: Resource 關閉資源出現異常。。。
		at Resource.close(AutoCloseableTest.java:167)
		at AutoCloseableTest.newStyle(AutoCloseableTest.java:34)
		... 22 more
2. 驗證:資源關閉的順序與資源定義的順序相反,所以定義時需要考慮關閉的先後順序來決定定義的先後順序;
    修改Resource和HttpConf的flag均爲false,執行 newStyle1() 結果如下
[12:03:22:601] [INFO] - Resource.init(AutoCloseableTest.java:150) - Resource初始化。。。
[12:03:22:606] [INFO] - HttpConn.init(AutoCloseableTest.java:186) - HttpConn初始化。。。
[12:03:22:606] [INFO] - Resource.doSomething(AutoCloseableTest.java:157) - Resource邏輯執行。。。
[12:03:22:606] [INFO] - HttpConn.doSomething(AutoCloseableTest.java:193) - HttpConn邏輯執行。。。
[12:03:22:606] [INFO] - HttpConn.close(AutoCloseableTest.java:201) - HttpConn 資源關閉中。。。
[12:03:22:607] [INFO] - Resource.close(AutoCloseableTest.java:165) - Resource 資源關閉中。。。
[12:03:22:607] [INFO] - AutoCloseableTest.newStyle1(AutoCloseableTest.java:59) - try-with-resource執行到finally塊...
3. 驗證:無論是否拋出異常,(資源必須存在,如果不存在就沒有所謂關閉)系統都會自動關閉實現AutoCloseable接口的資源
    1)修改Resource的flag1=true,執行newStyle1() 結果如下
        注意沒有關閉資源的操作
java.lang.Exception: Resource 初始化異常。。。
	at Resource.init(AutoCloseableTest.java:152)
	at Resource.<init>(AutoCloseableTest.java:146)
	at AutoCloseableTest.newStyle1(AutoCloseableTest.java:48)
[12:06:37:896] [INFO] - Resource.init(AutoCloseableTest.java:150) - Resource初始化。。。
[12:06:37:900] [INFO] - AutoCloseableTest.newStyle1(AutoCloseableTest.java:56) - try-with-resource執行到Exception...
[12:06:37:901] [INFO] - AutoCloseableTest.newStyle1(AutoCloseableTest.java:59) - try-with-resource執行到finally塊...
    2)修改Resource的flag1=false,修改HttpConf的flag1=true,執行newStyle1() 結果如下
    注意這裏只有關閉Resource的操作,並且可以驗證ry-block出現異常時的執行順序
[12:08:32:258] [INFO] - Resource.init(AutoCloseableTest.java:150) - Resource初始化。。。
[12:08:32:262] [INFO] - HttpConn.init(AutoCloseableTest.java:186) - HttpConn初始化。。。
[12:08:32:263] [INFO] - Resource.close(AutoCloseableTest.java:165) - Resource 資源關閉中。。。
[12:08:32:263] [INFO] - AutoCloseableTest.newStyle1(AutoCloseableTest.java:56) - try-with-resource執行到Exception...
java.lang.Exception: HttpConn 初始化異常。。。
	at HttpConn.init(AutoCloseableTest.java:188)
	at HttpConn.<init>(AutoCloseableTest.java:182)
	at AutoCloseableTest.newStyle1(AutoCloseableTest.java:49)
[12:08:32:264] [INFO] - AutoCloseableTest.newStyle1(AutoCloseableTest.java:59) - try-with-resource執行到finally塊...
4. 驗證try-catch-finally的異常抑制問題
    修改 Resource1的flag2和flag3爲true,執行oldStyle() 結果如下
    本應該出現 RuntimeException,但是隻拋出了IOException,這就是異常抑制問題。
[14:29:57:603] [INFO] - Resource1.init(AutoCloseableTest.java:221) - Resource1初始化。。。
[14:29:57:607] [INFO] - HttpConn1.init(AutoCloseableTest.java:256) - HttpConn1初始化。。。
[14:29:57:608] [INFO] - Resource1.doSomething(AutoCloseableTest.java:228) - Resource1邏輯執行。。。
[14:29:57:608] [INFO] - HttpConn1.close(AutoCloseableTest.java:270) - HttpConn1 資源關閉中。。。
[14:29:57:608] [INFO] - Resource1.close(AutoCloseableTest.java:235) - Resource1 資源關閉中。。。

java.io.IOException: Resource1 關閉資源出現異常。。。

	at Resource1.close(AutoCloseableTest.java:237)
	at AutoCloseableTest.oldStyle(AutoCloseableTest.java:96)
如有問題,請留言指正!謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章