【问题标题】:`ProvisionException` is cached and constructor code is never retried`ProvisionException` 被缓存并且构造函数代码永远不会重试
【发布时间】:2017-11-04 00:15:20
【问题描述】:

我有一个使用 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 集成中有一些特别之处。有什么关闭异常缓存的建议吗?

【问题讨论】:

    标签: java playframework guice


    【解决方案1】:

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

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

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

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

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

    例如一个重试构造的简单示例:

    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 ...
    }
    

    【讨论】:

    • 这行得通。我不禁感到重新设计IClock 实现以简单地避免在构造函数中抛出异常会更整洁。验证不能懒惰地完成吗?
    • 我同意,如果可以从根本上解决异常,那将是更好的方法。
    • 感谢您的建议,我同意理想情况下可以改进代码,但不幸的是实际情况并非如此(上面是一个非常简单的示例来说明问题)。 @RichDougherty:抛出异常和缓存异常似乎是 Guice 中的内置行为。我仍然不明白为什么这个问题只影响 Web 服务而不影响控制台应用程序(请参阅我的最后一句话) 不过,两者都在使用 Guice。 Play Framework 控制器是否实例化为单例?
    • @DevisLucato,很难说为什么 Play 和控制台应用程序中的代码不同。这可能是 Guice 的使用方式不同,也可能是环境不同,这意味着异常不会首先发生。可能需要更多信息/调试才能理解原因。 “Play Framework 控制器是否被实例化为单例?”默认情况下,我认为它们是暂时的(Guice 默认),除非您使用 @Singleton 注释。
    【解决方案2】:

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

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

    例如,之前:

    GET    /test    webservice.TestController.test
    

    之后:

    GET    /test    @webservice.TestController.test
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-27
      • 2018-06-27
      • 2013-08-31
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-10
      相关资源
      最近更新 更多