【问题标题】:How to not repeat code within catch blocks?如何不在 catch 块中重复代码?
【发布时间】: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/…
  • 这是“执行周围”模式可以提供帮助的地方:stackoverflow.com/questions/341971/…
  • 嘿@AndersR.Bystrup,非常感谢。我不得不承认我不知道,try-with-resources 存在。不幸的是javax.persistence.EntityManager 没有实现AutoClosable
  • 然后将其包装到实现AutoClosable 的对象中:)。 IE。您的createEntitiyManager 方法返回ClosableEntitiyManager,它是实现AutoClosableEntityManager 的包装,您就完成了。
  • 解决这个问题的另一种方法是使用 Spring。您声明了一个带有@Transactional 注释的方法,所有事务工作都为您完成。

标签: java


【解决方案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 子句都用于关闭Streams 等(任何实现AutoCloseable 的东西),您可以使用try-with-resources(如其中一个cmets 中所建议的那样)来摆脱@ 987654325@ 子句。

    但是,如果您需要更通用的解决方案,并且在 finally 子句中捕获了相同类型的 Exceptions 和相同类型的处理,则可以创建一个抽象类,例如:

    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。它允许您从一个过程中抽象出所有的管理代码,并只实现核心功能。当您有许多元需求(例如在正确处理异常时关闭内容)时,此模式特别有用。

          我将此模式用于运行数据库查询等,因为所有的规则都是确保关闭所有ResultSets 和Connections 同时仍能正确管理PreparedStatements。

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-05-02
              • 1970-01-01
              • 2017-08-22
              • 2016-06-08
              • 1970-01-01
              • 1970-01-01
              • 2018-03-04
              • 1970-01-01
              相关资源
              最近更新 更多