Spring - Создание бинов приводит к бесконечной рекурсии и (иронично) исключению StackOverflow. Как исправить? - PullRequest
2 голосов
/ 05 апреля 2019

Когда я запускаю свое приложение, по какой-то неясной для меня причине оно ожидает, пока оно не создаст экземпляр SchedulerFactoryBean для создания экземпляра компонента jtaTransactionManager. Когда это происходит, Spring переходит в бесконечную рекурсию, начиная с того, что приводит к исключению StackOverflow.

После отслеживания кода я не вижу циклической зависимости - менеджер транзакций никак не зависит от SchedulerAccessor

В изображении стека внизу класс Proxy $ 98 является некоторым улучшением org.springframework.scheduling.quartz.SchedulerAccessor


Редактировать 1: Обновить

Что происходит, так это то, что SchedulerFactoryBean инициализируется в методе preInstantiateSingletons() фабрики бобов. Менеджер транзакций не является одноэлементным, поэтому он не инициализирован заранее. Когда Spring проходит через рекомендации, он пытается инициализировать bean-компонент, но рекомендация возвращает его на тот же путь.


Редактировать 2: Внутренние органы (или инферналы)

Весенний класс org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration реализует атрибутactionManager как LazyProxy.

Это выполняется задолго до того, как код инициализации создает фактический компонент TransactionManager. В какой-то момент класс должен вызвать транзакцию в контексте TransactionManager, что заставляет контейнер Spring пытаться создать экземпляр компонента. Поскольку существует совет относительно прокси-компонента бина, метод-перехватчик в классе SimpleBatchConfiguration пытается выполнить метод getTransaction(), что, в свою очередь, заставляет контейнер Spring пытаться создать экземпляр компонента, который вызывает intergceptor, который пытается выполнить метод getTransaction() ....


Редактировать 3: @ EnableBatchProcessing

Я часто использую здесь слово «кажущийся», потому что это предположение, основанное на режимах сбоев во время запуска.

(очевидно) нет способа настроить, какой менеджер транзакций используется в аннотации @EnableBatchProcessing. Удаление @EnableBatchProcessing устранило рекурсивный вызов, но оставило меня с явной циклической зависимостью.

По какой-то неизвестной причине, хотя я и проследил, и этот код вызывается ровно один раз, он завершается ошибкой, поскольку считает, что компонент с именем "configurer" уже находится в процессе создания:

@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);
    }

}

Строительство боба jtaTransactionManager в DatabaseConfig

@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;
}

Строительство SchedulerFactoryBean в SchedulerConfig

@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;
}

Stack at breakpoint at 2nd level of recursion

1 Ответ

0 голосов
/ 17 апреля 2019

Было несколько невозможно запутаться, чтобы выяснить шаги к решению. Я закончил с этим, пока он не работал, потому что сообщения об исключениях не были информацией.

В итоге вот результат:

  1. Реорганизована упаковка, чтобы компоненты с заданием / шагом и глобальные компоненты находились в разных пакетах, поэтому при сканировании по контексту можно было легко получить нужные компоненты в нужном контексте.

  2. Клонировали и модифицировали org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration, чтобы получить бобы, которые я хотел для своего приложения

  3. Взял аннотацию @EnableBatchProcessing. Поскольку я уже инициализировал менее автоматически, все инициализировалось дважды, что создавало путаницу

  4. Устранено использование источников данных - XA и non-XA

  5. Используйте аннотацию @Primary, чтобы выбрать правильный (кусая язык здесь - нет способа сообщить структуре, какой из нескольких источников данных использовать, без явного указания, что в случае вопросов всегда используйте «этот»? Действительно ???)

...