3

我有一个需要调用方法并返回其值的进程。但是,根据具体情况,此过程可能需要调用几种不同的方法。如果我可以将方法及其参数传递给进程(如在 Python 中),那么这将没有问题。但是,我不知道在 Java 中有任何方法可以做到这一点。

这是一个具体的例子。(此示例使用 Apache ZooKeeper,但您无需了解有关 ZooKeeper 的任何内容即可理解该示例。)

ZooKeeper 对象有几种方法,如果网络出现故障,这些方法将失败。在这种情况下,我总是想重试该方法。为了简化这一点,我创建了一个继承 ZooKeeper 类的“BetterZooKeeper”类,它的所有方法都会在失败时自动重试。

这就是代码的样子:

public class BetterZooKeeper extends ZooKeeper {

  private void waitForReconnect() {
    // logic
  }

  @Override
  public Stat exists(String path, Watcher watcher) {
    while (true) {
      try {
        return super.exists(path, watcher);
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }

  @Override
  public byte[] getData(String path, boolean watch, Stat stat) {
    while (true) {
      try {
        return super.getData(path, watch, stat);
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }

  @Override
  public void delete(String path, int version) {
    while (true) {
      try {
        super.delete(path, version);
        return;
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }
}

(在实际程序中,为了简单起见,我从示例中取出了更多的逻辑和方法。)

我们可以看到我使用了相同的重试逻辑,但是每个方法的参数、方法调用和返回类型都不同。

这是我为消除代码重复所做的工作:

public class BetterZooKeeper extends ZooKeeper {

  private void waitForReconnect() {
    // logic
  }

  @Override
  public Stat exists(final String path, final Watcher watcher) {
    return new RetryableZooKeeperAction<Stat>() {
      @Override
      public Stat action() {
        return BetterZooKeeper.super.exists(path, watcher);
      }
    }.run();
  }

  @Override
  public byte[] getData(final String path, final boolean watch, final Stat stat) {
    return new RetryableZooKeeperAction<byte[]>() {
      @Override
      public byte[] action() {
        return BetterZooKeeper.super.getData(path, watch, stat);
      }
    }.run();
  }

  @Override
  public void delete(final String path, final int version) {
    new RetryableZooKeeperAction<Object>() {
      @Override
      public Object action() {
        BetterZooKeeper.super.delete(path, version);
        return null;
      }
    }.run();
    return;
  }

  private abstract class RetryableZooKeeperAction<T> {

    public abstract T action();

    public final T run() {
      while (true) {
        try {
          return action();
        } catch (KeeperException e) {
          // We will retry.
        }
        waitForReconnect();
      }
    }
  }
}

RetryableZooKeeperAction 使用函数的返回类型进行参数化。run() 方法保存重试逻辑,而 action() 方法是需要运行的 ZooKeeper 方法的占位符。BetterZooKeeper 的每个公共方法都实例化了一个匿名内部类,该内部类是 RetryableZooKeeperAction 内部类的子类,并覆盖了 action() 方法。局部变量(很奇怪)隐式传递给 action() 方法,这是可能的,因为它们是最终的。

最后,这种方法确实有效,并且确实消除了重试逻辑的重复。但是,它有两个主要缺点:(1) 每次调用方法时都会创建一个新对象,以及 (2) 丑陋且难以阅读。我还必须解决具有 void 返回值的“删除”方法。

所以,这是我的问题:在 Java 中有没有更好的方法来做到这一点?这不可能是一个完全不常见的任务,其他语言(如 Python)通过允许传递方法使其更容易。我怀疑可能有一种方法可以通过反射来做到这一点,但我一直无法理解它。

4

4 回答 4

2

这似乎(至少在我看来)是“正确的”Java 风格(或 Java 中与“Pythonic”类似的任何东西)重构。您已经正确使用了模板方法模式,并且您将最终变量传递到内部匿名子类中是正确的,并且正如内部类的设计者所期望的那样。您保留了静态类型并且很好地使用了泛型。

使用反射的解决方案确实是可能的,但它确实牺牲了一些静态类型的细节,并且您的代码将不得不捕获一些伴随调用方法而来的检查异常,从而增加了一些混乱。反射被高估了,恕我直言,这里没有必要。我们倾向于在 Java 中更多地使用它来进行检测,而不是使代码更易于阅读。如果您想干净地传递方法,请等待 Java 8!

另外,我不相信您的代码不可读。专业的 Java 程序员应该能够阅读此内容。此类事物存在内部类。也就是说,可以重构“多一步”并将现在的匿名类命名为本地类(当然仍在您的方法中),这样您的调用run()就不会那么难找到。除此之外,我没有真正的重构建议。

于 2012-07-06T03:29:08.883 回答
1

所写的代码是完全合理的,并且对于当前版本的 Java 不能改进太多。闭包(现在很快就会实现)会有所帮助。

冒着引入另一种复杂性的风险,简化这种包装是面向方面编程的主要好处之一。鉴于您正在使用 Java,您可能希望专门研究AspectJ

于 2012-07-06T03:34:03.760 回答
0

我认为这需要接口和泛型的结合。首先,我将定义一个描述可调用方法行为的接口,使用泛型来指定参数并返回类型。

public interface Action<Arg, Return> {
    Return attempt(Arg arg) throws KeeperException;
}

这可以用于编写一次重试方法作为泛型方法(无需创建整个泛型类):

public <Arg, Return> ReturnType retry(Arg arg, Action<Arg, Return> action) {
    while (true) {
        try {
            return action.attempt(arg);
        } catch (KeeperException e) {
            // We will retry.
        }
        waitForReconnect();
    }
}

然后,您需要指定一个数据结构来代替每个参数集合。对于该exists操作,它将是:

public class ExistsArgs {
    String path;
    Watcher watcher;
}

exists操作最适合该模式,因为它返回 void。我们需要一种更具体的类型:

public final class Void {
    // nothing class to take the place of void return type
}

public class ExistsAction implements Action<ExistsArgs, Void> {
    private ZooKeeper delegate;
    public ExistsAction(ZooKeeper delegate) {
        this.delegate = delegate;
    }
    public Void attempt(ExistsArgs args) throws KeeperException {
        delegate.exists(args.path, args.watcher);
        return null;
    }
}

ExistsArgs(创建 .的静态内部类可能是有意义的ExistsAction。)其他方法可以类似地处理。如果Action实现是. BetterZooKeeper_BetterZooKeeper.thisdelegate

于 2012-07-06T03:33:54.540 回答
0

如前所述,您应该使用 AOP 和 Java 注释。我会推荐一个来自jcabi-aspects的已读机制(我是一名开发人员):

@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
  // do the work
}

您可以注释任何方法,@RetryOnFailure当抛出异常时,它的调用将重复几次,直到句号成功。

另请阅读这篇博文:http ://www.yegor256.com/2014/08/15/retry-java-method-on-exception.html

于 2013-02-03T08:33:13.240 回答