injecter une référence de haricot dans un travail Quartz au printemps?


94

J'ai réussi à configurer et à planifier un travail Quartz à l'aide du magasin persistant JobStoreTX au printemps. Je n'utilise pas les travaux Quartz de Spring, car je dois les planifier dynamiquement, au moment de l'exécution, et tous les exemples d'intégration de Spring avec Quartz que j'ai trouvés codaient en dur les shcedules dans les fichiers de configuration Spring ... Quoi qu'il en soit, voici comment Je planifie le travail:

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 est un travail simple qui envoie des e-mails à l'aide de la classe JavaMailSenderImpl de 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");

}

Le problème est que j'ai besoin d'obtenir une référence à une instance de cette classe (JavaMailSenderImpl) dans ma classe EMailJob. Quand j'essaye de l'injecter comme ceci:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

il n'est pas injecté - la référence est NULL. Je suppose que cela se produit car ce n'est pas Spring qui instancie la classe EMailJob, mais Quartz et Quartz ne savent rien de l'injection de dépendances ...

Alors, y a-t-il un moyen de forcer cette injection à se produire?

Merci!

Mise à jour 1: @Aaron: voici une partie pertinente de la trace de pile depuis le démarrage, qui montre que EMailJob a été instancié deux fois:

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

Merci!

Mise à jour n ° 2: @Ryan:

J'ai essayé d'utiliser SpringBeanJobFactory comme suit:

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

Et j'ai modifié ma classe principale pour obtenir Scheduler de cette usine, au lieu de Quartz ':

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

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

Mais lorsque j'exécute l'application - obtenir des erreurs, voir ci-dessous. Voici le stacktrace du démarrage de Spring. On dirait que le planificateur lui-même est bien créé, mais l'erreur survient lorsqu'il essaie d'instancier mon 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)

Merci!

Réponses:


129

Vous pouvez l'utiliser SpringBeanJobFactorypour filer automatiquement des objets en quartz à l'aide d'un ressort:

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

Ensuite, attachez-le à votre SchedulerBean(dans ce cas, avec Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Travailler pour moi, en utilisant spring-3.2.1 et quartz-2.1.6.

Découvrez l'essentiel ici .

J'ai trouvé la solution dans ce billet de blog


13
Vous devriez gagner un prix pour cela, c'est fantastique!
Nathan Feger

2
La solution c'est vraiment génial! Tous les crédits à l'auteur du billet de blog :)
jelies

3
Merci - cela m'a sauvé des jours! Pourquoi Spring n'a-t-il pas fourni cet OOB. C'est la condition de base pour utiliser Quartz in Spring.
HandyManDan

4
cela devrait être l'implémentation par défaut :)
Diego Plentz

2
excellente solution, mais n'importe qui a une idée de la raison pour laquelle AutowireCapableBeanFactory beanFactory est marqué comme "transitoire"? AutowiringSpringBeanJobFactory ne semble pas être sérialisé de toute façon, donc beanFactory n'aura jamais besoin d'être sérialisé
Marios

57

Je viens de mettre SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);en première ligne de ma Job.execute(JobExecutionContext context)méthode.


7
C'est la vraie solution. Testé avec Spring 3.2.4.RELEASE et Quartz 2.2.0. ;)
aloplop85

3
@msangel - eh bien, les deux fonctionneront, mais le problème avec l'utilisation de SpringBeanAutowiringSupport dans votre travail Quartz, est que l'instance de travail doit maintenant CONNAÎTRE Spring, ce qui va à l'encontre de l'idée générale d'IoC (injection dep). Si vous avez maintenant par exemple besoin d'utiliser CDI, tous vos travaux de quartz devront être ajustés, au lieu d'une seule usine de travail.
demaniak

2
Cela n'a pas fonctionné pour moi dans un test unitaire car il recherche un contexte d'application Web. J'ai dû utiliser la réponse de @jelies
Wim Deblauwe

5
Cette solution ne fonctionne pas avec le ressort 4.1.4 et Quartz 2.2.1
skywalker

1
J'ai eu ce problème aussi et j'ai essayé cette solution. Cela fonctionne MAIS cela crée une nouvelle instance au lieu d'en utiliser une déjà créée (singleton par défaut). Quoi qu'il en soit, vous pouvez transmettre n'importe quoi à votre travail en utilisant scheduler.getContext (). Put ("objectName", object);
Krzysztof Cieśliński

13

Le même problème a été résolu dans LINK :

J'ai pu trouver une autre option à partir de la publication sur le forum Spring selon laquelle vous pouvez transmettre une référence au contexte de l'application Spring via SchedulerFactoryBean. Comme l'exemple ci-dessous:

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

Ensuite, en utilisant le code ci-dessous dans votre classe de travail, vous pouvez obtenir l'applicationContext et obtenir le bean de votre choix.

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

J'espère que ça aide. Vous pouvez obtenir plus d'informations sur le blog de Mark Mclaren


1
Merci, @Rippon! Après de nombreux essais et échecs, j'ai utilisé une approche très similaire à celle que vous avez suggérée: je n'ai pas utilisé la propriété applicationContextSchedulerContextKey et le contexte d'application, mais j'ai utilisé le 'code' <property name = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Marina

8

Vous avez raison dans votre hypothèse sur l'instanciation de la classe par Spring vs Quartz. Cependant, Spring fournit des classes qui vous permettent d'effectuer des injections de dépendances primitives dans Quartz. Découvrez SchedulerFactoryBean.setJobFactory () avec SpringBeanJobFactory . Essentiellement, en utilisant SpringBeanJobFactory, vous activez l'injection de dépendances sur toutes les propriétés du Job, mais uniquement pour les valeurs qui se trouvent dans le contexte du planificateur Quartz ou dans la mappe de données du job . Je ne sais pas ce que tous les styles DI qu'il prend en charge (constructeur, annotation, setter ...) mais je sais qu'il prend en charge l'injection de setter.


Salut Ryan, merci pour vos suggestions. Voulez-vous dire que je devrais utiliser SpringBeanFactoryJob, ainsi que des déclencheurs et des travaux préconfigurés qui étendent QuartzJobBean afin d'activer l'injection de dépendances? Le problème avec cette approche est que les déclencheurs sont définis statiquement dans les fichiers de configuration de Spring, où je dois être en mesure de définir des déclencheurs avec des horaires dynamiques au moment de l'exécution ... Voir ma prochaine réponse ci-dessous pour plus de détails - pas assez d'espace dans le zone de commentaires ...
Marina

@Marina: Non, ce n'est pas comme ça que ça marche. Utilisez SpringBeanJobFactory et faites-le comme vous le souhaitez. Cela fonctionnera simplement. De plus, ne publiez pas de réponse qui ne serait qu'une mise à jour de votre question. Modifiez plutôt votre question.
Ryan Stewart

désolé, je viens de remarquer votre commentaire! Je vais l'essayer comme vous le suggérez et vous informerai des résultats. Merci pour votre aide! Oh, et je vais essayer de modifier ma question au lieu de répondre ...
Marina

7

pour tous ceux qui essaieront cela à l'avenir.

org.springframework.scheduling.quartz.JobDetailBean fournit une carte des objets et ces objets peuvent être des haricots printaniers.

définir smth comme

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

et puis, à l'intérieur

public void executeInternal(JobExecutionContext context)

appelez myBean = (myBean) context.getMergedJobDataMap().get("myBean"); et vous êtes tous ensemble. Je sais, ça a l'air moche, mais comme solution de contournement ça marche


IMHO Je pense que cette solution est plus propre et «naturelle» que d'essayer d'ajouter la capacité de câblage automatique aux travaux de quartz, donc je ne pense pas que ce soit un travail.
reallynice

6
ApplicationContext springContext =

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

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

bean.method();

4

Merci, Rippon! J'ai enfin réussi à faire fonctionner cela aussi, après de nombreuses luttes, et ma solution est très proche de ce que vous avez suggéré! La clé était de créer mon propre Job pour étendre QuartzJobBean et d'utiliser le schedulerContextAsMap.

Je me suis échappé sans spécifier la propriété applicationContextSchedulerContextKey - cela a fonctionné sans cela pour moi.

Pour le bénéfice des autres, voici la configuration finale qui a fonctionné pour moi:

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

Notez que le bean 'mailService "est mon propre bean de service, géré par Spring. J'ai pu y accéder dans mon Job comme suit:

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

Et cette configuration m'a également permis de planifier dynamiquement les travaux, en utilisant des usines pour obtenir les déclencheurs et les détails des tâches et en définissant les paramètres requis sur eux par programme:

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

Merci encore à tous ceux qui ont aidé,

Marina


4

Une solution simple consiste à définir le bean spring dans la mappe de données de travail, puis à récupérer le bean dans la classe de travail, par exemple

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

considérant que les données de travail sont stockées en tant que blob (lors de l'utilisation de la persistance de la base de données), cela pourrait entraîner des problèmes de performances de la base de données (imaginez que les données sont vraiment énormes)
Sudip Bhandari

3

Voici à quoi ressemble le code avec @Component:

Classe principale qui planifie le travail:

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

L'EmailJob est le même que dans ma première publication à l'exception de l'annotation @Component:

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

Et le fichier de configuration du Spring contient:

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

Merci pour votre aide!

Marina


Lorsque votre application démarre, voyez-vous EmailJobêtre initialisé? Un moyen simple de vérifier est d'ajouter une ligne de journal dans le constructeur.
atrain

@Aaron: oui, je le fais - mais comme je viens de le découvrir, il est initialisé deux fois! Une fois par le framework Spring lui-même (et je parie que le service de messagerie est injecté dans cette instance ...) et ensuite, plus tard, après l'initialisation du Quartz lui-même - le EMailJob est à nouveau initialisé par le framework Quartz - et c'est celui qui n'a pas le service injecté ... Je vais essayer d'ajouter une trace de pile du démarrage de Spring en éditant ma question, comme Ryan l'a suggéré ...
Marina

2

Une solution de Hary https://stackoverflow.com/a/37797575/4252764 fonctionne très bien. C'est plus simple, ne nécessite pas autant de beans usine spéciaux et prend en charge plusieurs déclencheurs et tâches. J'ajouterais simplement que le travail Quartz peut être rendu générique, avec des travaux spécifiques implémentés en tant que beans Spring ordinaires.

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
  }

}

Merci. J'ai pensé à cela aussi. Mais dans mon cas, j'écrivais une bibliothèque qui résume toute implémentation de quartz. Ceci requis pour se souvenir du nom de la «clé» pour récupérer tous les objets. J'ai pu le faire à la manière du quartz pur et je l'ai juste posté comme réponse. Pls partagez vos pensées!
Karthik R

2

C'est un article assez ancien qui est toujours utile. Toutes les solutions que propose ces deux ont peu de condition qui ne conviennent pas à toutes:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Cela suppose ou nécessite que ce soit un projet basé sur le printemps
  • AutowiringSpringBeanJobFactory L'approche basée sur l'approche mentionnée dans la réponse précédente est très utile, mais la réponse est spécifique à ceux qui n'utilisent pas l'api de quartz vanille pur mais plutôt le wrapper de Spring pour que le quartz fasse de même.

Si vous souhaitez rester avec une implémentation pure de Quartz pour la planification (Quartz avec capacités de câblage automatique avec Spring), j'ai pu le faire comme suit:

Je cherchais à le faire autant que possible de manière quartz et donc peu de piratage s'avère 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);nous donne une instance de travail auto-câblée. Puisque AutowiringSpringBeanJobFactoryimplémente implicitement a JobFactory, nous avons maintenant activé une solution auto-câblable. J'espère que cela t'aides!


1

Un moyen simple de le faire serait d'annoter simplement les Jobs Quartz avec une @Componentannotation, puis Spring fera toute la magie DI pour vous, car il est maintenant reconnu comme un haricot de printemps. J'ai dû faire quelque chose de similaire pour un AspectJaspect - ce n'était pas un haricot de printemps jusqu'à ce que je l'ai annoté avec le @Componentstéréotype de printemps .


Merci, Aaron, je viens d'essayer - mais malheureusement, le même NPE se produit - et le service de messagerie n'est pas injecté dans le job bean ...
Marina

Votre EmailJobclasse est-elle dans un package qui serait analysé par Spring au démarrage de l'application? Le fait que vous ayez annoté avec @Componentmais que la classe injectée soit toujours nulle indique qu'elle n'est pas analysée - sinon la DI au démarrage de l'application lèverait une exception.
atrain

Aaron: oui, il est censé être scanné - j'ai le <context: component-scan base-package = "com.mybasepackage"> qui devrait le faire ... Dans ma prochaine réponse, je fournis un code complet de mon principal classe, avec la configuration Spring - juste au cas où quelque chose d'évident pourrait être repéré ...
Marina

Les champs de travail marqués avec "@Autowired" ne sont pas injectés même si vous marquez le travail avec "@Component"
aloplop85

6
Cela ne fonctionnera pas car la création d'objets Job est gérée par quarts et par conséquent les champs ne sont pas automatiquement câblés et les annotations de classe ne font rien sans traitement supplémentaire.
msangel

1

C'est la bonne réponse http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . et fonctionnera pour la plupart des gens. Mais si votre web.xml ne connaît pas tous les fichiers applicationContext.xml, le travail quartz ne pourra pas appeler ces beans. J'ai dû faire une couche supplémentaire pour injecter des fichiers d'application supplémentaires

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

Vous pouvez ajouter autant de fichiers contextuels que vous voulez que votre quartz prenne connaissance.


0

Assurez-vous que votre

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

la dépendance est tirée de

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

et PAS de

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

Il voulait que j'utilise

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

au lieu de

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

ainsi échouait à l'instance de travail d'autowire.


0

Lorsque vous utilisez déjà le vrai AspectJ dans votre projet, vous pouvez annoter la classe de bean de travail avec @Configurable. Ensuite, Spring injectera dans cette classe, même si elle est construite vianew


0

J'ai fait face au problème similaire et en suis sorti avec l'approche suivante:

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

Dans le code ci-dessus, j'injecte le bean dao.DAOFramework dans le bean JobA et dans la méthode ExecuteInternal, vous pouvez obtenir un bean injecté comme:

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

J'espère que ça aide! Je vous remercie.


0

La solution ci-dessus est excellente mais dans mon cas, l'injection ne fonctionnait pas. J'avais besoin d'utiliser autowireBeanProperties à la place, probablement en raison de la façon dont mon contexte est configuré:

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

Toutes ces solutions ci-dessus ne fonctionnent pas pour moi avec Spring 5 et Hibernate 5 et Quartz 2.2.3 lorsque je veux appeler des méthodes transactionnelles!

J'ai donc implémenté cette solution qui démarre automatiquement l'ordonnanceur et déclenche les jobs. J'ai trouvé beaucoup de ce code sur dzone . Comme je n'ai pas besoin de créer des déclencheurs et des travaux de manière dynamique, je voulais que les déclencheurs statiques soient prédéfinis via Spring Configuration et que seuls les travaux soient exposés en tant que composants Spring.

Ma configuration de base ressemble à ceci

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

}

Comme vous pouvez le voir, vous avez le planificateur et un simple déclencheur de test qui est défini via une expression cron. Vous pouvez évidemment choisir l'expression de planification que vous souhaitez. Vous avez alors besoin de l'AutowiringSpringBeanJobFactory qui va comme ceci

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

}

Ici, vous connectez votre contexte d'application normal et votre travail. C'est l'écart important car, normalement, Quartz démarre ses threads de travail qui n'ont aucune connexion avec votre contexte d'application. C'est la raison pour laquelle vous ne pouvez pas exécuter de mehtods transactionnels. La dernière chose qui manque est un travail. Ça peut ressembler à ça

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

Ce n'est pas une solution parfaite car vous êtes une classe supplémentaire uniquement pour appeler votre méthode de service. Mais néanmoins cela fonctionne.


0

Jobstore Jdbc

Si vous utilisez le jobstore jdbc, Quartz utilise un chargeur de classe différent. Cela empêche toutes les solutions de contournement pour le câblage automatique, car les objets du ressort ne seront pas compatibles côté quartz, car ils proviennent d'un chargeur de classe différent.

Pour résoudre ce problème, le chargeur de classe par défaut doit être défini dans le fichier de propriétés quartz comme ceci:

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

Comme référence: https://github.com/quartz-scheduler/quartz/issues/221


0

Prolongez simplement votre travail de 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());
    }

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.