J'ai réussi à configurer et à planifier un travail Quartz à l'aide du magasin persistant de JobStoreTX dans Spring. Je n'utilise pas les travaux Quartz de Spring, car je dois les planifier de manière dynamique, 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 programme 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 d'envoi de courrier électronique à l'aide de la classe JavaMailSenderImpl du 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 je dois obtenir une référence à une instance de cette classe (JavaMailSenderImpl) dans ma classe EMailJob. Quand j'essaye de l'injecter comme ça:
@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 sait rien de l'injection de dépendance ...
Alors, y a-t-il un moyen de forcer cette injection?
merci!
Mise à jour 1: @ Aaron: Voici une partie pertinente du stacktrace du démarrage, qui montre que le 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 # 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 je lance l'application, des erreurs se produisent, voir ci-dessous. Voici le stacktrace du démarrage de printemps. On dirait que le planificateur lui-même est bien créé, mais l'erreur survient lorsqu'il tente 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!
Vous pouvez utiliser cette SpringBeanJobFactory
pour connecter automatiquement des objets de quartz à l'aide de spring:
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 avec Spring-3.2.1 et quartz-2.1.6.
Découvrez le Gist complet ici .
J'ai trouvé la solution dans ce blog post
Je viens de mettre SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
comme première ligne de ma méthode Job.execute(JobExecutionContext context)
.
Le même problème a été résolu dans LINK :
J'ai pu trouver une autre option de publication sur le forum Spring consistant à faire référence au contexte de l'application Spring via SchedulerFactoryBean. Comme dans 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 d'emploi, vous pouvez obtenir l'applicationContext et obtenir le bean souhaité.
appCtx = (ApplicationContext)context.getScheduler().getContext().get("applicationContextSchedulerContextKey");
J'espère que cela vous aidera ... Vous pouvez obtenir plus d'informations de Mark Mclaren'sBlog
Vous avez raison de supposer que Spring vs. Quartz instancie la classe. Cependant, Spring fournit des classes qui vous permettent d’effectuer une injection de dépendance primitive dans Quartz. Consultez SchedulerFactoryBean.setJobFactory () avec SpringBeanJobFactory . En utilisant essentiellement SpringBeanJobFactory, vous activez l'injection de dépendance pour toutes les propriétés de Job, mais uniquement pour les valeurs qui se trouvent dans le contexte Quartz scheduler ou job data . Je ne sais pas ce que tous les styles de DI qu'il prend en charge (constructeur, annotation, setter ...), mais je sais qu'il prend en charge l'injection de setter.
pour tous ceux qui essaieront cela à l'avenir.
org.springframework.scheduling.quartz.JobDetailBeansupplies La carte des objets et ces objets peuvent être des haricots de printemps.
définir qch 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 ensuite, à l'intérieur
public void executeInternal(JobExecutionContext context)
appelez myBean = (myBean) context.getMergedJobDataMap().get("myBean");
et vous avez tous mis .. Je sais, ça a l'air moche, mais comme solution de contournement ça marche
ApplicationContext springContext =
WebApplicationContextUtils.getWebApplicationContext(ContextLoaderListener .getCurrentWebApplicationContext().getServletContext());
Bean bean = (Bean) springContext.getBean("beanName");
bean.method();
Merci, Rippon! J'ai enfin réussi à faire fonctionner cela aussi, après de nombreuses difficultés, et ma solution est très proche de celle que vous avez suggérée! La clé était de créer mon propre travail pour étendre QuartzJobBean et d’utiliser le schedulerContextAsMap.
Je me suis échappé sans spécifier la propriété applicationContextSchedulerContextKey - cela a fonctionné sans elle.
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 en tant que suivant:
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 de manière dynamique des travaux, en utilisant des usines pour obtenir des déclencheurs et des détails de travail, et en définissant les paramètres requis sur celles-ci 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
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();
}
}
EmailJob est identique à celui de 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 a:
...
<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
Une solution de Hary https://stackoverflow.com/a/37797575/4252764 / fonctionne très bien. C'est plus simple, il ne nécessite pas autant de beans d'usine spéciaux et prend en charge plusieurs déclencheurs et tâches… .. Ajoutons simplement que la tâche Quartz peut être générique, avec des tâches spécifiques implémentées sous forme de 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
}
}
Une solution simple consiste à définir le bean de printemps dans le mappage 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");
C'est un article assez ancien qui est toujours utile. Toutes les solutions proposées par ces deux-là avaient peu de condition qui ne suive pas toutes:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
Cela suppose ou nécessite qu’il s’agisse d’un projet basé sur le Web - SpringAutowiringSpringBeanJobFactory
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 à quartz vanille pure mais plutôt l'enveloppe de Spring pour que le quartz en fasse de même.Si vous souhaitez conserver une implémentation de Quartz pure pour la planification (Quartz avec des capacités de câblage automatique avec Spring), j'ai pu le faire comme suit:
Je cherchais à le faire autant que possible en quartz et, par conséquent, un petit 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. Étant donné que AutowiringSpringBeanJobFactory
implémente implicitement une JobFactory
, nous avons maintenant activé une solution auto-câblable. J'espère que cela t'aides!
J'ai fait face au même problème et j'en suis ressorti 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 un haricot dao.DAOFramework dans un haricot JobA et, dans la méthode ExecuteInternal, vous pouvez obtenir un haricot injecté comme:
daoFramework = (DAOFramework)context.getMergedJobDataMap().get("daoBean");
J'espère que ça aide! Je vous remercie.
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 configuration de mon contexte:
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;
}
}
Un moyen simple de le faire serait simplement d'annoter les tâches Quartz avec l'annotation @Component
, puis Spring effectuera toute la magie de la DI pour vous, car elle est maintenant reconnue comme un haricot Spring. Je devais faire quelque chose de similaire pour un aspect AspectJ
- ce n'était pas un haricot Spring avant de l'annoter avec le stéréotype Spring @Component
.
Étendez 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());
}
}
Lorsque vous utilisez déjà le véritable AspectJ dans votre projet, vous pouvez annoter la classe de bean de travail avec @Configurable
. Alors Spring injectera dans cette classe, même si elle est construite avec new
Jdbc jobstore
Si vous utilisez le magasin de travaux jdbc, Quartz utilise un chargeur de classes différent. Cela évite toutes les solutions de contournement du câblage automatique, car les objets du printemps 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 classes par défaut doit être défini dans le fichier de propriétés quartz comme suit:
org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.ThreadContextClassLoadHelper
À titre de référence: https://github.com/quartz-scheduler/quartz/issues/221
C'est la bonne réponse http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . et travaillera pour la plupart des gens. Mais si votre fichier web.xml ne connaît pas tous les fichiers applicationContext.xml, le travail quartz ne pourra pas appeler ces beans. Je devais faire une couche supplémentaire pour injecter des fichiers applicationContext 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 de contexte que vous souhaitez que votre quartz connaisse.
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)
donc échouait à l'instance de travail autowire.
Toutes les solutions ci-dessus ne fonctionnent pas avec Spring 5, Hibernate 5 et Quartz 2.2.3 lorsque je souhaite appeler des méthodes transactionnelles!
J'ai donc implémenté cette solution qui démarre automatiquement le planificateur et déclenche les travaux. J'ai trouvé beaucoup de ce code à dzone . Comme je n'ai pas besoin de créer de déclencheurs et de travaux de manière dynamique, je souhaitais 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 constater, vous avez le planificateur et un déclencheur de test simple, défini via une expression cron. Vous pouvez évidemment choisir l'expression de planification que vous préférez. Vous avez ensuite besoin de AutowiringSpringBeanJobFactory qui se présente comme suit
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. Il s’agit là d’une lacune importante, car normalement, Quartz démarre avec des threads de travail qui n’ont aucun lien avec le contexte de votre 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 parce que vous avez une classe supplémentaire uniquement pour appeler votre méthode de service. Mais néanmoins cela fonctionne.