menyuntikkan referensi kacang ke dalam pekerjaan Quartz di Spring?


94

Saya berhasil mengonfigurasi dan menjadwalkan pekerjaan Quartz menggunakan penyimpanan persisten JobStoreTX di Musim Semi. Saya tidak menggunakan pekerjaan Spring's Quartz, karena saya perlu menjadwalkannya secara dinamis, pada waktu proses, dan semua contoh pengintegrasian Spring dengan Quartz yang saya temukan adalah hard-coding shcedules di file konfigurasi Spring ... Bagaimanapun, inilah caranya Saya menjadwalkan pekerjaan:

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 adalah pekerjaan sederhana yang mengirim e-mail menggunakan kelas JavaMailSenderImpl Spring.

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

    public EMailJob() {
    }
    public void execute(JobExecutionContext context)
       throws JobExecutionException {
   ....
    try {
        mailSenderImpl.send(mimeMessage);
    } catch (MessagingException e) {
        ....
        throw new JobExecutionException("EMailJob failed: " +  jobKey.getName(), e);
    }

    logger.info("EMailJob finished OK");

}

Masalahnya adalah saya perlu mendapatkan referensi ke instance kelas ini (JavaMailSenderImpl) di kelas EMailJob saya. Ketika saya mencoba menyuntikkannya seperti ini:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

itu tidak disuntikkan - referensinya adalah NULL. Saya berasumsi ini terjadi karena bukan Spring yang membuat instance kelas EMailJob, tetapi Quartz, dan Quartz tidak tahu apa-apa tentang injeksi ketergantungan ...

Jadi, adakah cara untuk memaksa injeksi ini terjadi?

Terima kasih!

Pembaruan 1: @ Aaron: berikut adalah bagian yang relevan dari stacktrace dari startup, yang menunjukkan EMailJob dibuat dua kali:

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

Terima kasih!

Pembaruan # 2: @Ryan:

Saya mencoba menggunakan SpringBeanJobFactory sebagai berikut:

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

Dan saya telah memodifikasi kelas utama saya untuk mendapatkan Scheduler dari pabrik ini, bukan Quartz ':

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

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

Tetapi ketika saya menjalankan aplikasi - mendapatkan kesalahan, lihat di bawah. Berikut adalah stacktrace dari startup Spring. Sepertinya Penjadwal itu sendiri dibuat dengan baik, tetapi kesalahan datang ketika mencoba memberi contoh EMailJob saya:

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)

Terima kasih!

Jawaban:


129

Anda dapat menggunakan ini SpringBeanJobFactoryuntuk secara otomatis memasang objek kuarsa menggunakan pegas:

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

Kemudian, lampirkan ke Anda SchedulerBean(dalam hal ini, dengan Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Bekerja untuk saya, menggunakan pegas-3.2.1 dan kuarsa-2.1.6.

Lihat inti lengkapnya di sini .

Saya menemukan solusinya di posting blog ini


13
Anda harus memenangkan penghargaan untuk ini, ini luar biasa!
Nathan Feger

2
Solusinya sangat bagus! Semua kredit untuk penulis posting blog :)
jelies

3
Terima kasih - ini menyelamatkan saya berhari-hari! Mengapa Spring belum menyediakan OOB ini. Ini adalah persyaratan paling dasar untuk menggunakan Quartz di Spring.
HandyManDan

4
ini harus menjadi implementasi default :)
Diego Plentz

2
solusi yang bagus, tetapi ada yang tahu mengapa AutowireCapableBeanFactory beanFactory ditandai sebagai "transient"? AutowiringSpringBeanJobFactory tampaknya tidak diserialkan, jadi beanFactory juga tidak perlu diserialkan
Marios

57

Saya hanya menempatkan SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);sebagai baris pertama dari Job.execute(JobExecutionContext context)metode saya .


7
Inilah solusi sebenarnya. Diuji dengan Pegas 3.2.4.RELEASE dan Kuarsa 2.2.0. ;)
aloplop85

3
@msangel - baik, keduanya AKAN berfungsi, tetapi masalah dengan menggunakan SpringBeanAutowiringSupport dalam pekerjaan Quartz Anda, adalah bahwa contoh pekerjaan sekarang perlu TAHU tentang Spring, yang bertentangan dengan seluruh gagasan IoC (dep injection). Jika Anda sekarang misalnya perlu menggunakan CDI, semua pekerjaan kuarsa Anda perlu disesuaikan, bukan hanya satu pekerjaan pabrik.
demaniak

2
Ini tidak berhasil untuk saya dalam pengujian unit karena mencari konteks aplikasi web. Saya harus menggunakan jawaban dari @jelies
Wim Deblauwe

5
Solusi ini tidak berfungsi dengan pegas 4.1.4 dan Quartz 2.2.1
skywalker

1
Saya mengalami masalah ini juga dan saya mencoba solusi ini. Ini berfungsi TAPI itu membuat instance baru daripada menggunakan yang sudah dibuat (default tunggal). Bagaimanapun Anda bisa meneruskan apa pun ke pekerjaan Anda dengan menggunakan scheduler.getContext (). Put ("objectName", object);
Krzysztof Cieśliński

13

Masalah yang sama telah diselesaikan di LINK :

Saya dapat menemukan opsi lain dari posting di forum Spring bahwa Anda dapat memberikan referensi ke konteks aplikasi Spring melalui SchedulerFactoryBean. Seperti contoh yang ditunjukkan di bawah ini:

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

Kemudian menggunakan kode di bawah ini di kelas pekerjaan Anda, Anda bisa mendapatkan applicationContext dan kacang apa pun yang Anda inginkan.

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

Semoga membantu. Anda dapat memperoleh informasi lebih lanjut dari Mark Mclaren'sBlog


1
Terima kasih, @Rippon! Setelah banyak mencoba dan gagal, saya telah menggunakan pendekatan yang sangat mirip yang Anda sarankan: Saya belum menggunakan properti applicationContextSchedulerContextKey dan konteks aplikasi, tetapi saya menggunakan 'code' <property name = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Marina

8

Asumsi Anda benar tentang Spring vs. Quartz yang mendasari kelas. Namun, Spring menyediakan beberapa class yang memungkinkan Anda melakukan injeksi dependensi primitif di Quartz. Lihat SchedulerFactoryBean.setJobFactory () bersama dengan SpringBeanJobFactory . Pada dasarnya, dengan menggunakan SpringBeanJobFactory, Anda mengaktifkan injeksi ketergantungan pada semua properti Pekerjaan, tetapi hanya untuk nilai yang ada dalam konteks penjadwal Quartz atau peta data pekerjaan . Saya tidak tahu semua gaya DI yang didukungnya (konstruktor, anotasi, penyetel ...) tetapi saya tahu itu mendukung injeksi penyetel.


Hai, Ryan, terima kasih atas saran Anda. Apakah maksud Anda saya harus menggunakan SpringBeanFactoryJob, bersama dengan Pemicu yang telah dikonfigurasi sebelumnya dan pekerjaan yang memperluas QuartzJobBean untuk mengaktifkan injeksi ketergantungan? Masalah dengan pendekatan ini adalah bahwa pemicu didefinisikan secara statis di file konfigurasi Spring, di mana saya harus dapat menentukan pemicu dengan jadwal dinamis pada waktu proses ... Lihat jawaban saya berikutnya di bawah untuk lebih jelasnya - tidak cukup ruang di area komentar ...
Marina

@Marina: Tidak, bukan itu cara kerjanya. Gunakan SpringBeanJobFactory, dan lakukan sesuai keinginan Anda. Ini akan berhasil. Selain itu, jangan memposting jawaban yang hanya merupakan pembaruan untuk pertanyaan Anda. Edit pertanyaan Anda sebagai gantinya.
Ryan Stewart

maaf, saya baru memperhatikan komentar Anda! Saya akan mencobanya seperti yang Anda sarankan dan akan memberi tahu Anda hasilnya. Terima kasih atas bantuan Anda! Oh, dan saya akan mencoba mengedit pertanyaan saya alih-alih menjawab ...
Marina

7

untuk semua yang akan mencobanya di masa depan.

org.springframework.scheduling.quartz.JobDetailBean menyediakan peta objek dan objek tersebut mungkin kacang musim semi.

definisikan

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

dan kemudian, di dalam

public void executeInternal(JobExecutionContext context)

menelepon myBean = (myBean) context.getMergedJobDataMap().get("myBean"); dan Anda siap. Saya tahu, ini terlihat jelek, tetapi sebagai solusi, itu berhasil


IMHO Saya merasa solusi ini lebih bersih dan "alami" daripada mencoba menambahkan kemampuan autowiring ke pekerjaan kuarsa, jadi menurut saya ini bukan solusi.
reallynice

6
ApplicationContext springContext =

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

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

bean.method();

4

Terima kasih, Rippon! Saya akhirnya berhasil juga, setelah banyak perjuangan, dan solusi saya sangat dekat dengan apa yang Anda sarankan! Kuncinya adalah membuat Job saya sendiri untuk memperluas QuartzJobBean, dan menggunakan schedulerContextAsMap.

Saya berhasil lolos tanpa menentukan properti applicationContextSchedulerContextKey - ini berfungsi tanpa itu untuk saya.

Untuk kepentingan orang lain, berikut adalah konfigurasi terakhir yang berhasil untuk saya:

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

Perhatikan bahwa kacang 'mailService "adalah kacang layanan saya sendiri, yang dikelola oleh Spring. Saya dapat mengaksesnya dalam Pekerjaan saya sebagai berikut:

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

Dan konfigurasi ini juga memungkinkan saya untuk menjadwalkan pekerjaan secara dinamis, dengan menggunakan pabrik untuk mendapatkan Pemicu dan JobDetails serta mengatur parameter yang diperlukan pada mereka secara terprogram:

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

Terima kasih banyak lagi untuk semua orang yang telah membantu,

Marina


4

Solusi sederhana adalah dengan mengatur kacang pegas di Peta Data Pekerjaan dan kemudian mengambil kacang di kelas pekerjaan, misalnya

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

mengingat bahwa data pekerjaan disimpan sebagai blob (bila menggunakan persistensi db) hal ini dapat menyebabkan masalah kinerja db (Bayangkan data sangat besar)
Sudip Bhandari

3

Berikut adalah tampilan kode dengan @Component:

Kelas utama yang menjadwalkan pekerjaan:

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 sama seperti di posting pertama saya kecuali untuk penjelasan @Component:

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

Dan file konfigurasi Spring memiliki:

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

Terima kasih atas semua bantuannya!

Marina


Saat aplikasi Anda dimulai, apakah Anda melihat EmailJobsedang diinisialisasi? Cara mudah untuk memeriksanya adalah dengan menambahkan baris log di konstruktor.
atrain

@ Aaron: ya, saya lakukan - tetapi seperti yang baru saya temukan, itu sedang diinisialisasi dua kali! Sekali oleh kerangka kerja Spring itu sendiri (dan saya yakin instance ini memiliki layanan email yang dimasukkan ke dalamnya ...) dan kemudian, setelah Quartz itu sendiri diinisialisasi - EMailJob diinisialisasi lagi oleh kerangka Quartz - dan itulah satu-satunya yang tidak memiliki layanan yang diinjeksi ... Saya akan mencoba menambahkan jejak tumpukan startup Spring dengan mengedit pertanyaan saya, seperti yang disarankan Ryan ...
Marina

2

Solusi dari Hary https://stackoverflow.com/a/37797575/4252764 bekerja dengan sangat baik. Ini lebih sederhana, tidak memerlukan begitu banyak biji pabrik khusus, dan mendukung banyak pemicu dan pekerjaan. Hanya akan menambahkan bahwa pekerjaan Quartz dapat dibuat menjadi generik, dengan pekerjaan tertentu diimplementasikan sebagai kacang Spring biasa.

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
  }

}

Terima kasih. Saya mempertimbangkan itu juga. Tetapi dalam kasus saya, saya sedang menulis perpustakaan yang mengabstraksi implementasi kuarsa apa pun. Ini diperlukan untuk mengingat nama 'kunci' untuk mengambil objek apa pun. Saya bisa melakukannya dengan cara kuarsa murni dan hanya mempostingnya sebagai jawaban. Tolong bagikan pemikiran Anda!
Karthik R

2

Ini adalah posting lama yang masih berguna. Semua solusi yang mengusulkan keduanya memiliki kondisi kecil yang tidak cocok untuk semua:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Ini mengasumsikan atau membutuhkannya menjadi proyek berbasis web musim semi
  • AutowiringSpringBeanJobFactory Pendekatan berbasis yang disebutkan dalam jawaban sebelumnya sangat membantu, tetapi jawabannya khusus untuk mereka yang tidak menggunakan api kuarsa vanilla murni melainkan pembungkus pegas untuk kuarsa untuk melakukan hal yang sama.

Jika Anda ingin tetap menggunakan implementasi Quartz murni untuk penjadwalan (Quartz dengan kemampuan Autowiring dengan Spring), saya dapat melakukannya sebagai berikut:

Saya ingin melakukannya dengan cara kuarsa sebanyak mungkin dan dengan demikian sedikit peretasan terbukti membantu.

 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);memberi kami contoh pekerjaan autowired. Karena AutowiringSpringBeanJobFactorysecara implisit mengimplementasikan a JobFactory, kami sekarang mengaktifkan solusi kabel otomatis. Semoga ini membantu!


1

Cara sederhana untuk melakukannya adalah dengan memberi anotasi pada Quartz Jobs dengan @Componentanotasi, dan Spring akan melakukan semua keajaiban DI untuk Anda, karena sekarang dikenal sebagai kacang Spring. Saya harus melakukan sesuatu yang serupa untuk satu AspectJaspek - itu bukan kacang musim semi sampai saya menjelaskannya dengan @Componentstereotip Musim Semi .


Terima kasih, Aaron, saya baru saja mencoba - tetapi sayangnya NPE yang sama terjadi - dan layanan surat tidak disuntikkan ke dalam kacang pekerjaan ...
Marina

Apakah EmailJobkelas Anda ada dalam paket yang akan dipindai oleh Spring saat aplikasi dimulai? Fakta bahwa Anda memberi anotasi @Componenttetapi kelas yang diinjeksi masih null menunjukkan bahwa kelas tersebut tidak dipindai - jika tidak, DI saat startup aplikasi akan mengeluarkan pengecualian.
atrain

Aaron: ya, itu seharusnya dipindai - Saya memiliki <konteks: component-scan base-package = "com.mybasepackage"> yang harus melakukannya ... Dalam jawaban saya berikutnya, saya memberikan kode lengkap utama saya kelas, dengan konfigurasi Spring - kalau-kalau sesuatu yang jelas dapat terlihat ...
Marina

Bidang pekerjaan yang ditandai dengan "@Autowired" tidak dimasukkan bahkan jika Anda menandai Pekerjaan dengan "@Component"
aloplop85

6
Ini tidak akan berfungsi karena membuat objek Job dikelola oleh quart dan oleh karena itu bidang tidak dipasang secara otomatis dan anotasi kelas tidak melakukan apa-apa tanpa penanganan ekstra.
msangel

1

Ini adalah jawaban yang benar http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . dan akan berhasil untuk sebagian besar orang. Tetapi jika web.xml Anda tidak mengetahui semua file applicationContext.xml, pekerjaan kuarsa tidak akan dapat memanggil kacang tersebut. Saya harus melakukan lapisan tambahan untuk memasukkan file applicationContext tambahan

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

Anda dapat menambahkan sejumlah file konteks yang Anda ingin agar kuarsa diperhatikan.


0

Pastikan Anda

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

ketergantungan ditarik dari

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

dan BUKAN dari

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

Ia ingin saya menggunakan

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

dari pada

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

begitu juga gagal untuk melakukan autowire instance pekerjaan.


0

Ketika Anda sudah menggunakan AspectJ nyata dalam proyek Anda, maka Anda dapat membuat anotasi kelas kacang pekerjaan dengan @Configurable. Kemudian Spring akan menyuntikkan ke dalam kelas ini, meskipun itu dibangun melaluinew


0

Saya menghadapi masalah serupa dan keluar darinya dengan pendekatan berikut:

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

Pada kode di atas saya menyuntikkan kacang dao.DAOFramework ke dalam kacang JobA dan di dalam metode ExecuteInternal Anda bisa mendapatkan kacang yang disuntikkan seperti:

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

Saya harap ini membantu! Terima kasih.


0

Solusi di atas bagus tetapi dalam kasus saya injeksi tidak berfungsi. Saya perlu menggunakan autowireBeanProperties, mungkin karena cara konteks saya dikonfigurasi:

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

Semua solusi di atas tidak berfungsi untuk saya dengan Spring 5 dan Hibernate 5 dan Quartz 2.2.3 ketika saya ingin memanggil metode transaksional!

Oleh karena itu, saya menerapkan solusi ini yang secara otomatis memulai penjadwal dan memicu pekerjaan. Saya menemukan banyak kode itu di dzone . Karena saya tidak perlu membuat pemicu dan pekerjaan secara dinamis, saya ingin pemicu statis ditentukan sebelumnya melalui Konfigurasi Musim Semi dan hanya pekerjaan yang akan diekspos sebagai Komponen Musim Semi.

Konfigurasi dasar saya terlihat seperti ini

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

}

Seperti yang Anda lihat, Anda memiliki penjadwal dan pemicu pengujian sederhana yang ditentukan melalui ekspresi cron. Anda jelas dapat memilih ekspresi penjadwalan apa pun yang Anda suka. Anda kemudian membutuhkan AutowiringSpringBeanJobFactory yang bekerja seperti ini

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

}

Di sini Anda menghubungkan konteks lamaran normal dan pekerjaan Anda bersama-sama. Ini adalah celah penting karena biasanya Quartz memulai thread pekerja yang tidak memiliki koneksi ke konteks aplikasi Anda. Itulah alasan mengapa Anda tidak dapat menjalankan mehtod Transaksional. Hal terakhir yang hilang adalah pekerjaan. Ini bisa terlihat seperti itu

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

Ini bukan solusi sempurna karena Anda kelas tambahan hanya untuk memanggil metode layanan Anda. Tapi bagaimanapun itu berhasil.


0

Jobstore Jdbc

Jika Anda menggunakan jdbc jobstore, Quartz menggunakan classloader yang berbeda. Hal ini mencegah semua solusi untuk pemasangan kabel otomatis, karena objek dari pegas tidak akan kompatibel di sisi kuarsa, karena objek tersebut berasal dari loader kelas yang berbeda.

Untuk memperbaikinya, classloader default harus disetel di file properti kuarsa seperti ini:

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

Sebagai referensi: https://github.com/quartz-scheduler/quartz/issues/221


0

Cukup perpanjang pekerjaan Anda dari 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());
    }

}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.