11

AutoCloseableJava 7 の try-with-resources ステートメントで動作する を実装するときに、try ブロック内に例外があったかどうかを知りたいです。例えば:

class C implements AutoCloseable {
    @Override
    public void close() {
        if (exceptionOccurred)
            something();
        else
            somethingElse();
    }
}

これを説明するには:

try (C c = new C()) {

    // This should cause a call to "something()"
    if (something)
        throw new RuntimeException();

    // This should cause a call to "somethingElse()"
    else
        ;
}

さて、try-with-resources ステートメントが bytecodeにどのように変換されるかを理解すると、それは不可能だと思います。しかし、インストルメンテーション/リフレクション/文書化されていないコンパイラ機能を使用してRuntimeException、内部から上記にアクセスできる(信頼できる!)トリックはありますAutoCloseable.close()か?

注: 私は API 設計者であり、API コンシューマのリソースの試行コードを制御することはできません。したがって、実装はAutoCloseableサイトで行う必要があります

4

2 に答える 2

10

これを行う通常の方法は、try ブロックの最後で明示的に呼び出しを行うことです。例えば:

try (CustomTransaction transaction = ...) {
    // Do something which might throw an exception...

    transaction.commitOnClose();
}

次に、トランザクションが呼び出さcloseれたかどうかに基づいて、トランザクションを中止するかコミットします。commitOnClose()

自動ではありませんが、達成するのは非常に簡単で、読むのも非常に簡単です。

于 2014-01-25T17:14:28.270 に答える
2

私はしばらくこれに苦労してきました。Jon Skeet の回答は嫌いです。なぜなら、開発者 (つまり私) が誤ってcommitOnClose(). 開発者がコードのブロックを離れたときに、commit() または rollback() のいずれかを強制的に呼び出す方法が必要です。

ラムダの例外とチェック例外はうまく連携しないため、適切な解決策を見つけるには少し戸惑いましたが、最終的に私と私の同僚は、次のような作業を可能にするコードを思いつきました:

TransactionEnforcer.DbResult<String> result = transactionEnforcer.execute(db -> {
  try {
    db.someFunctionThatThrowsACheckedException();
  } catch (TheException e) {
    return failure("fallback value");
  }
  return success(db.getAFancyValue());
});

result.ifPresent(v -> System.out.println(v));

値を返す方法、コードが成功したかどうかを確認する方法、および Java の codepath 戻り値チェックにより、コードをコミットする必要があるかどうかを常に明示するように強制されることに注意してください。

以下のコードを使用して実装されます。

package nl.knaw.huygens.timbuctoo.database;

import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

public class TransactionEnforcer {
  private final Supplier<DbClass> dbClassFactory;

  public TransactionEnforcer(Supplier<DbClass> dbClassFactory) {
    this.dbClassFactory = dbClassFactory;
  }

  public <U> DbResult<U> execute(Function<DbClass, DbResult<U>> actions) {
    DbClass db = dbClassFactory.get();
    try {
      DbResult<U> result = actions.apply(db);
      if (result.isSuccess()) {
        db.close(true);
      } else {
        db.close(false);
      }
      return result;
    } catch (RuntimeException e) {
      db.close(false);
      throw e;
    }
  }

  public static class DbResult<T> {
    private final Optional<T> value;
    private final boolean success;

    private DbResult(T value, boolean success) {
      this.value = Optional.of(value);
      this.success = success;
    }

    public static <T> DbResult<T> success(T value) {
      return new DbResult<T>(value, true);
    }

    public static <T> DbResult<T> success() {
      return new DbResult<T>(null, true);
    }

    public static <T> DbResult<T> failure(T value) {
      return new DbResult<T>(value, false);
    }

    public static <T> DbResult<T> failure() {
      return new DbResult<T>(null, false);
    }

    public boolean isSuccess() {
      return success;
    }

    public Optional<T> getValue() {
      return value;
    }
  }
}

(DbClass は演習として読者に任せます)

于 2016-09-23T13:23:27.277 に答える