缓存`ProvisionException`并且永远不会重试构造函数代码

时间:2017-11-04 00:15:21

标签: java playframework guice

我有一个使用PlayFramework 2.6.5和Guice DI(libraryDependencies += guice)构建的Java Web服务,只是在时间注入模式下。所有依赖项都是通过构造函数注入的,使用@Inject@ImplementedBy,而Guice Module为空。

由于瞬态错误,某些依赖项可能会在构造函数中引发异常。发生这种情况时,服务失败并显示ProvisionException(可以,客户端需要重试)。

我发现这些异常都被缓存了,即使解决了异常的根本原因,Play或Guice也不会重试实例化这些类,并且在重新启动Web服务之前一直保持相同的异常。

例如考虑以下类Clock,如果构造函数是午夜(00:xx)则失败。一旦系统时钟到达午夜,服务就无法实例化该类。当时钟到达凌晨1点时,同样的异常会被抛出。此外,异常消息始终相同(在示例中,异常消息是第一次发生异常的时间)

@ImplementedBy(OddClock.class)
public interface IClock {
    //...
}

public class OddClock implements IClock {
    @Inject
    public OddClock() throws Exception {
        if (DateTime.now().hourOfDay().get() == 0) {
            throw new Exception(DateTime.now().toString());
        }
    }
}

public class TimeController {
    @Inject
    public TimeController(IClock clock) {
        this.clock = clock;
    }
}
顺便说一下,同样的代码库也在控制台应用程序中使用,它没有遇到这个问题,所以我认为Play + Guice集成有一些特别之处。有关关闭异常缓存的建议吗?

2 个答案:

答案 0 :(得分:1)

抛出异常并缓存异常似乎是Guice中的内置行为。它也是一种公平行为,因为Guice期望它创建的对象能够避免IO和其他非确定性行为。

https://github.com/google/guice/wiki/BeCarefulAboutIoInProviders

  

提供商没有定义重试策略。当值不可用时,多次调用get()可能会导致多个失败的条款。

您可以通过更改您使用的范围来避免缓存,以便每次都重新创建实例。例如。使用瞬态范围而不是单例。

在我看来,更好的解决方案是获取不可靠的对象并将其包装在另一个隐藏失败并处理重试的对象中。这样Guice在尝试创建可靠对象时总能成功,您可以在可靠的包装器中添加自己的故障处理代码。

E.g。重试构造的一个简单示例:

public class ReliableClock { 
  private Factory<Clock> clockFactory;
  private Clock internalClock;
  public ReliableClock(Factory<Clock> clockFactory) {
    this.clockFactory = clockFactory;
  }
  private synchronized Clock currentClock() throws Exception {
    if (clock == null) {
      clock = clockFactory.create() // May throw exception
    }
    return clock;
  }
  // ... methods ...
}

答案 1 :(得分:0)

我找到了一种解决方案,使PF的行为更加明确和可预测。假设单个控制器是用户想要的,那么PF默认生成的路由即使在实例被破坏时也会缓存控制器。

如上所述here,可以更改默认行为,在路由配置中的每个操作前添加@

例如,之前:

GET    /test    webservice.TestController.test

后:

GET    /test    @webservice.TestController.test

使用这种语法,控制器不是默认的单例,并且仍然可以在需要时使用@Singleton。此外,单例控制器在异常情况下不会被缓存,从而允许在不重新启动服务的情况下恢复瞬时错误。 该代码的副本可用here