inietta il riferimento di fagioli in un lavoro al quarzo in primavera?


94

Sono riuscito a configurare e programmare un lavoro Quartz utilizzando l'archivio persistente JobStoreTX in primavera. Non utilizzo i lavori Quartz di Spring, perché ho bisogno di programmarli dinamicamente, in fase di esecuzione, e tutti gli esempi di integrazione di Spring con Quartz che ho trovato erano hard-coding degli shcedule nei file di configurazione di Spring ... Comunque, ecco come Pianifico il lavoro:

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 è un lavoro semplice che invia e-mail utilizzando la classe JavaMailSenderImpl di 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");

}

Il problema è che ho bisogno di ottenere un riferimento a un'istanza di questa classe (JavaMailSenderImpl) nella mia classe EMailJob. Quando provo a iniettarlo in questo modo:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

non è iniettato - il riferimento è NULL. Suppongo che ciò stia accadendo perché non è Spring a creare un'istanza della classe EMailJob, ma Quartz, e Quartz non sa nulla dell'iniezione di dipendenza ...

Quindi, c'è un modo per forzare questa iniezione?

Grazie!

Aggiornamento 1: @Aaron: ecco una parte rilevante dello stacktrace all'avvio, che mostra che l'EMailJob è stato istanziato due volte:

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 ...

Grazie!

Aggiornamento n. 2: @Ryan:

Ho provato a utilizzare SpringBeanJobFactory come segue:

    <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>

E ho modificato la mia classe principale per ottenere Scheduler da questa fabbrica, invece di Quartz ':

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

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

Ma quando eseguo l'app, ottengo errori, vedi sotto. Ecco lo stacktrace dell'avvio di Spring. Sembra che lo Scheduler stesso sia stato creato correttamente, ma l'errore si verifica quando tenta di istanziare il mio 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)

Grazie!

Risposte:


129

Puoi usarlo SpringBeanJobFactoryper autowire automaticamente oggetti al quarzo usando la molla:

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

Quindi, allegalo al tuo SchedulerBean(in questo caso, con Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Funzionando per me, usando la primavera-3.2.1 e il quarzo-2.1.6.

Dai un'occhiata al succo completo qui .

Ho trovato la soluzione in questo post del blog


13
Dovresti vincere un premio per questo, è fantastico!
Nathan Feger

2
La soluzione è davvero fantastica! Tutti i ringraziamenti all'autore del post sul blog :)
jelies

3
Grazie, questo mi ha salvato giorni! Perché la primavera non ha fornito questo OOB. Questo è il requisito fondamentale per l'utilizzo del quarzo in primavera.
HandyManDan

4
questa dovrebbe essere l'implementazione predefinita :)
Diego Plentz

2
ottima soluzione, ma qualcuno ha idea del perché AutowireCapableBeanFactory beanFactory sia contrassegnato come "transitorio"? AutowiringSpringBeanJobFactory non sembra comunque essere serializzato, quindi beanFactory non avrà mai bisogno di essere serializzato
Marios

57

Ho appena messo SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);come prima riga del mio Job.execute(JobExecutionContext context)metodo.


7
Questa è la vera soluzione. Testato con Spring 3.2.4.RELEASE e Quartz 2.2.0. ;)
aloplop85

3
@msangel - beh, entrambi funzioneranno, ma il problema con l'utilizzo di SpringBeanAutowiringSupport nel tuo lavoro Quartz, è che l'istanza di lavoro ora deve CONOSCERE la primavera, il che va contro l'intera idea di IoC (dep injection). Se ora, ad esempio, è necessario utilizzare CDI, tutti i lavori al quarzo dovranno essere regolati, invece di una sola fabbrica di lavoro.
demaniak

2
Questo non ha funzionato per me in uno unit test poiché cerca un contesto di applicazione web. Ho dovuto usare la risposta di @jelies
Wim Deblauwe

5
Questa soluzione non funziona con la molla 4.1.4 e Quartz 2.2.1
skywalker

1
Anch'io ho avuto questo problema e ho provato questa soluzione. Funziona MA crea una nuova istanza invece di usarne una già creata (singleton predefinito). Ad ogni modo puoi passare qualsiasi cosa al tuo lavoro usando scheduler.getContext (). Put ("objectName", object);
Krzysztof Cieśliński

13

Lo stesso problema è stato risolto in LINK :

Ho trovato un'altra opzione dal post sul forum di Spring che puoi passare un riferimento al contesto dell'applicazione Spring tramite SchedulerFactoryBean. Come l'esempio mostrato di seguito:

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

Quindi utilizzando il codice seguente nella tua classe di lavoro puoi ottenere applicationContext e ottenere qualsiasi bean desideri.

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

Spero che sia d'aiuto. Puoi ottenere maggiori informazioni dal blog di Mark Mclaren


1
Grazie, @Rippon! Dopo molti tentativi e fallimenti, ho utilizzato un approccio molto simile che hai suggerito: non ho usato la proprietà applicationContextSchedulerContextKey e il contesto dell'applicazione, ma ho usato il 'codice' <property name = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Marina

8

Hai ragione nella tua ipotesi su Spring vs Quartz che istanziano la classe. Tuttavia, Spring fornisce alcune classi che consentono di eseguire alcune primitive iniezione di dipendenze in Quartz. Controlla SchedulerFactoryBean.setJobFactory () insieme a SpringBeanJobFactory . In sostanza, utilizzando SpringBeanJobFactory, si abilita l'inserimento delle dipendenze su tutte le proprietà del lavoro, ma solo per i valori che si trovano nel contesto dello scheduler Quartz o nella mappa dei dati del lavoro . Non so quali siano tutti gli stili DI che supporta (costruttore, annotazione, setter ...) ma so che supporta l'iniezione di setter.


Ciao, Ryan, grazie per i tuoi suggerimenti. Vuoi dire che dovrei usare SpringBeanFactoryJob, insieme a trigger e processi preconfigurati che estendono QuartzJobBean per abilitare l'inserimento delle dipendenze? Il problema con questo approccio è che i trigger sono definiti staticamente nei file di configurazione di Spring, dove ho bisogno di essere in grado di definire trigger con pianificazioni dinamiche in fase di esecuzione ... Vedi la mia prossima risposta di seguito per maggiori dettagli - Spazio insufficiente nel area commenti ...
Marina

@Marina: No, non è così che funziona. Usa SpringBeanJobFactory e fallo nel modo che preferisci. Funzionerà e basta. Inoltre, non pubblicare una risposta che è solo un aggiornamento alla tua domanda. Modifica invece la tua domanda.
Ryan Stewart

scusa, ho appena notato il tuo commento! Lo proverò come suggerisci e ti farò conoscere i risultati. Grazie per il vostro aiuto! Oh, e cercherò di modificare la mia domanda invece di rispondere ...
Marina

7

per tutti coloro che lo proveranno in futuro.

org.springframework.scheduling.quartz.JobDetailBean fornisce la mappa degli oggetti e questi oggetti possono essere fagioli primaverili.

definire smth come

<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>

e poi, dentro

public void executeInternal(JobExecutionContext context)

chiama myBean = (myBean) context.getMergedJobDataMap().get("myBean"); e sei pronto. Lo so, sembra brutto, ma come soluzione alternativa funziona


IMHO ritengo questa soluzione più pulita e "naturale" rispetto al tentativo di aggiungere la capacità di autowiring ai lavori al quarzo, quindi non penso che sia un aggiramento.
davvero bello

6
ApplicationContext springContext =

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

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

bean.method();

4

Grazie, Rippon! Finalmente anche questo ho funzionato, dopo molte lotte, e la mia soluzione è molto vicina a ciò che hai suggerito! La chiave era creare il mio lavoro per estendere QuartzJobBean e utilizzare lo schedulerContextAsMap.

Sono riuscito a farla franca senza specificare la proprietà applicationContextSchedulerContextKey - ha funzionato senza di essa per me.

A beneficio degli altri, ecco la configurazione finale che ha funzionato per me:

    <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>

Si noti che il bean "mailService" è il proprio bean di servizio, gestito da Spring. Sono stato in grado di accedervi nel mio lavoro come segue:

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

E questa configurazione mi ha anche permesso di schedulare dinamicamente i lavori, utilizzando le factory per ottenere Trigger e JobDetails e impostando i parametri richiesti su di essi a livello di programmazione:

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

Grazie mille ancora a tutti coloro che hanno aiutato,

Marina


4

Una soluzione semplice è impostare il bean di primavera nella mappa dati lavoro e quindi recuperare il bean nella classe di lavoro, ad esempio

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

considerando che i dati del lavoro sono archiviati come blob (quando si utilizza la persistenza db) ciò potrebbe portare a problemi di prestazioni del database (si immagini che i dati siano davvero enormi)
Sudip Bhandari

3

Ecco come appare il codice con @Component:

Classe principale che programma il lavoro:

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 è lo stesso del mio primo intervento tranne per l'annotazione @Component:

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

E il file di configurazione di Spring ha:

...
<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>

Grazie per tutto l'aiuto!

Marina


Quando la tua app si avvia, vedi che è EmailJobstata inizializzata? Un modo semplice per verificare è aggiungere una riga di registro nel costruttore.
atrain

@ Aaron: sì, ma come ho appena scoperto, viene inizializzato due volte! Una volta dal framework Spring stesso (e scommetto che questa istanza ha il servizio di posta inserito in esso ...) e poi, in seguito, dopo che il Quartz stesso è stato inizializzato - EMailJob viene inizializzato di nuovo dal framework Quartz - ed è quello che non ha il servizio iniettato ... cercherò di aggiungere una traccia dello stack dell'avvio di Spring modificando la mia domanda, come suggerito da Ryan ...
Marina

2

Una soluzione di Hary https://stackoverflow.com/a/37797575/4252764 funziona molto bene. È più semplice, non necessita di così tanti bean di fabbrica speciali e supporta più trigger e processi. Vorrei solo aggiungere che il lavoro Quartz può essere reso generico, con lavori specifici implementati come normali bean 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
  }

}

Grazie. Ho considerato anche questo. Ma nel mio caso, stavo scrivendo una libreria che astrae qualsiasi implementazione al quarzo. Ciò è necessario per ricordare il nome della "chiave" per recuperare gli oggetti. Sono stato in grado di farlo in modo puro al quarzo e l'ho appena pubblicato come risposta. Pls condividi i tuoi pensieri!
Karthik R

2

Questo è un post piuttosto vecchio che è ancora utile. Tutte le soluzioni che propone questi due avevano poche condizioni che non si adattano a tutte:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Ciò presuppone o richiede che sia un progetto basato sul web primaverile
  • AutowiringSpringBeanJobFactory l'approccio basato su menzionato nella risposta precedente è molto utile, ma la risposta è specifica per coloro che non usano l'API di quarzo vaniglia puro, ma piuttosto l'involucro di Spring per il quarzo per fare lo stesso.

Se vuoi rimanere con l'implementazione di Quartz pura per la pianificazione (Quartz con funzionalità di Autowiring con Spring), sono stato in grado di farlo come segue:

Stavo cercando di farlo al quarzo il più possibile e quindi un piccolo trucco si è rivelato utile.

 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);ci fornisce un'istanza di lavoro autowired. Poiché AutowiringSpringBeanJobFactoryimplementa implicitamente a JobFactory, ora abbiamo abilitato una soluzione con cablaggio automatico. Spero che questo ti aiuti!


1

Un modo semplice per farlo sarebbe semplicemente annotare i lavori al quarzo con @Componentannotazioni, quindi Spring eseguirà tutta la magia DI per te, poiché ora è riconosciuto come un bean Spring. Ho dovuto fare qualcosa di simile per un AspectJaspetto: non era un fagiolo primaverile finché non l'ho annotato con lo @Componentstereotipo della primavera .


Grazie, Aaron, ho appena provato - ma sfortunatamente accade lo stesso NPE - e il servizio di posta non viene iniettato nel bean di lavoro ...
Marina

La tua EmailJobclasse è in un pacchetto che verrebbe scansionato da Spring all'avvio dell'app? Il fatto che hai annotato con @Componentma la classe iniettata è ancora nulla indica che non viene scansionata, altrimenti il ​​DI all'avvio dell'app genererebbe un'eccezione.
atrain

Aaron: sì, dovrebbe essere scansionato - ho il <context: component-scan base-package = "com.mybasepackage"> che dovrebbe farlo ... Nella mia prossima risposta sto fornendo un codice completo del mio main classe, con la configurazione Spring - nel caso in cui qualcosa di ovvio possa essere individuato ...
Marina

I campi del lavoro contrassegnati con "@Autowired" non vengono inseriti anche se contrassegni il lavoro con "@Component"
aloplop85

6
Questo non funzionerà perché la creazione di oggetti Lavoro è gestita da quarti e quindi i campi non sono cablati automaticamente e le annotazioni di classe non fanno nulla senza una gestione extra.
msangel

1

Questa è la risposta giusta http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . e funzionerà per la maggior parte delle persone. Ma se il tuo web.xml non è a conoscenza di tutti i file applicationContext.xml, il lavoro quartz non sarà in grado di richiamare quei bean. Ho dovuto fare un livello aggiuntivo per iniettare file applicationContext aggiuntivi

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

Puoi aggiungere un numero qualsiasi di file di contesto di cui desideri che il tuo quarzo sia a conoscenza.


0

Assicurati che il tuo

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

la dipendenza viene estratta da

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

e NON da

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

Voleva che usassi

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

invece di

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

quindi non riusciva a eseguire il cablaggio automatico dell'istanza di lavoro.


0

Quando usi già AspectJ reale nel tuo progetto, puoi annotare la classe del bean di lavoro con @Configurable. Quindi Spring si inietterà in questa classe, anche se è costruita tramitenew


0

Ho affrontato il problema simile e ne sono uscito con il seguente approccio:

<!-- 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>

Nel codice sopra inietto il bean dao.DAOFramework nel bean JobA e nel metodo ExecuteInternal puoi ottenere un bean iniettato come:

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

Spero possa essere d'aiuto! Grazie.


0

La soluzione sopra è ottima, ma nel mio caso l'iniezione non funzionava. Avevo invece bisogno di usare autowireBeanProperties, probabilmente a causa del modo in cui è configurato il mio contesto:

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

Tutte quelle soluzioni sopra non funzionano per me con Spring 5 e Hibernate 5 e Quartz 2.2.3 quando voglio chiamare metodi transazionali!

Ho quindi implementato questa soluzione che avvia automaticamente lo scheduler e fa partire i lavori. Ho trovato molto di quel codice su dzone . Poiché non ho bisogno di creare trigger e processi dinamicamente, volevo che i trigger statici fossero predefiniti tramite Spring Configuration e solo i processi fossero esposti come Spring Components.

La mia configurazione di base assomiglia a questa

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

}

Come puoi vedere, hai lo scheduler e un semplice trigger di test definito tramite un'espressione cron. Ovviamente puoi scegliere qualsiasi espressione di pianificazione che ti piace. È quindi necessario AutowiringSpringBeanJobFactory che funziona in questo modo

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

}

Qui colleghi il tuo normale contesto applicativo e il tuo lavoro insieme. Questo è il divario importante perché normalmente Quartz avvia i suoi thread di lavoro che non hanno connessione al contesto dell'applicazione. Questo è il motivo per cui non puoi eseguire i metodi transazionali. L'ultima cosa che manca è un lavoro. Può assomigliare a quello

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

Non è una soluzione perfetta perché sei una classe in più solo per chiamare il tuo metodo di servizio. Ma nonostante tutto funziona.


0

Jdbc jobstore

Se stai usando jdbc jobstore, Quartz utilizza un diverso classloader. Ciò impedisce tutte le soluzioni alternative per il cablaggio automatico, poiché gli oggetti dalla primavera non saranno compatibili sul lato quarzo, perché hanno avuto origine da un caricatore di classi diverso.

Per risolvere questo problema, il classloader predefinito deve essere impostato nel file delle proprietà del quarzo in questo modo:

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

Come riferimento: https://github.com/quartz-scheduler/quartz/issues/221


0

Estendi semplicemente il tuo lavoro da 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());
    }

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.