добавить ссылку на bean-компонент в задание Quartz весной?


95

Мне удалось настроить и запланировать задание Quartz с помощью постоянного хранилища JobStoreTX в Spring. Я не использую задания Spring Quartz, потому что мне нужно планировать их динамически, во время выполнения, и все примеры интеграции Spring с Quartz, которые я обнаружил, жестко кодировали shcedules в файлах конфигурации Spring ... В любом случае, вот как Я планирую работу:

JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
.withIdentity("someJobKey", "immediateEmailsGroup")
.storeDurably()
.build();

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() 
.withIdentity("someTriggerKey", "immediateEmailsGroup")
.startAt(fireTime)
.build();

// pass initialization parameters into the job
emailJob.getJobDataMap().put(NotificationConstants.MESSAGE_PARAMETERS_KEY,       messageParameters);
emailJob.getJobDataMap().put(NotificationConstants.RECIPIENT_KEY, recipient);

if (!scheduler.checkExists(jobKey) && scheduler.getTrigger(triggerKey) != null)     {                                       
// schedule the job to run
Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
}

EMailJob - это простая задача по отправке электронной почты с использованием класса JavaMailSenderImpl Spring.

public class EMailJob implements Job {
@Autowired
private JavaMailSenderImpl mailSenderImpl;

    public EMailJob() {
    }
    public void execute(JobExecutionContext context)
       throws JobExecutionException {
   ....
    try {
        mailSenderImpl.send(mimeMessage);
    } catch (MessagingException e) {
        ....
        throw new JobExecutionException("EMailJob failed: " +  jobKey.getName(), e);
    }

    logger.info("EMailJob finished OK");

}

Проблема в том, что мне нужно получить ссылку на экземпляр этого класса (JavaMailSenderImpl) в моем классе EMailJob. Когда я пытаюсь ввести это так:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

он не вводится - ссылка NULL. Я предполагаю, что это происходит потому, что не Spring создает экземпляр класса EMailJob, а Quartz, а Quartz ничего не знает о внедрении зависимостей ...

Итак, есть ли способ заставить эту инъекцию произойти?

Благодарность!

Обновление 1: @Aaron: вот соответствующая часть трассировки стека из запуска, которая показывает, что экземпляр EMailJob был создан дважды:

2011-08-15 14:16:38,687 [main] INFO     org.springframework.context.support.GenericApplicationContext - Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler#0' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2011-08-15 14:16:38,734 [main] INFO  org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1328c7a: defining beans [...]; root of factory hierarchy
2011-08-15 14:16:39,734 [main] INFO  com.cambridgedata.notifications.EMailJob - EMailJob() -  initializing ...
2011-08-15 14:16:39,937 [main] INFO  org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor -   Validated configuration attributes
2011-08-15 14:16:40,078 [main] INFO  org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Validated configuration attributes
2011-08-15 14:16:40,296 [main] INFO  org.springframework.jdbc.datasource.init.ResourceDatabasePopulator - Executing SQL script from class path resource ...
2011-08-15 14:17:14,031 [main] INFO  com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 14:17:14,109 [main] INFO  com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 14:17:14,171 [main] INFO  org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 14:17:14,171 [main] INFO  org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 14:17:14,187 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 14:17:14,187 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 14:17:14,187 [main] INFO  org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'NotificationsScheduler' with instanceId  'NON_CLUSTERED'
 Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
   NOT STARTED.
 Currently in standby mode.
 Number of jobs executed: 0
 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
 Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.

2011-08-15 14:17:14,187 [main] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'NotificationsScheduler' initialized from the specified file : 'spring/quartz.properties' from the class resource path.
2011-08-15 14:17:14,187 [main] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 14:17:14,234 [main] INFO  com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 2sajb28h1lcabf28k3nr1|13af084, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 2sajb28h1lcabf28k3nr1|13af084, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 14:17:14,312 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 14:17:14,328 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 14:17:14,328 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 14:17:14,328 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 14:17:14,328 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 14:17:14,328 [main] INFO  org.quartz.core.QuartzScheduler - Scheduler NotificationsScheduler_$_NON_CLUSTERED started.
2011-08-15 14:17:14,515 [NotificationsScheduler_QuartzSchedulerThread] INFO  com.cambridgedata.notifications.EMailJob - EMailJob() -  initializing ...

Благодарность!

Обновление №2: @Ryan:

Я пробовал использовать SpringBeanJobFactory следующим образом:

    <bean id="jobFactoryBean" class="org.springframework.scheduling.quartz.SpringBeanJobFactory">
</bean>

<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="configLocation" value="classpath:spring/quartz.properties"/>
        <property name="jobFactory" ref="jobFactoryBean"/>
</bean>

И я изменил свой основной класс, чтобы получить Scheduler с этой фабрики вместо Quartz:

    @PostConstruct
public void initNotificationScheduler() {
    try {
        //sf = new StdSchedulerFactory("spring/quartz.properties");
        //scheduler = sf.getScheduler();

        scheduler = schedulerFactoryBean.getScheduler();
        scheduler.start();
            ....

Но когда запускаю приложение - получаю ошибки, см. Ниже. Вот трассировка стека из запуска Spring. Похоже, что сам планировщик создан нормально, но ошибка возникает, когда он пытается создать экземпляр моего EMailJob:

2011-08-15 21:49:42,968 [main] INFO  org.springframework.scheduling.quartz.SchedulerFactoryBean - Loading Quartz config from [class path resource [spring/quartz.properties]]
2011-08-15 21:49:43,031 [main] INFO  com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 21:49:43,109 [main] INFO  com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
 Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
 NOT STARTED.
 Currently in standby mode.
 Number of jobs executed: 0
 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
 Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.

2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@566633
2011-08-15 21:49:43,265 [main] INFO  com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge13f8h1lsg7py1rg0iu0|1956391, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge13f8h1lsg7py1rg0iu0|1956391, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 21:49:43,343 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.core.QuartzScheduler - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2011-08-15 21:49:43,562 [schedulerFactoryBean_QuartzSchedulerThread] ERROR org.quartz.core.ErrorLogger - An error occured instantiating job to be executed. job= 'immediateEmailsGroup.DEFAULT.jobFor_1000new1'
org.quartz.SchedulerException: Problem instantiating class  'com.cambridgedata.notifications.EMailJob' -  [See nested exception:  java.lang.AbstractMethodError:  org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;]
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:141)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:381)
Caused by: java.lang.AbstractMethodError: org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:134)

Благодарность!

Ответы:


130

Вы можете использовать это SpringBeanJobFactoryдля автоматического автоматического связывания кварцевых объектов с помощью пружины:

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
    ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

Затем прикрепите его к себе SchedulerBean(в данном случае с Java-config):

@Bean
public SchedulerFactoryBean quartzScheduler() {
    SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();

    ...

    AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
    jobFactory.setApplicationContext(applicationContext);
    quartzScheduler.setJobFactory(jobFactory);

    ...

    return quartzScheduler;
}

У меня работает, используя пружину-3.2.1 и кварц-2.1.6.

Ознакомьтесь с полной сутью здесь .

Я нашел решение в этом сообщении в блоге


13
Вы должны получить за это награду, это фантастика!
Nathan Feger

2
Решение действительно отличное! Все кредиты автору сообщения в блоге :)
jelies 09

3
Спасибо - это сэкономило мне дни! Почему Spring не предоставил этот OOB. Это самое основное требование для использования Quartz в Spring.
HandyManDan

4
это должна быть реализация по умолчанию :)
Диего Пленц

2
отличное решение, но кто-нибудь знает, почему AutowireCapableBeanFactory beanFactory помечен как «временный»? AutowiringSpringBeanJobFactory в любом случае, похоже, не сериализуется, так что ни один из beanFactory никогда не потребуется сериализовать
Мариос

57

Я просто поставил SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);первую строчку своего Job.execute(JobExecutionContext context)метода.


7
Это настоящее решение. Протестировано с помощью Spring 3.2.4.RELEASE и Quartz 2.2.0. ;)
aloplop85 09

3
@msangel - хорошо, оба БУДУТ работать, но проблема с использованием SpringBeanAutowiringSupport в вашем задании Quartz заключается в том, что экземпляру задания теперь нужно ЗНАТЬ о Spring, что противоречит всей идее IoC (Dep-инъекция). Если вам теперь, например, нужно использовать CDI, нужно будет настроить все ваши кварцевые задания, а не только одну фабрику заданий.
demaniak 07

2
У меня это не сработало в модульном тесте, поскольку он ищет контекст веб-приложения. Мне пришлось использовать ответ от @jelies
Вим Деблаув

5
Это решение не работает с Spring 4.1.4 и Quartz 2.2.1
Skywalker

1
У меня тоже была эта проблема, и я попробовал это решение. Он работает, НО он создает новый экземпляр вместо использования уже созданного (синглтон по умолчанию). В любом случае вы можете передать своему заданию что угодно, используя scheduler.getContext (). Put ("objectName", object);
Krzysztof Cieśliński 01

13

Та же проблема была решена в LINK :

Я мог найти другой вариант из сообщения на форуме Spring, в котором вы можете передать ссылку на контекст приложения Spring через SchedulerFactoryBean. Как в примере, показанном ниже:

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyy name="triggers">
    <list>
        <ref bean="simpleTrigger"/>
            </list>
    </property>
    <property name="applicationContextSchedulerContextKey">
        <value>applicationContext</value>
</property>

Затем, используя приведенный ниже код в своем классе работы, вы можете получить applicationContext и получить любой компонент, который хотите.

appCtx = (ApplicationContext)context.getScheduler().getContext().get("applicationContextSchedulerContextKey");

Надеюсь, это поможет. Вы можете получить больше информации в блоге Марка Макларена.


1
Спасибо, @Rippon! После долгих попыток и неудач я использовал очень похожий подход, который вы предложили: я не использовал свойство applicationContextSchedulerContextKey и контекст приложения, но я использовал 'код' <property name = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Марина

8

Вы правы в своем предположении о Spring vs. Quartz, создающем экземпляр класса. Однако Spring предоставляет несколько классов, которые позволяют выполнять простейшие инъекции зависимостей в Quartz. Ознакомьтесь с SchedulerFactoryBean.setJobFactory () вместе с SpringBeanJobFactory . По сути, с помощью SpringBeanJobFactory вы включаете внедрение зависимостей для всех свойств задания, но только для значений, которые находятся в контексте планировщика Quartz или на карте данных задания . Я не знаю, какие все стили DI он поддерживает (конструктор, аннотация, установщик ...), но я знаю, что он поддерживает внедрение установщика.


Привет, Райан, спасибо за твои предложения. Вы имеете в виду, что мне придется использовать SpringBeanFactoryJob вместе с предварительно настроенными триггерами и заданиями, расширяющими QuartzJobBean, чтобы включить внедрение зависимостей? Проблема с этим подходом заключается в том, что триггеры определяются статически в файлах конфигурации Spring, где мне нужно иметь возможность определять триггеры с динамическими расписаниями во время выполнения ... Подробнее см. Мой следующий ответ ниже - недостаточно места в область комментариев ...
Марина

@ Марина: Нет, это не так. Используйте SpringBeanJobFactory и делайте это так, как вы хотите. Это просто сработает. Кроме того, не публикуйте ответ, который является просто обновлением вашего вопроса. Вместо этого отредактируйте свой вопрос.
Райан Стюарт,

извините, я только что заметил ваш комментарий! Я попробую, как вы предлагаете, и дам вам знать результаты. Спасибо за помощь! О, и я постараюсь отредактировать свой вопрос, вместо того, чтобы отвечать ...
Марина

7

для всех, кто попробует это в будущем.

org.springframework.scheduling.quartz.JobDetailBean предоставляет карту объектов, и эти объекты могут быть Spring beans.

определять что-л. как

<bean name="myJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass"
        value="my.cool.class.myCoolJob" />
    <property name="jobDataAsMap">
        <map>
            <entry key="myBean" value-ref="myBean" />
        </map>
    </property>
</bean>

а затем внутри

public void executeInternal(JobExecutionContext context)

звоните myBean = (myBean) context.getMergedJobDataMap().get("myBean"); и все готово. Я знаю, это выглядит некрасиво, но как обходной путь работает


ИМХО, я считаю это решение более чистым и «естественным», чем попытки добавить возможность автоматического подключения к кварцевым работам, поэтому я не думаю, что это обходной путь.
reallynice

6
ApplicationContext springContext =

WebApplicationContextUtils.getWebApplicationContext(ContextLoaderListener .getCurrentWebApplicationContext().getServletContext());

Bean bean = (Bean) springContext.getBean("beanName");

bean.method();

4

Спасибо, Риппон! Я наконец-то получил и эту работу после многих усилий, и мое решение очень близко к тому, что вы предложили! Ключевым моментом было создание моей собственной задачи по расширению QuartzJobBean и использование schedulerContextAsMap.

Я ушел, не указав свойство applicationContextSchedulerContextKey - у меня оно работало без него.

Для других вот последняя конфигурация, которая сработала для меня:

    <bean id="quartzScheduler"  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="configLocation" value="classpath:spring/quartz.properties"/>
        <property name="jobFactory">
            <bean  class="org.springframework.scheduling.quartz.SpringBeanJobFactory" />
        </property>
        <property name="schedulerContextAsMap">
            <map>
                <entry key="mailService" value-ref="mailService" />
            </map>
        </property>
</bean>
<bean id="jobTriggerFactory"
      class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
    <property name="targetBeanName">
        <idref local="jobTrigger" />
    </property>
</bean>
<bean id="jobTrigger"   class="org.springframework.scheduling.quartz.SimpleTriggerBean"
    scope="prototype">
      <property name="group" value="myJobs" />
      <property name="description" value="myDescription" />
      <property name="repeatCount" value="0" />
</bean>

<bean id="jobDetailFactory"
      class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
    <property name="targetBeanName">
        <idref local="jobDetail" />
    </property>
</bean>

<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"
scope="prototype">
<property name="jobClass" value="com.cambridgedata.notifications.EMailJob" />
<property name="volatility" value="false" />
<property name="durability" value="false" />
<property name="requestsRecovery" value="true" />
</bean> 
<bean id="notificationScheduler"   class="com.cambridgedata.notifications.NotificationScheduler">
    <constructor-arg ref="quartzScheduler" />
    <constructor-arg ref="jobDetailFactory" />
    <constructor-arg ref="jobTriggerFactory" />
</bean>

Обратите внимание, что bean-компонент mailService - это мой собственный служебный bean-компонент, управляемый Spring. Я смог получить к нему доступ в моем Job следующим образом:

    public void executeInternal(JobExecutionContext context)
    throws JobExecutionException {

    logger.info("EMailJob started ...");
    ....
    SchedulerContext schedulerContext = null;
    try {
        schedulerContext = context.getScheduler().getContext();
    } catch (SchedulerException e1) {
        e1.printStackTrace();
    }
    MailService mailService = (MailService)schedulerContext.get("mailService");
    ....

И эта конфигурация также позволила мне динамически планировать задания, используя фабрики для получения триггеров и JobDetails и программно задавая для них необходимые параметры:

    public NotificationScheduler(final Scheduler scheduler,
        final ObjectFactory<JobDetail> jobDetailFactory,
        final ObjectFactory<SimpleTrigger> jobTriggerFactory) {
    this.scheduler = scheduler;
    this.jobDetailFactory = jobDetailFactory;
    this.jobTriggerFactory = jobTriggerFactory;
           ...
        // create a trigger
        SimpleTrigger trigger = jobTriggerFactory.getObject();
        trigger.setRepeatInterval(0L);
    trigger.setStartTime(new Date());

    // create job details
    JobDetail emailJob = jobDetailFactory.getObject();

    emailJob.setName("new name");
    emailJob.setGroup("immediateEmailsGroup");
            ...

Еще раз спасибо всем, кто помогал,

Марина


4

Простое решение - установить компонент Spring в карте данных задания, а затем получить этот компонент в классе задания, например

// the class sets the configures the MyJob class 
    SchedulerFactory sf = new StdSchedulerFactory();
    Scheduler sched = sf.getScheduler();
    Date startTime = DateBuilder.nextGivenSecondDate(null, 15);
    JobDetail job = newJob(MyJob.class).withIdentity("job1", "group1").build();
    job.getJobDataMap().put("processDataDAO", processDataDAO);

`

 // this is MyJob Class
    ProcessDataDAO processDataDAO = (ProcessDataDAO) jec.getMergedJobDataMap().get("processDataDAO");

учитывая, что данные о задании хранятся в виде больших двоичных объектов (при использовании постоянства БД), это может привести к проблемам с производительностью БД (представьте, что данные действительно огромны)
Судип Бхандари,

3

Вот как выглядит код с @Component:

Основной класс, который планирует работу:

public class NotificationScheduler {

private SchedulerFactory sf;
private Scheduler scheduler;

@PostConstruct
public void initNotificationScheduler() {
    try {
    sf = new StdSchedulerFactory("spring/quartz.properties");
    scheduler = sf.getScheduler();
    scheduler.start();
            // test out sending a notification at startup, prepare some parameters...
    this.scheduleImmediateNotificationJob(messageParameters, recipients);
        try {
            // wait 20 seconds to show jobs
            logger.info("sleeping...");
            Thread.sleep(40L * 1000L); 
            logger.info("finished sleeping");
           // executing...
        } catch (Exception ignore) {
        }

      } catch (SchedulerException e) {
    e.printStackTrace();
    throw new RuntimeException("NotificationScheduler failed to retrieve a Scheduler instance: ", e);
    }
}


public void scheduleImmediateNotificationJob(){
  try {
    JobKey jobKey = new JobKey("key");
    Date fireTime = DateBuilder.futureDate(delayInSeconds, IntervalUnit.SECOND);
    JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
    .withIdentity(jobKey.toString(), "immediateEmailsGroup")
        .build();

    TriggerKey triggerKey = new TriggerKey("triggerKey");
    SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() 
        .withIdentity(triggerKey.toString(), "immediateEmailsGroup")
        .startAt(fireTime)
        .build();

    // schedule the job to run
    Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
  } catch (SchedulerException e) {
    logger.error("error scheduling job: " + e.getMessage(), e);
    e.printStackTrace();
      }
}

@PreDestroy
public void cleanup(){
    sf = null;
    try {
        scheduler.shutdown();
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}

EmailJob такой же, как и в моей первой публикации, за исключением аннотации @Component:

@Component
public class EMailJob implements Job { 
  @Autowired
  private JavaMailSenderImpl mailSenderImpl;
... }

И файл конфигурации Spring имеет:

...
<context:property-placeholder location="classpath:spring/*.properties" />
<context:spring-configured/>
<context:component-scan base-package="com.mybasepackage">
  <context:exclude-filter expression="org.springframework.stereotype.Controller"
        type="annotation" />
</context:component-scan>
<bean id="mailSenderImpl" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="${mail.host}"/>
    <property name="port" value="${mail.port}"/>
    ...
</bean>
<bean id="notificationScheduler" class="com.mybasepackage.notifications.NotificationScheduler">
</bean>

Спасибо за помощь!

Марина


Когда ваше приложение запускается, вы видите EmailJobего инициализацию? Легкий способ проверить - добавить строку журнала в конструктор.
atrain

@Aaron: да, но, как я только что обнаружил, он инициализируется дважды! Один раз самим фреймворком Spring (и я уверен, что в этот экземпляр встроена почтовая служба ...), а затем, позже, после инициализации самого Quartz - EMailJob снова инициализируется фреймворком Quartz - и это тот в котором не внедрена служба ... Я попытаюсь добавить трассировку стека запуска Spring, отредактировав свой вопрос, как предложил Райан ...
Марина

2

Решение от Hary https://stackoverflow.com/a/37797575/4252764 работает очень хорошо. Это проще, не нужно столько специальных фабричных компонентов и поддерживает несколько триггеров и заданий. Я бы просто добавил, что задание Quartz может быть универсальным, а конкретные задания реализованы как обычные компоненты Spring.

public interface BeanJob {
  void executeBeanJob();
}

public class GenericJob implements Job {

  @Override
  public void execute(JobExecutionContext context) throws JobExecutionException {
    JobDataMap dataMap = context.getMergedJobDataMap();
    ((BeanJob)dataMap.get("beanJob")).executeBeanJob();    
  }

}

@Component
public class RealJob implements BeanJob {
  private SomeService service;

  @Autowired
  public RealJob(SomeService service) {
    this.service = service;
  }

  @Override
  public void executeBeanJob() {
      //do do job with service
  }

}

Спасибо. Я тоже думал об этом. Но в моем случае я писал библиотеку, которая абстрагирует любую реализацию кварца. Это необходимо для запоминания имени «ключа» для извлечения любых объектов. Я смог сделать это чистым кварцевым способом и просто опубликовал его как ответ. Пожалуйста, поделитесь своими мыслями!
Karthik R

2

Это довольно старый пост, который все еще полезен. Все решения, которые предлагают эти два, имеют небольшое условие, которое не подходит всем:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Это предполагает или требует, чтобы это был весенний веб-проект.
  • AutowiringSpringBeanJobFactory подход на основе, упомянутый в предыдущем ответе, очень полезен, но ответ предназначен для тех, кто не использует чистый api ванильного кварца, а скорее обертку Spring для кварца, чтобы сделать то же самое.

Если вы хотите остаться с чистой реализацией Quartz для планирования (Quartz с возможностями Autowiring с Spring), я смог сделать это следующим образом:

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

 public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory{

    private AutowireCapableBeanFactory beanFactory;

    public AutowiringSpringBeanJobFactory(final ApplicationContext applicationContext){
        beanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        beanFactory.initializeBean(job, job.getClass().getName());
        return job;
    }
}


@Configuration
public class SchedulerConfig {   
    @Autowired private ApplicationContext applicationContext;

    @Bean
    public AutowiringSpringBeanJobFactory getAutowiringSpringBeanJobFactory(){
        return new AutowiringSpringBeanJobFactory(applicationContext);
    }
}


private void initializeAndStartScheduler(final Properties quartzProperties)
            throws SchedulerException {
        //schedulerFactory.initialize(quartzProperties);
        Scheduler quartzScheduler = schedulerFactory.getScheduler();

        //Below one is the key here. Use the spring autowire capable job factory and inject here
        quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
        quartzScheduler.start();
    }

quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);дает нам экземпляр задания с автоматическим подключением. Поскольку AutowiringSpringBeanJobFactoryнеявно реализует a JobFactory, теперь мы включили решение с автоматическим подключением. Надеюсь это поможет!


1

Простой способ сделать это - просто аннотировать Quartz Jobs с помощью @Componentаннотации, и тогда Spring сделает за вас всю магию DI, поскольку теперь он распознается как компонент Spring. Мне пришлось сделать что-то подобное для AspectJаспекта - это не был компонент Spring, пока я не аннотировал его @Componentстереотипом Spring .


Спасибо, Аарон, я только что попробовала - но, к сожалению, происходит то же самое NPE - и почтовый сервис не внедряется в компонент задания ...
Марина

Находится ли ваш EmailJobкласс в пакете, который будет сканироваться Spring при запуске приложения? Тот факт, что вы аннотировали, @Componentно внедренный класс по-прежнему имеет значение null, означает, что он не сканируется - в противном случае DI при запуске приложения вызовет исключение.
atrain

Аарон: да, предполагается, что его нужно сканировать - у меня есть <context: component-scan base-package = "com.mybasepackage">, который должен это делать ... В следующем ответе я предоставляю полный код моей основной класс, с конфигурацией Spring - на всякий случай можно заметить что-то очевидное ...
Марина

Поля вакансий, отмеченные «@Autowired», не вводятся, даже если вы помечаете задание «@Component»
aloplop85

6
Это не сработает, потому что создание объектов Job управляется Quarts, и поэтому поля не связаны автоматически, а аннотации классов ничего не делают без дополнительной обработки.
msangel

1

Приведенное выше решение отличное, но в моем случае инъекция не сработала. Вместо этого мне нужно было использовать autowireBeanProperties, вероятно, из-за того, как настроен мой контекст:

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        //beanFactory.autowireBean(job);
        beanFactory.autowireBeanProperties(job, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
        return job;
    }
}

1

Это правильный ответ http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . и будет работать для большинства людей. Но если ваш web.xml не знает обо всех файлах applicationContext.xml, задание quartz не сможет вызвать эти bean-компоненты. Мне пришлось сделать дополнительный слой для вставки дополнительных файлов applicationContext

public class MYSpringBeanJobFactory extends SpringBeanJobFactory
        implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {

        try {
                PathMatchingResourcePatternResolver pmrl = new PathMatchingResourcePatternResolver(context.getClassLoader());
                Resource[] resources = new Resource[0];
                GenericApplicationContext createdContext = null ;
                    resources = pmrl.getResources(
                            "classpath*:my-abc-integration-applicationContext.xml"
                    );

                    for (Resource r : resources) {
                        createdContext = new GenericApplicationContext(context);
                        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(createdContext);
                        int i = reader.loadBeanDefinitions(r);
                    }

            createdContext.refresh();//important else you will get exceptions.
            beanFactory = createdContext.getAutowireCapableBeanFactory();

        } catch (IOException e) {
            e.printStackTrace();
        }



    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle)
            throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

Вы можете добавить любое количество файлов контекста, о которых должен знать кварц.


0

Убедитесь, что ваш

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

зависимость извлекается из

    "org.springframework:spring-context-support:4..."

а НЕ из

    "org.springframework:spring-support:2..."

Он хотел, чтобы я использовал

@Override
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler)

вместо того

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

поэтому не удалось выполнить автоматическое подключение экземпляра задания.


0

Если вы уже используете настоящий AspectJ в своем проекте, вы можете аннотировать класс компонента задания с помощью @Configurable. Затем Spring будет внедряться в этот класс, даже если он построен с помощьюnew


0

Я столкнулся с похожей проблемой и решил ее следующим образом:

<!-- Quartz Job -->
<bean name="JobA" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <!-- <constructor-arg ref="dao.DAOFramework" /> -->
     <property name="jobDataAsMap">
    <map>
        <entry key="daoBean" value-ref="dao.DAOFramework" />
    </map>
</property>
    <property name="jobClass" value="com.stratasync.jobs.JobA" />
    <property name="durability" value="true"/>
</bean>

В приведенном выше коде я ввожу bean-компонент dao.DAOFramework в bean-компонент JobA, а внутри метода ExecuteInternal вы можете получить внедренный bean-компонент, например:

  daoFramework = (DAOFramework)context.getMergedJobDataMap().get("daoBean");

Я надеюсь, что это помогает! Спасибо.


0

Все вышеперечисленные решения не работают в Spring 5, Hibernate 5 и Quartz 2.2.3, когда я хочу вызывать транзакционные методы!

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

Моя базовая конфигурация выглядит так

@Configuration
public class QuartzConfiguration {

  @Autowired
  ApplicationContext applicationContext;

  @Bean
  public SchedulerFactoryBean scheduler(@Autowired JobFactory jobFactory) throws IOException {
    SchedulerFactoryBean sfb = new SchedulerFactoryBean();

    sfb.setOverwriteExistingJobs(true);
    sfb.setAutoStartup(true);
    sfb.setJobFactory(jobFactory);

    Trigger[] triggers = new Trigger[] {
        cronTriggerTest().getObject()
    };
    sfb.setTriggers(triggers);
    return sfb;
  }

  @Bean
  public JobFactory cronJobFactory() {
    AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
    jobFactory.setApplicationContext(applicationContext);
    return jobFactory;
  }

  @Bean 
  public CronTriggerFactoryBean cronTriggerTest() {
    CronTriggerFactoryBean tfb = new CronTriggerFactoryBean();
    tfb.setCronExpression("0 * * ? * * *");

    JobDetail jobDetail = JobBuilder.newJob(CronTest.class)
                            .withIdentity("Testjob")
                            .build()
                            ;

    tfb.setJobDetail(jobDetail);
    return tfb;
  }

}

Как видите, у вас есть планировщик и простой тестовый триггер, который определяется через выражение cron. Очевидно, вы можете выбрать любое выражение планирования, которое вам нравится. Затем вам понадобится AutowiringSpringBeanJobFactory, который выглядит следующим образом

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

  @Autowired
  private ApplicationContext applicationContext;

  private SchedulerContext schedulerContext;

  @Override
  public void setApplicationContext(final ApplicationContext context) {
    this.applicationContext = context;
  }


  @Override
  protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
    Job job = applicationContext.getBean(bundle.getJobDetail().getJobClass());
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);

    MutablePropertyValues pvs = new MutablePropertyValues();
    pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
    pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());

    if (this.schedulerContext != null)
    {
        pvs.addPropertyValues(this.schedulerContext);
    }
    bw.setPropertyValues(pvs, true);

    return job;
  }  

  public void setSchedulerContext(SchedulerContext schedulerContext) {
    this.schedulerContext = schedulerContext;
    super.setSchedulerContext(schedulerContext);
  }

}

Здесь вы объединяете свой обычный контекст приложения и свою работу. Это важный пробел, потому что обычно Quartz запускает свои рабочие потоки, которые не связаны с контекстом вашего приложения. Это причина, по которой вы не можете выполнять транзакционные методы. Последнее, чего не хватает, - это работы. Это может выглядеть так

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

  @Override
  public void execute(JobExecutionContext context) throws JobExecutionException {
    s.execute();
  }

}

Это не идеальное решение, потому что вы дополнительный класс только для вызова вашего метода обслуживания. Но тем не менее работает.


0

Jdbc jobstore

Если вы используете jdbc jobstore, Quartz использует другой загрузчик классов. Это предотвращает все обходные пути для автоматического подключения, поскольку объекты из Spring не будут совместимы на стороне кварца, потому что они происходят из другого загрузчика классов.

Чтобы исправить это, загрузчик классов по умолчанию должен быть установлен в файле свойств кварца следующим образом:

org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.ThreadContextClassLoadHelper

Как ссылка: https://github.com/quartz-scheduler/quartz/issues/221


0

Просто продлите свою работу с QuartzJobBean

public class MyJob extends QuartzJobBean {

    @Autowired
    private SomeBean someBean;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Some bean is " + someBean.toString());
    }

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.