Добавить ссылку на компонент в работу Quartz весной?
Мне удалось настроить и запланировать задание Quartz с помощью постоянного хранилища JobStoreTX в Spring. Я не использую задания Spring в Quartz, потому что мне нужно планировать их динамически, во время выполнения, и все примеры интеграции Spring с Quartz, которые я обнаружил, были жестко запрограммированы в расписания конфигурационных файлов Spring... В любом случае, вот как Я планирую работу:
JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
.withIdentity("someJobKey", "immediateEmailsGroup")
.storeDurably()
.build();
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("someTriggerKey", "immediateEmailsGroup")
.startAt(fireTime)
.build();
// pass initialization parameters into the job
emailJob.getJobDataMap().put(NotificationConstants.MESSAGE_PARAMETERS_KEY, messageParameters);
emailJob.getJobDataMap().put(NotificationConstants.RECIPIENT_KEY, recipient);
if (!scheduler.checkExists(jobKey) && scheduler.getTrigger(triggerKey) != null) {
// schedule the job to run
Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
}
EMailJob - это простая работа, которая отправляет электронную почту с помощью класса SpringMail JavaMailSenderImpl.
public class EMailJob implements Job {
@Autowired
private JavaMailSenderImpl mailSenderImpl;
public EMailJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
....
try {
mailSenderImpl.send(mimeMessage);
} catch (MessagingException e) {
....
throw new JobExecutionException("EMailJob failed: " + jobKey.getName(), e);
}
logger.info("EMailJob finished OK");
}
Проблема в том, что мне нужно получить ссылку на экземпляр этого класса (JavaMailSenderImpl) в моем классе EMailJob. Когда я пытаюсь ввести это так:
@Autowired
private JavaMailSenderImpl mailSenderImpl;
он не вводится - ссылка NULL. Я предполагаю, что это происходит потому, что не экземпляр Spring создает экземпляр класса EMailJob, а Quartz, а Quartz ничего не знает о внедрении зависимостей...
Итак, есть ли способ заставить эту инъекцию произойти?
Спасибо!
Обновление 1: @Aaron: вот соответствующая часть трассировки стека от запуска, которая показывает, что EMailJob был создан дважды:
2011-08-15 14:16:38,687 [main] INFO org.springframework.context.support.GenericApplicationContext - Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler#0' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2011-08-15 14:16:38,734 [main] INFO org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1328c7a: defining beans [...]; root of factory hierarchy
2011-08-15 14:16:39,734 [main] INFO com.cambridgedata.notifications.EMailJob - EMailJob() - initializing ...
2011-08-15 14:16:39,937 [main] INFO org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Validated configuration attributes
2011-08-15 14:16:40,078 [main] INFO org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Validated configuration attributes
2011-08-15 14:16:40,296 [main] INFO org.springframework.jdbc.datasource.init.ResourceDatabasePopulator - Executing SQL script from class path resource ...
2011-08-15 14:17:14,031 [main] INFO com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 14:17:14,109 [main] INFO com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 14:17:14,171 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 14:17:14,171 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 14:17:14,187 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'NotificationsScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'NotificationsScheduler' initialized from the specified file : 'spring/quartz.properties' from the class resource path.
2011-08-15 14:17:14,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 14:17:14,234 [main] INFO com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 2sajb28h1lcabf28k3nr1|13af084, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 2sajb28h1lcabf28k3nr1|13af084, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 14:17:14,312 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 14:17:14,328 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 14:17:14,328 [main] INFO org.quartz.core.QuartzScheduler - Scheduler NotificationsScheduler_$_NON_CLUSTERED started.
2011-08-15 14:17:14,515 [NotificationsScheduler_QuartzSchedulerThread] INFO com.cambridgedata.notifications.EMailJob - EMailJob() - initializing ...
Спасибо!
Обновление № 2: @Ryan:
Я попытался использовать SpringBeanJobFactory следующим образом:
<bean id="jobFactoryBean" class="org.springframework.scheduling.quartz.SpringBeanJobFactory">
</bean>
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:spring/quartz.properties"/>
<property name="jobFactory" ref="jobFactoryBean"/>
</bean>
И я изменил свой основной класс, чтобы получить планировщик с этой фабрики вместо Quartz':
@PostConstruct
public void initNotificationScheduler() {
try {
//sf = new StdSchedulerFactory("spring/quartz.properties");
//scheduler = sf.getScheduler();
scheduler = schedulerFactoryBean.getScheduler();
scheduler.start();
....
Но когда я запускаю приложение - получаю ошибки, см. Ниже. Вот трассировка стека от запуска Spring. Похоже, сам Планировщик создан нормально, но ошибка возникает, когда он пытается создать экземпляр моего EMailJob:
2011-08-15 21:49:42,968 [main] INFO org.springframework.scheduling.quartz.SchedulerFactoryBean - Loading Quartz config from [class path resource [spring/quartz.properties]]
2011-08-15 21:49:43,031 [main] INFO com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 21:49:43,109 [main] INFO com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2011-08-15 21:49:43,187 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 21:49:43,187 [main] INFO org.quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@566633
2011-08-15 21:49:43,265 [main] INFO com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge13f8h1lsg7py1rg0iu0|1956391, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge13f8h1lsg7py1rg0iu0|1956391, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 21:49:43,343 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 21:49:43,359 [main] INFO org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 21:49:43,359 [main] INFO org.quartz.core.QuartzScheduler - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2011-08-15 21:49:43,562 [schedulerFactoryBean_QuartzSchedulerThread] ERROR org.quartz.core.ErrorLogger - An error occured instantiating job to be executed. job= 'immediateEmailsGroup.DEFAULT.jobFor_1000new1'
org.quartz.SchedulerException: Problem instantiating class 'com.cambridgedata.notifications.EMailJob' - [See nested exception: java.lang.AbstractMethodError: org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;]
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:141)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:381)
Caused by: java.lang.AbstractMethodError: org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:134)
Спасибо!
19 ответов
Просто краткий обзор решения, предоставленного jelies, и несколько других вещей, облегчающих интеграцию Spring+Quartz: http://codrspace.com/Khovansa/spring-quartz-with-a-database/
Вы можете использовать это SpringBeanJobFactory
для автоматического подключения кварцевых объектов с помощью пружины:
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
Затем прикрепите его к вашему SchedulerBean
(в данном случае с Java-config):
@Bean
public SchedulerFactoryBean quartzScheduler() {
SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
...
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
quartzScheduler.setJobFactory(jobFactory);
...
return quartzScheduler;
}
Работаю для меня, используя пружины-3.2.1 и кварц-2.1.6.
Проверьте полную суть здесь.
Я просто положил SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
как первая строка моего Job.execute(JobExecutionContext context)
метод.
Та же проблема была решена в LINK:
Я мог найти другой вариант из поста на форуме Spring, где вы можете передать ссылку на контекст приложения Spring через SchedulerFactoryBean. Как пример, показанный ниже:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyy name="triggers">
<list>
<ref bean="simpleTrigger"/>
</list>
</property>
<property name="applicationContextSchedulerContextKey">
<value>applicationContext</value>
</property>
Затем, используя приведенный ниже код в своем классе работы, вы можете получить applicationContext и получить любой боб, какой захотите.
appCtx = (ApplicationContext)context.getScheduler().getContext().get("applicationContextSchedulerContextKey");
Надеюсь, поможет. Вы можете получить больше информации из блога Марка Макларена
Вы правы в своем предположении о том, что Spring vs. Quartz создаёт класс. Тем не менее, Spring предоставляет некоторые классы, которые позволяют вам делать некоторые примитивные внедрения зависимостей в Quartz. Проверьте SchedulerFactoryBean.setJobFactory() вместе с SpringBeanJobFactory. По сути, используя SpringBeanJobFactory, вы включаете внедрение зависимостей для всех свойств задания, но только для значений, которые находятся в контексте планировщика Quartz или карте данных задания. Я не знаю, какие все стили DI он поддерживает (конструктор, аннотации, сеттер...), но я знаю, что он поддерживает инъекцию сеттера.
Для всех, кто попробует это в будущем.
org.springframework.scheduling.quartz.JobDetailBean предоставляет карту объектов, и эти объекты могут быть пружинными бобами.
определить что-л. как
<bean name="myJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass"
value="my.cool.class.myCoolJob" />
<property name="jobDataAsMap">
<map>
<entry key="myBean" value-ref="myBean" />
</map>
</property>
</bean>
а затем внутри
public void executeInternal(JobExecutionContext context)
вызов myBean = (myBean) context.getMergedJobDataMap().get("myBean");
и все готово Я знаю, это выглядит некрасиво, но как обходной путь, это работает
ApplicationContext springContext =
WebApplicationContextUtils.getWebApplicationContext(ContextLoaderListener .getCurrentWebApplicationContext().getServletContext());
Bean bean = (Bean) springContext.getBean("beanName");
bean.method();
Спасибо, Риппон! Я, наконец, тоже заработал, после многих трудностей, и мое решение очень близко к тому, что вы предложили! Ключ должен был сделать мою собственную работу по расширению QuartzJobBean и использовать schedulerContextAsMap.
Я ушел, не указав свойство applicationContextSchedulerContextKey - оно работало без меня.
Для пользы других, вот последняя конфигурация, которая сработала для меня:
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:spring/quartz.properties"/>
<property name="jobFactory">
<bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory" />
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="mailService" value-ref="mailService" />
</map>
</property>
</bean>
<bean id="jobTriggerFactory"
class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName">
<idref local="jobTrigger" />
</property>
</bean>
<bean id="jobTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
scope="prototype">
<property name="group" value="myJobs" />
<property name="description" value="myDescription" />
<property name="repeatCount" value="0" />
</bean>
<bean id="jobDetailFactory"
class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName">
<idref local="jobDetail" />
</property>
</bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"
scope="prototype">
<property name="jobClass" value="com.cambridgedata.notifications.EMailJob" />
<property name="volatility" value="false" />
<property name="durability" value="false" />
<property name="requestsRecovery" value="true" />
</bean>
<bean id="notificationScheduler" class="com.cambridgedata.notifications.NotificationScheduler">
<constructor-arg ref="quartzScheduler" />
<constructor-arg ref="jobDetailFactory" />
<constructor-arg ref="jobTriggerFactory" />
</bean>
Обратите внимание, что bean-компонент "mailService" - это мой собственный bean-компонент службы, управляемый Spring. Я смог получить к нему доступ в своей работе следующим образом:
public void executeInternal(JobExecutionContext context)
throws JobExecutionException {
logger.info("EMailJob started ...");
....
SchedulerContext schedulerContext = null;
try {
schedulerContext = context.getScheduler().getContext();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
MailService mailService = (MailService)schedulerContext.get("mailService");
....
И эта конфигурация также позволила мне динамически планировать задания, используя фабрики для получения триггеров и JobDetails и устанавливая для них необходимые параметры программно:
public NotificationScheduler(final Scheduler scheduler,
final ObjectFactory<JobDetail> jobDetailFactory,
final ObjectFactory<SimpleTrigger> jobTriggerFactory) {
this.scheduler = scheduler;
this.jobDetailFactory = jobDetailFactory;
this.jobTriggerFactory = jobTriggerFactory;
...
// create a trigger
SimpleTrigger trigger = jobTriggerFactory.getObject();
trigger.setRepeatInterval(0L);
trigger.setStartTime(new Date());
// create job details
JobDetail emailJob = jobDetailFactory.getObject();
emailJob.setName("new name");
emailJob.setGroup("immediateEmailsGroup");
...
Еще раз большое спасибо всем, кто помог,
Марина
Простое решение состоит в том, чтобы установить bean-компонент Spring на карте данных задания, а затем получить, например, bean-компонент в классе задания.
// 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");
Вот как выглядит код с @Component:
Основной класс, который планирует работу:
public class NotificationScheduler {
private SchedulerFactory sf;
private Scheduler scheduler;
@PostConstruct
public void initNotificationScheduler() {
try {
sf = new StdSchedulerFactory("spring/quartz.properties");
scheduler = sf.getScheduler();
scheduler.start();
// test out sending a notification at startup, prepare some parameters...
this.scheduleImmediateNotificationJob(messageParameters, recipients);
try {
// wait 20 seconds to show jobs
logger.info("sleeping...");
Thread.sleep(40L * 1000L);
logger.info("finished sleeping");
// executing...
} catch (Exception ignore) {
}
} catch (SchedulerException e) {
e.printStackTrace();
throw new RuntimeException("NotificationScheduler failed to retrieve a Scheduler instance: ", e);
}
}
public void scheduleImmediateNotificationJob(){
try {
JobKey jobKey = new JobKey("key");
Date fireTime = DateBuilder.futureDate(delayInSeconds, IntervalUnit.SECOND);
JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
.withIdentity(jobKey.toString(), "immediateEmailsGroup")
.build();
TriggerKey triggerKey = new TriggerKey("triggerKey");
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity(triggerKey.toString(), "immediateEmailsGroup")
.startAt(fireTime)
.build();
// schedule the job to run
Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
} catch (SchedulerException e) {
logger.error("error scheduling job: " + e.getMessage(), e);
e.printStackTrace();
}
}
@PreDestroy
public void cleanup(){
sf = null;
try {
scheduler.shutdown();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
EmailJob такой же, как в моей первой публикации, за исключением аннотации @Component:
@Component
public class EMailJob implements Job {
@Autowired
private JavaMailSenderImpl mailSenderImpl;
... }
И конфигурационный файл Spring имеет:
...
<context:property-placeholder location="classpath:spring/*.properties" />
<context:spring-configured/>
<context:component-scan base-package="com.mybasepackage">
<context:exclude-filter expression="org.springframework.stereotype.Controller"
type="annotation" />
</context:component-scan>
<bean id="mailSenderImpl" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${mail.host}"/>
<property name="port" value="${mail.port}"/>
...
</bean>
<bean id="notificationScheduler" class="com.mybasepackage.notifications.NotificationScheduler">
</bean>
Спасибо за помощь!
Марина
Это довольно старый пост, который все еще полезен. Все решения, которые предлагают эти два, имели небольшое условие, которое не подходит всем:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
Это предполагает или требует, чтобы это был весенний веб-проектAutowiringSpringBeanJobFactory
Подход на основе, упомянутый в предыдущем ответе, очень полезен, но ответ специфичен для тех, кто использует не чистый ванильный кварцевый API, а оболочку Spring, чтобы кварц делал то же самое.
Если вы хотите остаться с чистой реализацией Quartz для планирования (Quartz с возможностями Autowiring в Spring), я смог сделать это следующим образом:
Я искал, чтобы сделать это кварцевым способом как можно больше, и таким образом небольшой взлом оказывается полезным.
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory{
private AutowireCapableBeanFactory beanFactory;
public AutowiringSpringBeanJobFactory(final ApplicationContext applicationContext){
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
beanFactory.initializeBean(job, job.getClass().getName());
return job;
}
}
@Configuration
public class SchedulerConfig {
@Autowired private ApplicationContext applicationContext;
@Bean
public AutowiringSpringBeanJobFactory getAutowiringSpringBeanJobFactory(){
return new AutowiringSpringBeanJobFactory(applicationContext);
}
}
private void initializeAndStartScheduler(final Properties quartzProperties)
throws SchedulerException {
//schedulerFactory.initialize(quartzProperties);
Scheduler quartzScheduler = schedulerFactory.getScheduler();
//Below one is the key here. Use the spring autowire capable job factory and inject here
quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
quartzScheduler.start();
}
quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
дает нам инстанцию с автозапуском поскольку AutowiringSpringBeanJobFactory
неявно реализует JobFactory
Теперь мы включили автоматическое решение. Надеюсь это поможет!
Решение от Хари /questions/5365933/dobavit-ssyilku-na-komponent-v-rabotu-quartz-vesnoj/5365960#5365960 работает очень хорошо. Это проще, не требует большого количества специальных фабричных компонентов и поддерживает несколько триггеров и заданий. Просто добавлю, что задание Quartz можно сделать универсальным, а конкретные задания реализованы как обычные компоненты Spring.
public interface BeanJob {
void executeBeanJob();
}
public class GenericJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getMergedJobDataMap();
((BeanJob)dataMap.get("beanJob")).executeBeanJob();
}
}
@Component
public class RealJob implements BeanJob {
private SomeService service;
@Autowired
public RealJob(SomeService service) {
this.service = service;
}
@Override
public void executeBeanJob() {
//do do job with service
}
}
Решение выше отличное, но в моем случае инъекция не работала. Вместо этого мне нужно было использовать autowireBeanProperties, вероятно, из-за того, как настроен мой контекст:
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
//beanFactory.autowireBean(job);
beanFactory.autowireBeanProperties(job, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
return job;
}
}
Это правильный ответ /questions/5365933/dobavit-ssyilku-na-komponent-v-rabotu-quartz-vesnoj/5365964#5365964. и будет работать для большинства людей. Но если ваш web.xml не знает обо всех файлах applicationContext.xml, кварцевое задание не сможет вызвать эти бины. Мне пришлось сделать дополнительный слой, чтобы добавить дополнительные файлы applicationContext
public class MYSpringBeanJobFactory extends SpringBeanJobFactory
implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
try {
PathMatchingResourcePatternResolver pmrl = new PathMatchingResourcePatternResolver(context.getClassLoader());
Resource[] resources = new Resource[0];
GenericApplicationContext createdContext = null ;
resources = pmrl.getResources(
"classpath*:my-abc-integration-applicationContext.xml"
);
for (Resource r : resources) {
createdContext = new GenericApplicationContext(context);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(createdContext);
int i = reader.loadBeanDefinitions(r);
}
createdContext.refresh();//important else you will get exceptions.
beanFactory = createdContext.getAutowireCapableBeanFactory();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)
throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
Вы можете добавить любое количество контекстных файлов, о которых должен знать ваш кварц.
Просто продлите свою работу с 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());
}
}
Простой способ сделать это было бы просто аннотировать Quartz Jobs с @Component
аннотации, и тогда Spring сделает всю магию DI для вас, так как теперь она распознается как bean-компонент Spring. Я должен был сделать что-то подобное для AspectJ
аспект - это не был боб Spring, пока я не аннотировал его с Spring @Component
стереотип.
Я столкнулся с подобной проблемой и вышел из нее следующим подходом:
<!-- Quartz Job -->
<bean name="JobA" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- <constructor-arg ref="dao.DAOFramework" /> -->
<property name="jobDataAsMap">
<map>
<entry key="daoBean" value-ref="dao.DAOFramework" />
</map>
</property>
<property name="jobClass" value="com.stratasync.jobs.JobA" />
<property name="durability" value="true"/>
</bean>
В приведенном выше коде я внедряю bean-компонент dao.DAOFramework в bean-компонент JobA, а внутри метода ExecuteInternal вы можете получить вставленный bean-компонент, например:
daoFramework = (DAOFramework)context.getMergedJobDataMap().get("daoBean");
Я надеюсь, что это помогает! Спасибо.
Jdbc JobStore
Если вы используете jdbc jobstore, Quartz использует другой загрузчик классов. Это предотвращает все обходные пути для автоматического подключения, так как объекты из пружины не будут совместимы на стороне кварца, потому что они возникли из загрузчика другого класса.
Чтобы это исправить, загрузчик классов по умолчанию должен быть установлен в файле свойств кварца следующим образом:
org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.ThreadContextClassLoadHelper
Как ссылка: https://github.com/quartz-scheduler/quartz/issues/221
Убедитесь, что ваш
AutowiringSpringBeanJobFactory extends SpringBeanJobFactory
зависимость вытягивается из
"org.springframework:spring-context-support:4..."
а не из
"org.springframework:spring-support:2..."
Он хотел, чтобы я использовал
@Override
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler)
вместо
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)
так не удалось автоматически подключить экземпляр задания.
Когда вы уже используете настоящий AspectJ в своем проекте, вы можете аннотировать класс bean-компонента задания с помощью @Configurable
, Затем Spring добавит в этот класс, даже если он построен с помощью new
Все эти решения выше не работают для меня с Spring 5 и Hibernate 5 и Quartz 2.2.3, когда я хочу вызвать транзакционные методы!
Поэтому я реализовал это решение, которое автоматически запускает планировщик и запускает задания. Я нашел много этого кода в dzone. Поскольку мне не нужно создавать триггеры и задания динамически, я хотел, чтобы статические триггеры были предварительно определены через Spring Configuration, и только задания были представлены как Spring Components.
Моя базовая конфигурация выглядит так
@Configuration
public class QuartzConfiguration {
@Autowired
ApplicationContext applicationContext;
@Bean
public SchedulerFactoryBean scheduler(@Autowired JobFactory jobFactory) throws IOException {
SchedulerFactoryBean sfb = new SchedulerFactoryBean();
sfb.setOverwriteExistingJobs(true);
sfb.setAutoStartup(true);
sfb.setJobFactory(jobFactory);
Trigger[] triggers = new Trigger[] {
cronTriggerTest().getObject()
};
sfb.setTriggers(triggers);
return sfb;
}
@Bean
public JobFactory cronJobFactory() {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
@Bean
public CronTriggerFactoryBean cronTriggerTest() {
CronTriggerFactoryBean tfb = new CronTriggerFactoryBean();
tfb.setCronExpression("0 * * ? * * *");
JobDetail jobDetail = JobBuilder.newJob(CronTest.class)
.withIdentity("Testjob")
.build()
;
tfb.setJobDetail(jobDetail);
return tfb;
}
}
Как видите, у вас есть планировщик и простой тестовый триггер, который определяется с помощью выражения cron. Очевидно, вы можете выбрать любое выражение расписания, которое вам нравится. Затем вам нужен AutowiringSpringBeanJobFactory, который выглядит следующим образом
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
@Autowired
private ApplicationContext applicationContext;
private SchedulerContext schedulerContext;
@Override
public void setApplicationContext(final ApplicationContext context) {
this.applicationContext = context;
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
Job job = applicationContext.getBean(bundle.getJobDetail().getJobClass());
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
if (this.schedulerContext != null)
{
pvs.addPropertyValues(this.schedulerContext);
}
bw.setPropertyValues(pvs, true);
return job;
}
public void setSchedulerContext(SchedulerContext schedulerContext) {
this.schedulerContext = schedulerContext;
super.setSchedulerContext(schedulerContext);
}
}
Здесь вы связываете обычный контекст приложения и свою работу вместе. Это важный пробел, потому что обычно Quartz запускает рабочие потоки, которые не связаны с контекстом вашего приложения. Вот почему вы не можете выполнять транзакционные методы. Последнее, чего не хватает, - это работа. Это может выглядеть так
@Component
public class CronTest implements Job {
@Autowired
private MyService s;
public CronTest() {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
s.execute();
}
}
Это не идеальное решение, потому что вы дополнительный класс только для вызова вашего метода обслуживания. Но тем не менее это работает.