вводити посилання на квасолі на роботу з кварцу навесні?


94

Мені вдалося налаштувати та запланувати роботу з кварцу за допомогою постійного магазину JobStoreTX навесні. Я не використовую кварцові завдання Spring, тому що мені потрібно планувати їх динамічно, під час виконання, і всі приклади інтеграції 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>

І я змінив свій основний клас, щоб отримувати Планувальник з цієї фабрики, замість 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)

Дякую!

Відповіді:


129

Ви можете використовувати це 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
За це вам слід виграти нагороду, це фантастика!
Натан Фегер

2
Рішення це справді чудове! Всі автори допису в блозі :)
желе

3
Дякую - це врятувало мені дні! Чому Spring не надав цей OOB. Це дуже основна вимога для використання кварцу навесні.
HandyManDan

4
це має бути реалізацією за замовчуванням :)
Дієго Пленц

2
Прекрасне рішення, але хтось знає, чому AutowireCapableBeanFactory beanFactory позначений як "перехідний"? AutowiringSpringBeanJobFactory, схоже, все одно не серіалізується, тому ніколи не потрібно буде серіалізувати beanFactory
Маріос,

57

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


7
Це справжнє рішення. Тестується за допомогою пружини 3.2.4.RELEASE та кварцу 2.2.0. ;)
aloplop85

3
@msangel - ну, обидва БУДУть працювати, але проблема використання SpringBeanAutowiringSupport у вашому кварцовому завданні полягає в тому, що екземпляру завдання тепер потрібно ЗНАТИ про Spring, що суперечить всій ідеї IoC (деп введення). Якщо зараз вам, наприклад, потрібно використовувати CDI, всі ваші кварцові завдання потрібно буде скоригувати, а не лише одну фабрику.
demaniak

2
Це не спрацювало для мене в модульному тесті, оскільки він шукає контекст веб-програми. Мені довелося використати відповідь від @jelies
Вім Деблауве

5
Це рішення не працює з пружиною 4.1.4 та кварцом 2.2.1
skywalker

1
У мене була така проблема, і я спробував це рішення. Він працює, АЛЕ створює новий екземпляр замість того, щоб використовувати вже створений (за замовчуванням синглтон). У будь-якому випадку ви можете передати будь-що на свою роботу, використовуючи scheduler.getContext (). Put ("objectName", object);
Krzysztof Cieśliński

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 та контекст програми, але використовував 'код' <ім'я властивості = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Марина,

8

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


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

@Marina: Ні, це не так працює. Використовуйте SpringBeanJobFactory і робіть це так, як хочете. Це просто спрацює. Також не публікуйте відповідь, яка є лише оновленням вашого запитання. Натомість відредагуйте своє запитання.
Райан Стюарт,

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

7

для всіх, хто спробує це в майбутньому.

org.springframework.scheduling.quartz.JobDetailBean карта поставок об’єктів, і ці об’єкти можуть бути ярими бобами.

визначити щось подібне

<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"); і все готово. Я знаю, це виглядає потворно, але як обхідний спосіб це працює


IMHO Я вважаю, що це рішення є більш чистим і "природним", ніж спроби додати можливість автоматичного підключення до кварцових завдань, тому я не думаю, що це обхід.
reallynice

6
ApplicationContext springContext =

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

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

bean.method();

4

Дякую, Ріппон! Я нарешті отримав це результативне, після багатьох боротьб, і моє рішення дуже близьке до того, що ви запропонували! Ключем було зробити власну роботу, щоб розширити QuartzJobBean, і використовувати планувальникContextAsMap.

Я врятувався, не вказавши властивість 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>

Зверніть увагу, що компонент 'mailService "- це мій власний службовий компонент, яким керує Spring. Я мав доступ до нього у своїй роботі наступним чином:

    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");
    ....

І ця конфігурація також дозволила мені динамічно планувати завдання, використовуючи фабрики для отримання Triggers та 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

Просте рішення - встановити спринковий бін у Карті даних про роботу, а потім отримати бін у класі завдання, наприклад

// 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");

враховуючи, що дані про роботу зберігаються як BLOB (при використанні постійності
баз

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: так, я знаю - але, як я щойно виявив, він ініціалізується двічі! Одного разу самим фреймворком (і я впевнений, що цей екземпляр в нього вводить поштову службу ...), а потім, пізніше, після ініціалізації самого кварцу - EMailJob знову ініціалізується кварцовим фреймворком - і це той що служба не введена ... Я спробую додати стек запуску Spring, відредагувавши своє запитання, як запропонував Райан ...
Марина,

2

Рішення від Hary https://stackoverflow.com/a/37797575/4252764 працює дуже добре. Це простіше, не потребує стільки спеціальних заводських бобів, а також підтримує кілька тригерів та завдань. Додамо лише, що кварцові завдання можна зробити загальними, конкретні завдання реалізують як звичайні весняні боби.

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, щоб кварц робив те саме.

Якщо ви хочете залишитися з чистою реалізацією кварцу для планування (Кварц з можливостями автоматичного підключення з 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

Простим способом це було б просто анотувати @Componentанотацію кварцових робочих місць , і тоді Spring зробить за вас всю магію DI, оскільки це зараз визнано як Spring bean. Мені довелося зробити щось подібне для одного AspectJаспекту - це не був весняний боб, поки я не анотував його @Componentстереотипом Весна .


Дякую, Аароне, я щойно спробував - але, на жаль, трапляється той самий NPE - і поштова служба не вводиться в робочий біг ...
Марина

Ваш EmailJobклас у пакеті, який буде сканований Spring при запуску програми? Той факт, що ви коментували, @Componentале введений клас все ще є нульовим, вказує на те, що він не сканується - інакше DI при запуску програми видасть виняток.
atrain

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

Поля завдання, позначені "@Autowired", не вставляються, навіть якщо Ви позначили Роботу "@Component"
aloplop85,

6
Це не спрацює, оскільки створення об’єктів Завдання управляється квартами, а отже поля не підключаються автоматично, а анотації класів нічого не роблять без додаткової обробки.
msangel

1

Це правильна відповідь http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . і буде працювати для більшості людей. Але якщо ваш web.xml знає не всі файли applicationContext.xml, кварцове завдання не зможе викликати ці компоненти. Мені довелося зробити додатковий шар, щоб ввести додаткові файли 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 у своєму проекті, тоді ви можете коментувати клас bean-завдання @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>

У наведеному вище коді я ввожу dao.DAOFramework bean в JobA bean, а всередині методу ExecuteInternal ви можете отримати ін'єктований bean, як:

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

Сподіваюся, це допоможе! Дякую.


0

Рішення вище є чудовим, але в моєму випадку ін’єкція не спрацювала. Мені потрібно було використовувати 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;
    }
}

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

Якщо ви використовуєте jdbc jobstore, Quartz використовує інший завантажувач класів. Це запобігає всі обхідні шляхи для автоматичного підключення, оскільки предмети з пружини не будуть сумісні на кварцовій стороні, оскільки вони походять від завантажувача іншого класу.

Щоб це виправити, завантажувач класів за замовчуванням повинен бути встановлений у файлі властивостей кварцу таким чином:

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.