所以我有一个非常奇怪和令人不安的问题。在我们的低性能计算机上,一些单例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的行为方式取决于计算机的性能。