在低性能计算机上使用Spring bean单例复制



所以我有一个非常奇怪和令人不安的问题。在我们的低性能计算机上,一些单例bean在Spring Context初始化期间被复制。这只会发生在硬件性能较低的计算机上,并且会一直发生。

到目前为止,我能告诉你的是,它似乎发生在一个循环依赖的一堆类,我怀疑它可能与bean init方法有关。通过init方法而不是构造函数将LockScreen注入MainContentPane,可以解决循环依赖。

我有两个日志,一个来自正常性能的计算机,一个来自低性能的计算机。日志显示了差异和问题。行尾的数字是System.identityHashCode(object)方法的实例id。日志格式为:

LOGLEVEL  [Thread ID]  LoggingClass  Message

在性能正常的计算机上出现如下打印输出:

INFO  [JavaFX Application Thread]  MainContentPane      Constructor: 869589588
INFO  [JavaFX Application Thread]  MainContentPane      Getting LockScreen In Spring Init....
INFO  [JavaFX Application Thread]  SessionHandler       Constructor: 939274676
INFO  [JavaFX Application Thread]  SessionHandler       Injected MainContentPane Instance: 869589588
INFO  [JavaFX Application Thread]  UserStateBinder      Constructor: 2010765576
INFO  [JavaFX Application Thread]  UserStateBinder      Injected SessionHandler Instance: 939274676
INFO  [JavaFX Application Thread]  LockScreenLockedController Constructor: 1866179042
INFO  [JavaFX Application Thread]  LockScreenLockedController Injected UserStateBinder Instance: 2010765576
INFO  [JavaFX Application Thread]  LockScreen           Constructor: 204176749
INFO  [JavaFX Application Thread]  LockScreen           Injected LockScreenLockedController Instance: 1866179042
INFO  [JavaFX Application Thread]  LockScreen           This instance: 204176749
INFO  [JavaFX Application Thread]  LockScreen           Bean Factory instance: 1371189401
INFO  [JavaFX Application Thread]  MainContentPane      Injected LockScreen Instance: 204176749

这里没有重复的。

但是,如果我们查看性能较低的计算机的日志,我们可以看到初始化后创建的副本类似于上面的。

INFO  [JavaFX Application Thread]  MainContentPane      Constructor: 22324067
INFO  [JavaFX Application Thread]  MainContentPane      Getting LockScreen In Spring Init....
INFO  [JavaFX Application Thread]  SessionHandler       Constructor: 32463502
INFO  [JavaFX Application Thread]  SessionHandler       Injected MainContentPane Instance: 22324067
INFO  [JavaFX Application Thread]  UserStateBinder      Constructor: 19793387
INFO  [JavaFX Application Thread]  UserStateBinder      Injected SessionHandler Instance: 32463502
INFO  [JavaFX Application Thread]  LockScreenLockedController Constructor: 29065840
INFO  [JavaFX Application Thread]  LockScreenLockedController Injected UserStateBinder Instance: 19793387
INFO  [JavaFX Application Thread]  LockScreen           Constructor: 12729388
INFO  [JavaFX Application Thread]  LockScreen           Injected LockScreenLockedController Instance: 29065840
INFO  [JavaFX Application Thread]  LockScreen           This instance: 12729388
INFO  [JavaFX Application Thread]  LockScreen           Bean Factory instance: 30716643
INFO  [JavaFX Application Thread]  MainContentPane      Injected LockScreen Instance: 12729388
INFO  [JavaFX Application Thread]  SessionHandler       Constructor: 11043228
INFO  [JavaFX Application Thread]  SessionHandler       Injected MainContentPane Instance: 22324067
INFO  [JavaFX Application Thread]  UserStateBinder      Constructor: 24902967
INFO  [JavaFX Application Thread]  UserStateBinder      Injected SessionHandler Instance: 32463502
INFO  [JavaFX Application Thread]  LockScreenLockedController Constructor: 17521714
INFO  [JavaFX Application Thread]  LockScreenLockedController Injected UserStateBinder Instance: 19793387
INFO  [JavaFX Application Thread]  LockScreen           Constructor: 16791356
INFO  [JavaFX Application Thread]  LockScreen           Injected LockScreenLockedController Instance: 29065840
INFO  [JavaFX Application Thread]  LockScreen           This instance: 16791356
INFO  [JavaFX Application Thread]  LockScreen           Bean Factory instance: 30716643

在这里我们可以看到,除了MainContentPane类之外,所有的类都创建了第二组实例。新的类集依赖于之前的实例集(检查id),并且bean工厂与以前的实例相同。

所有这些消息都在主线程(JavaFX应用程序线程)上打印,所以似乎也没有并发问题。

该项目还包括一个嵌入式Jetty http服务器。我不知道Jetty中是否有任何东西可能在性能较低的计算机上导致此问题。

版本:

JRE(incl. JavaFX): 1.8.0.101
Spring: 4.3.3.RELEASE
Jetty + Websocket: 9.3.6.v20151106

我怀疑这个问题可以通过设置Spring上下文设置setallowbeandefinitionoverride (false)来解决。但这也无济于事。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.setAllowBeanDefinitionOverriding(false);
        context.register(ClientContext.class,
                MainContext.class,
                CommonContext.class,
                CciContext.class,
                PersistenceContext.class,
                SimulatorContext.class);
        context.refresh();

如果您还需要其他信息,请告诉我,谢谢您花时间帮助我。

编辑:

我现在已经确认所有的初始化和所有的bean访问都发生在一个线程中。我在跟踪日志中只发现了两个有趣的事实。首先,似乎所有的初始化都以相同的顺序发生,即使它是完全相同的软件版本(Spring是并行初始化吗?)第二,中等性能的计算机确实有复制!它们只在UserStateBinder类上有复制(或者可能是重新初始化?)高性能开发计算机根本没有这个问题。

我们可能会从明天开始将spring从我们的项目中移除,因为我们无法找到解决这个问题的方法。如果有人想让我测试任何理论,我仍然会有项目的当前版本可供我使用。

因此,经过大量的进一步测试,我能够解决问题,但没有找到奇怪行为的根本原因。

我怀疑这种行为在某种程度上是由Spring初始化具有某种时间依赖性的bean引起的,这种依赖性改变了行为。我不能确定这是否是并行的结果。

问题只发生在循环依赖LockScreen -> LockScreenLockedController -> UserStateBinder -> SessionHandler -> MainContentPane -> LockScreen。通过将MainContentPane中的构造函数依赖移到Spring init方法中,打破了循环依赖。代码是这样的:

@Bean(initMethod = "init")
public MainContentPane mainContentPane() {...}
@Bean
public LockScreen lockScreen() {...}
@Bean
public LockScreenLockedController lockScreenLockedController() {...}
@Bean
public UserStateBinder userStateBinder() {...}
@Bean
public SessionHandler sessionHandler() {...}

解决方案是在Spring中显式地使用@DependsOn注释声明依赖项,就像这样。

@Bean(initMethod = "init")
public MainContentPane mainContentPane() {...}
@Bean
@DependsOn("lockScreenLockedController")
public LockScreen lockScreen() {...}
@Bean
@DependsOn("userStateBinder")
public LockScreenLockedController lockScreenLockedController() {...}
@Bean
@DependsOn("sessionHandler")
public UserStateBinder userStateBinder() {...}
@Bean
@DependsOn("mainContentPane")
public SessionHandler sessionHandler() {...}

我希望这能帮助任何有同样问题的人,我仍然很想知道为什么Spring的行为方式取决于计算机的性能。

最新更新