如何不在 catch 块中重复代码?
Posted
技术标签:
【中文标题】如何不在 catch 块中重复代码?【英文标题】:How to not repeat code within catch blocks? 【发布时间】:2014-10-05 05:22:27 【问题描述】:我很难不在我目前正在开发的 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 语句放在单独的方法中实际上是行不通的。
我想避免重复这段代码。在这些情况下应用的最佳做法是什么?
【问题讨论】:
也许 try-with-resources 可以帮助你 -- docs.oracle.com/javase/tutorial/essential/exceptions/… 这是“执行周围”模式可以提供帮助的地方:***.com/questions/341971/… 嘿@AndersR.Bystrup,非常感谢。我不得不承认我不知道,try-with-resources 存在。不幸的是javax.persistence.EntityManager
没有实现AutoClosable
。
然后将其包装到实现AutoClosable
的对象中:)。 IE。您的createEntitiyManager
方法返回ClosableEntitiyManager
,它是实现AutoClosable
的EntityManager
的包装,您就完成了。
解决这个问题的另一种方法是使用 Spring。您声明了一个带有@Transactional
注释的方法,所有事务工作都为您完成。
【参考方案1】:
我会创建一个独立逻辑的抽象,比如Job
,而doSomething()
将在Service
类中变为processJob()
。您将在每次处理时调用您的processJob()
,并且除了independent logic
之外的示例中的所有代码都将只编写一次。
编辑:nos 在评论中建议:What is the "Execute Around" idiom?
【讨论】:
【参考方案2】:你可以让你的方法签名返回异常
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;
【讨论】:
嗨@Arno_Geismar,非常感谢!不过,问题实际上并不在于方法的顺序执行。【参考方案3】:如果您所有的finally
子句都用于关闭Stream
s 等(任何实现AutoCloseable
的东西),您可以使用try-with-resources
(如其中一个cmets 中所建议的那样)来摆脱@ 987654325@ 子句。
但是,如果您需要更通用的解决方案,并且在 finally
子句中捕获了相同类型的 Exception
s 和相同类型的处理,则可以创建一个抽象类,例如:
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;
【讨论】:
你如何看待有一个Exception
类型的局部变量,它最初为空,然后有一个 catch
块设置变量并重新抛出?然后finally
块可以执行应该无条件执行的逻辑,仅在succeed
情况下,并且仅在fail
情况下,以任何需要的顺序。请注意,编译器最终会复制 finally
块的代码,但至少不必在源代码中复制它。【参考方案4】:
创建接口:
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;
另见What is the "Execute Around" idiom?
【讨论】:
OK - 一个明显的问题...当任何异常发生时 tx 怎么会不为空???可能发生异常的最后一个地方是assignemnt em.getTransaction() 如果这个抛出,em 仍然为空,如果不是你的try-block 结束了o_O 是的 - 确实是这样 - 尽管这部分是从原始代码逐字记录的 - 所以 OP 必须将其构建成实际需要的结构。 值得注意的是,something
将无法从execute
中访问。
在 Java 8 中,您可以将 new EntityManagerAction() public void execute(EntityManager em) ...
替换为 (em) -> ...
,因为 EntityManagerAction 是一个函数式接口。或者,您可以将方法引用 this::doStuff
传递给它,其中 doStuff
看起来像 void doStuff(EntityManager em) ...
。【参考方案5】:
您可以实现类似于 Spring Transaction Template 的机制。
首先,实现一个回调,它将为每个业务操作实现,提供实体管理器和事务:
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 的情况下编写的。但你明白了。
编辑:在 JDK 8 中使用 lambda 可能会更短,因为 TransactionCallback 可能是函数式接口 :)。
【讨论】:
【参考方案6】:我们最近遇到了这样的问题,并决定采用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;
【讨论】:
【参考方案7】:如果您使用的是 JavaEE 应用程序服务器,那么您的代码可以通过使用无状态会话 bean 大大简化:
@Stateless
public class SomethingFactory
@PersistenceContext
private EntityManager em;
public SomeEntity doSomething (String someAttribute, String anotherAttribute)
/*
* ... independent logic ...
*/
return something;
容器将为您处理所有事务管理语义。
【讨论】:
【参考方案8】:为了完整起见 - 有 Template Method Pattern。它允许您从一个过程中抽象出所有的管理代码,并只实现核心功能。当您有许多元需求(例如在正确处理异常时关闭内容)时,此模式特别有用。
我将此模式用于运行数据库查询等,因为所有的规则都是确保关闭所有ResultSet
s 和Connection
s 同时仍能正确管理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】:仅供参考 - JDK 1.8 中的新 lambda 表达式功能现在是解决此问题的最优雅的方法。你的 DoSomething 方法应该是这样的:
DataExecutor.execute(() ->
// let's 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();
【讨论】:
以上是关于如何不在 catch 块中重复代码?的主要内容,如果未能解决你的问题,请参考以下文章
Try-catch-finally-return 澄清 [重复]