2014-01-25 7 views
4

При реализации AutoCloseable для работы с оператором try-with-resources Java 7 я хотел бы знать, было ли исключение в блоке try. Например .:Может ли моя реализация AutoCloseable.close() обнаружить потенциальное исключение?

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 
     ; 
} 

Теперь, от понимания how the try-with-resources statement translates to bytecode, я предполагаю, что это не возможно. Но есть ли какой-либо (надежный!) Трюк с помощью инструментария/отражение/некоторая недокументированная функция компилятора, которая позволяет мне получить доступ к приведенному выше RuntimeException с точностью до AutoCloseable.close()?

Примечание: Я разработчик API, и я не могу контролировать код API-запросов потребителей. Таким образом, реализация должна быть выполнена на сайте AutoCloseable.

ответ

8

Обычный способ сделать это - просто явно сделать вызов в конце блока try. Например:

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

    transaction.commitOnClose(); 
} 

Тогда в close, вы либо прервать транзакцию или совершить его, основываясь на том, были ли назвать или не commitOnClose().

Это не автоматическая система, но ее довольно просто добиться - и ее очень просто читать.

+0

Я вижу, что вы правы в принципе. Но я разработчик API, и я не контролирую код API-запросов потребителей. Я должен добавить это к вопросу –

+1

Как вы догадались, что это действительно касается транзакций, кстати? :-) –

+0

@LukasEder: Если вы управляете API, вы можете сообщить своим пользователям API, что им нужно вызвать дополнительный метод. Это, вероятно, самый простой вариант для ваших потребителей, и на самом деле вы ничего не можете сделать с прямым методом 'close()', насколько мне известно. –

1

Я некоторое время боролся с этим. Мне не нравится ответ Джона Скита, потому что разработчик (то есть я) может случайно забыть позвонить commitOnClose(). Я хочу, чтобы разработчик был вынужден вызвать commit() или rollback(), когда он покидает блок кода.

Lambda и проверил исключения не играют хорошо вместе, чтобы правильное решение приняло немного озадачивает, но в конце концов я и Коллега моего придумали кусок кода, который позволяет работать так:

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 гарантирует, что вы всегда будете явно о том, должен ли код быть совершен или нет.

Это осуществляется с помощью кода ниже:

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 в качестве упражнения для читателя)

+0

Несомненно. Мы сделали это тоже, в конце концов, например. в [jOOQ 'метод transactionResult()') (http://www.jooq.org/javadoc/latest/org/jooq/DSLContext.html#transactionResult-org.jooq.TransactionalCallable-), который выполняет функциональный интерфейс в контекст одноразовой транзакции. Это нормально, если Java-идиомы желательны на сайте вызова. Однако конкретный вопрос касался использования Java-ресурсов try-with-resources. –

+1

ОК. Причина, по которой я опубликовал этот пример, заключалась в том, что я столкнулся с этим вопросом, пока у меня была такая же проблема, как у вас (как можно прочитать в комментариях к ответу Джона Скетса). Вы упоминаете использование Lambda/SAM, но мне потребовалось некоторое время, чтобы придумать с решением, которое сработало. Поэтому для всех, кто приходит к этому вопросу с одним и тем же вариантом использования, может быть приятно получить решение, которое им поможет. (Но если вы считаете, что это оффтоп, я снова удалю его) – Jauco

+0

Нет, не от темы вообще. Это хороший ответ, большое спасибо за обмен. Просто объяснил, почему я не собираюсь это принимать :) –