Spring - 实例化 bean 会导致无限递归和(具有讽刺意味的)StackOverflow 异常。怎么修?

Spring - Instantiating beans results in infinite recursion and (ironic) StackOverflow exception. How to fix?

当我启动我的应用程序时,出于某种对我来说不明显的原因,它正在等待实例化 SchedulerFactoryBean 以实例化 jtaTransactionManager bean。当它这样做时,Spring 进入无限递归,从导致 Whosebug 异常开始。

跟踪 hte 代码后,我发现没有循环依赖 - 事务管理器不以任何方式依赖于 SchedulerAccessor

在底部的堆栈视图图像中,Proxy$98 class 是 org.springframework.scheduling.quartz.SchedulerAccessor

的一些增强

编辑 1:更新

正在发生的事情是 SchedulerFactoryBean 正在 bean 工厂的 preInstantiateSingletons() 方法中初始化。事务管理器不是单例,因此没有预初始化。当 Spring 通过建议时,它会尝试初始化 bean,但建议会引导它回到相同的路径。


编辑 2:内部(或地狱)

springclassorg.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration将 transactionManager 属性实现为 LazyProxy。

这是在初始化代码构造实际的 TransactionManager bean 之前执行的。在某些时候,class 需要在 TransactionManager 上下文中调用事务,这会导致 spring 容器尝试实例化 bean。由于bean代理上有advice,SimpleBatchConfigurationclass中的方法拦截器尝试执行getTransaction()方法,进而导致spring容器尝试实例化调用拦截器的 bean,拦截器尝试执行 getTransaction() 方法 ....


编辑 3:@EnableBatchProcessing

我在这里经常使用 "apparent" 这个词,因为它是根据启动期间的故障模式进行的猜测。

(显然)无法配置在 @EnableBatchProcessing 注释中使用哪个事务管理器。剥离 @EnableBatchProcessing 消除了递归调用,但给我留下了明显的循环依赖。

由于某些未知原因,即使我已经跟踪并且这段代码只调用了一次,它还是失败了,因为它认为名为 "configurer" 的 bean 已经在创建中:

@Bean({ "configurer", "defaultBatchConfigurer" })
@Order(1)
public BatchConfigurer configurer() throws IOException, SystemException {
    DefaultBatchConfigurer result = new DefaultBatchConfigurer(securityDataSource(), transactionManager());

    return result;
}

启动递归的代码是:

protected void registerJobsAndTriggers() throws SchedulerException {
    TransactionStatus transactionStatus = null;
    if (this.transactionManager != null) {
        transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
    }

AppInitializer 启动代码:

@Override
public void onStartup(ServletContext container) throws ServletException {
    Logger logger = LoggerFactory.getLogger(this.getClass());

    try {
        // DB2XADataSource db2DataSource = null;

        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(DatabaseConfig.class);
        rootContext.register(SecurityConfig.class);
        rootContext.register(ExecutionContextConfig.class);
        rootContext.register(SimpleBatchConfiguration.class);
        rootContext.register(MailConfig.class);
        rootContext.register(JmsConfig.class);
        rootContext.register(SchedulerConfig.class);
        rootContext.refresh();
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
    }

}

DatabaseConfig

中构建 jtaTransactionManager bean
@Bean(destroyMethod = "shutdown")
@Order(1)
public BitronixTransactionManager bitronixTransactionManager() throws IOException, SystemException {
    btmConfig();
    BitronixTransactionManager bitronixTransactionManager = TransactionManagerServices.getTransactionManager();
    bitronixTransactionManager.setTransactionTimeout(3600); // TODO: Make this configurable
    return bitronixTransactionManager;
}

@Bean({ "transactionManager", "jtaTransactionManager" })
@Order(1)
public PlatformTransactionManager transactionManager() throws IOException, SystemException {
    JtaTransactionManager mgr = new JtaTransactionManager();

    mgr.setTransactionManager(bitronixTransactionManager());
    mgr.setUserTransaction(bitronixTransactionManager());
    mgr.setAllowCustomIsolationLevels(true);
    mgr.setDefaultTimeout(3600);
    mgr.afterPropertiesSet();

    return mgr;
}

SchedulerConfig

中构建 SchedulerFactoryBean
@Autowired
@Qualifier("transactionManager")
public void setJtaTransactionManager(PlatformTransactionManager jtaTransactionManager) {
    this.jtaTransactionManager = jtaTransactionManager;
}

@Bean
@Order(3)
public SchedulerFactoryBean schedulerFactoryBean() {
    Properties quartzProperties = new Properties();

    quartzProperties.put("org.quartz.jobStore.driverDelegateClass",
            delegateClass.get(getDatabaseType()));
    quartzProperties.put("org.quartz.jobStore.tablePrefix", getTableSchema()
            + ".QRTZ_");
    quartzProperties.put("org.quartz.jobStore.class",
            org.quartz.impl.jdbcjobstore.JobStoreCMT.class.getName());
    quartzProperties.put("org.quartz.scheduler.instanceName",
            "MxArchiveScheduler");
    quartzProperties.put("org.quartz.threadPool.threadCount", "3");

    SchedulerFactoryBean result = new SchedulerFactoryBean();
    result.setDataSource(securityDataSource());


    result.setNonTransactionalDataSource(nonJTAsecurityDataSource());
    result.setTransactionManager(jtaTransactionManager);
    result.setQuartzProperties(quartzProperties);

    return result;
}

有几个令人难以置信的复杂步骤来找出解决方案的步骤。我最终对它进行了修改,直到它起作用,因为异常消息不是信息。

最后,结果如下:

  1. 重构了包装,因此 job/step 作用域和全局作用域的 bean 在不同的包中,因此上下文扫描可以轻松地在正确的上下文中捕获正确的 bean。

  2. 克隆和修改 org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration 以获取我想要用于我的应用程序的 bean

  3. 去掉了@EnableBatchProcessing注解。由于我已经在进行较少的自动初始化,因此所有内容都进行了两次初始化,这造成了混乱

  4. 清理了数据源的使用 - XA 和非 XA

  5. 使用 @Primary 注释来选择正确的(这里是尖锐的舌头 - 没有办法告诉框架使用几个数据源中的哪一个而不隐含地告诉它在有问题的情况下总是使用"this one"?真的吗???)