Как не повторять код в блоках catch?
Мне сложно не повторять себя в Java-программе, над которой я сейчас работаю.
Скажем, мне нужно объявить множество методов, которые в основном структурированы следующим образом:
public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
EntityManager em = this.createEntityManager();
EntityTransaction tx = null;
try {
/*
* ... independent logic ...
*/
tx = em.getTransaction();
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
return something;
}
Тело метода всех методов должно содержать тезисы элементов управления ресурсами.
Сама "независимая логика" будет довольно сложной, поэтому постановка инструкции try/catch отдельным методом не будет работать.
Я хочу избежать повторения этого кода. Каковы наилучшие методы применения в этих ситуациях?
Ответы
Ответ 1
Создайте интерфейс:
public interface EntityManagerAction {
public void execute(EntityManager em);
}
И класс утилиты:
public class EntityUtil {
public static void executeWithEntityManager(EntityManagerAction action) {
EntityManager em = someHowCreateEntityManager();
EntityTransaction tx = null;
try {
action.execute(em);
tx = em.getTransaction();
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
}
}
Теперь вы можете повторно использовать плиту котла в классе EntityUtil, и ваш код будет выглядеть следующим образом:
public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
Something something;
EntityUtil.executeWithEntityManager(new EntityManagerAction() {
public void execute(EntityManager em ) {
/*
* ... independent logic ...
*/
//use the passed in 'em' here.
}
});
return something;
}
См. также Что такое "Выполнить круг" " идиома?
Ответ 2
Если все ваши предложения finally
используются для закрытия Stream
и таких (что-либо, что реализует AutoCloseable
), вы можете использовать try-with-resources
(как предложено в одном из комментариев), чтобы избавиться от finally
.
Однако, если вам нужно более общее решение и иметь один и тот же тип Exception
и один и тот же тип обработки в предложении finally
, вы можете создать абстрактный класс, например:
abstract class SensitiveBlockHandler {
public void handle() {
try {
doHandling();
} catch (SomeException | AnotherException e) {
// TODO: handle exceptions here ...
} finally {
// TODO: cleanup here ...
}
}
protected abstract void doHandling();
}
Затем вы можете создавать внутренние классы для обработки различных ситуаций, либо как анонимные классы, либо нет. Код должен выглядеть примерно так:
public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
new SensitiveBlockHandler() {
protected void doHandling() {
/*
* ... independent logic ...
*/
}
}.handle();
return something;
}
Ответ 3
Я бы создал абстракцию независимой логики, скажем Job
, а doSomething()
станет processJob()
в классе Service
. Вы будете называть ваш processJob()
для каждой обработки, и весь код из вашего примера, кроме independent logic
, будет написан ровно один раз.
Изменить:. Что такое предложение, предлагаемое в комментарии: Что такое "Выполнить Around" , идиома?
Ответ 4
Недавно мы столкнулись с такой проблемой и решили пойти с шаблоном callback.
Итак, если все методы имеют похожий код, и это просто независимая логика, которая отличается, вы можете создать интерфейс, реализация которого обрабатывает независимый код. Так что-то вроде этого:
public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
return (SomeEntity)callbackMethod(someAttribute, anotherAttribute, new IndependentCodeInterfaceImpl1());
}
public SomeOtherEntity doSomethingElse (String someAttribute, String anotherAttribute) {
return (SomeOtherEntity)callbackMethod(someAttribute, anotherAttribute, new IndependentCodeInterfaceImpl2());
}
private Object callbackMethod(String someAttribute, String anotherAttribute, IndependentCodeInterface independent) {
EntityManager em = this.createEntityManager();
EntityTransaction tx = null;
Object response = null;
try {
response = independent.execute(someAttribute, anotherAttribute, em);
tx = em.getTransaction();
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
return response;
}
Ответ 5
Если вы используете сервер приложений JavaEE, значительно упростите свой код, используя сеанс без состояния bean:
@Stateless
public class SomethingFactory {
@PersistenceContext
private EntityManager em;
public SomeEntity doSomething (String someAttribute, String anotherAttribute) {
/*
* ... independent logic ...
*/
return something;
}
}
Контейнер будет следить за всей семантикой управления транзакциями.
Ответ 6
вы можете сделать, чтобы ваши сигнатуры метода возвращали исключение
public SomeEntity doSomething (String someAttribute, String anotherAttribute) throws RuntimeException {
// your independent logic
}
public SomeEntity doSomethingElse (String someAttribute, String anotherAttribute) throws RuntimeException {
// your independent logic
}
public SomeEntity doSomethingDifferent (String someAttribute, String anotherAttribute) throws RuntimeException {
// your independent logic
}
то вы можете обрабатывать его по отдельному методу:
public String yourHandleMethod(){
String something = "";
EntityManager em = this.createEntityManager();
EntityTransaction tx = null;
try{
doSomething();
doSomethingElse();
doSomethingDifferent();
tx = em.getTransaction();
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
return something;
Ответ 7
Вы можете реализовать механизм, похожий на Spring Шаблон транзакции.
Сначала реализуем обратный вызов, который будет реализован для каждой бизнес-операции, предоставляя как диспетчер сущностей, так и транзакцию:
public static interface TransactionCallback<R> {
R doInTransaction(EntityManager em, EntityTransaction tx);
}
Затем создайте общий метод с шаблоном кода:
public <T> T execute(TransactionCallback<T> callback) {
EntityManager em = this.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
return callback.doInTransaction(em, tx);
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
}
Наконец, вы можете создать бизнес-логику, подобную этой:
public SomeEntity doSomething(String someAttribute, String anotherAttribute) {
return execute(new TransactionCallback<SomeEntity>() {
@Override
public SomeEntity doInTransaction(EntityManager em, EntityTransaction tx) {
// do something here
}
});
}
Этот подход имеет несколько преимуществ. Он чист, и вы можете перехватывать все действия в одном месте - например: добавить журнал и т.д.
Прошу прощения за любые синтаксические ошибки, я пишу это без IDE. Но у вас есть идея.
EDIT: возможно, он может быть короче, используя lambdas в JDK 8, потому что TransactionCallback может быть функциональным интерфейсом:).
Ответ 8
Для полноты - там Шаблон метода шаблона. Это позволяет вам абстрагировать весь код управления из процедуры и просто реализовать основную функцию. Этот шаблон особенно полезен, когда у вас есть множество мета-требований, таких как закрытие материала при правильном обращении с исключениями.
Я использую этот шаблон для, помимо прочего, запуска запросов к базе данных из-за всех правил о том, чтобы закрыть все ResultSet
и Connection
при правильном управлении PreparedStatement
s.
abstract class DoSomethingWithEntity {
public SomeEntity doSomething(String someAttribute, String anotherAttribute) {
SomeEntity something;
EntityManager em = createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
// Call the abstract stuff that can be different.
something = doIt(em, tx);
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
return something;
}
// The bit you want to write yourself. Make it abstract so you have to write it.
abstract SomeEntity doIt(EntityManager em, EntityTransaction tx) throws Exception;
}
public void test() {
SomeEntity e = new DoSomethingWithEntity() {
@Override
SomeEntity doIt(EntityManager em, EntityTransaction tx) {
// Do your stuff here.
return new SomeEntity();
}
}.doSomething("someAttribute", "anotherAttribute");
}
Обратите внимание, что вам не нужно делать все исключения в RuntimeException
- вы можете явно определить, какие исключения разрешены.
Ответ 9
FYI - новая функция лямбда-выражений в JDK 1.8 теперь является самым элегантным способом решения этой проблемы. Ваш метод DoSomething будет примерно таким:
DataExecutor.execute(() -> {
// let say your "independent logic" is the following three statements
statementA;
statementB;
statementC;
});
Ваш метод DataExecutor.execute будет выглядеть примерно так (вам может понадобиться тип возврата):
public static void execute(work)
{
EntityManager em = this.createEntityManager();
EntityTransaction tx = null;
try {
// this is the "independent logic" you passed in via the "work" lambda
work.doWork();
tx = em.getTransaction();
} catch (RuntimeException e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
И, наконец, вам нужно определить функциональный интерфейс (или использовать существующий):
@FunctionalInterface
public interface DataFuncVoid {
public abstract void doWork();
}