Bohnenreferenz in einen Quarzjob im Frühjahr injizieren?


92

Ich habe es geschafft, einen Quarzjob mit dem persistenten JobStoreTX-Speicher im Frühjahr zu konfigurieren und zu planen. Ich verwende keine Spring's Quartz-Jobs, da ich sie zur Laufzeit dynamisch planen muss und alle Beispiele für die Integration von Spring in Quartz, die ich gefunden habe, die Shcedules in den Spring-Konfigurationsdateien hartcodierten ... Wie auch immer, hier ist, wie Ich plane den Job:

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

Der EMailJob ist ein einfacher Job, bei dem E-Mails mit der JavaMailSenderImpl-Klasse von Spring gesendet werden.

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

}

Das Problem ist, dass ich einen Verweis auf eine Instanz dieser Klasse (JavaMailSenderImpl) in meiner EMailJob-Klasse erhalten muss. Wenn ich versuche, es so zu injizieren:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

es wird nicht injiziert - die Referenz ist NULL. Ich gehe davon aus, dass dies geschieht, weil nicht Spring die EMailJob-Klasse instanziiert, sondern Quartz, und Quartz weiß nichts über Abhängigkeitsinjektion ...

Gibt es eine Möglichkeit, diese Injektion zu erzwingen?

Vielen Dank!

Update 1: @Aaron: Hier ist ein relevanter Teil des Stacktraces vom Start, der zeigt, dass der EMailJob zweimal instanziiert wurde:

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

Vielen Dank!

Update Nr. 2: @Ryan:

Ich habe versucht, die SpringBeanJobFactory wie folgt zu verwenden:

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

Und ich habe meine Hauptklasse geändert, um Scheduler von dieser Fabrik anstelle von Quartz 'zu erhalten:

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

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

Aber wenn ich die App starte - erhalte Fehler, siehe unten. Hier ist der Stacktrace vom Spring-Start. Der Scheduler selbst scheint in Ordnung zu sein, aber der Fehler tritt auf, wenn versucht wird, meinen EMailJob zu instanziieren:

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)

Vielen Dank!

Antworten:


129

Mit dieser SpringBeanJobFactoryOption können Sie Quarzobjekte mithilfe der Feder automatisch automatisch verdrahten:

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

Hängen Sie es dann an Ihre an SchedulerBean(in diesem Fall mit Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Ich arbeite für mich mit Spring-3.2.1 und Quarz-2.1.6.

Schauen Sie sich hier das komplette Wesentliche an .

Ich habe die Lösung in diesem Blogbeitrag gefunden


13
Sie sollten eine Auszeichnung dafür gewinnen, es ist fantastisch!
Nathan Feger

2
Die Lösung ist wirklich toll! Alle Credits an den Autor des Blogposts :)
jelies

3
Danke - das hat mir Tage erspart! Warum hat Spring dieses OOB nicht bereitgestellt? Dies ist die Grundvoraussetzung für die Verwendung von Quarz im Frühjahr.
HandyManDan

4
Dies sollte die Standardimplementierung sein :)
Diego Plentz

2
Tolle Lösung, aber jeder hat eine Idee, warum AutowireCapableBeanFactory beanFactory als "vorübergehend" markiert ist. AutowiringSpringBeanJobFactory scheint ohnehin nicht serialisiert zu sein, daher muss beanFactory auch nie serialisiert werden
Marios

57

Ich habe nur SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);als erste Zeile meiner Job.execute(JobExecutionContext context)Methode gesetzt.


7
Dies ist die wirkliche Lösung. Getestet mit Feder 3.2.4.FREIGABE und Quarz 2.2.0. ;)
aloplop85

3
@msangel - nun, beide werden funktionieren, aber das Problem bei der Verwendung von SpringBeanAutowiringSupport in Ihrem Quartz-Job ist, dass die Jobinstanz jetzt über Spring Bescheid wissen muss, was gegen die gesamte Idee von IoC (Dep Injection) verstößt. Wenn Sie jetzt beispielsweise CDI verwenden müssen, müssen alle Ihre Quarzjobs angepasst werden, anstatt nur die eine Jobfabrik.
Demaniak

2
Dies hat bei einem Unit-Test bei mir nicht funktioniert, da nach einem Webanwendungskontext gesucht wird. Ich musste die Antwort von @jelies
Wim Deblauwe

5
Diese Lösung funktioniert nicht mit Spring 4.1.4 und Quartz 2.2.1
Skywalker

1
Ich hatte auch dieses Problem und habe diese Lösung ausprobiert. Es funktioniert, ABER es erstellt eine neue Instanz, anstatt bereits erstellte zu verwenden (Standard-Singleton). Auf jeden Fall können Sie mit scheduler.getContext (). Put ("objectName", object) alles an Ihren Job übergeben.
Krzysztof Cieśliński

13

Das gleiche Problem wurde in LINK behoben :

Ich konnte eine andere Option aus dem Beitrag im Spring-Forum finden, mit der Sie über SchedulerFactoryBean einen Verweis auf den Spring-Anwendungskontext übergeben können. Wie das folgende Beispiel:

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

Wenn Sie dann den folgenden Code in Ihrer Jobklasse verwenden, können Sie den applicationContext abrufen und die gewünschte Bean abrufen.

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

Ich hoffe es hilft. Weitere Informationen erhalten Sie im Mark Mclaren'sBlog


1
Danke, @Rippon! Nach langem Ausprobieren habe ich einen sehr ähnlichen Ansatz verwendet, den Sie vorgeschlagen haben: Ich habe die Eigenschaft applicationContextSchedulerContextKey und den Anwendungskontext nicht verwendet, aber den Code '<property name = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </ map> </ property>
Marina

8

Sie haben Recht mit Ihrer Annahme, dass Spring vs. Quartz die Klasse instanziiert. Spring bietet jedoch einige Klassen, mit denen Sie eine primitive Abhängigkeitsinjektion in Quarz durchführen können. Schauen Sie sich SchedulerFactoryBean.setJobFactory () zusammen mit SpringBeanJobFactory an . Im Wesentlichen aktivieren Sie mithilfe der SpringBeanJobFactory die Abhängigkeitsinjektion für alle Job-Eigenschaften, jedoch nur für Werte, die sich im Quartz- Scheduler-Kontext oder in der Job-Datenzuordnung befinden . Ich weiß nicht, welche DI-Stile es unterstützt (Konstruktor, Annotation, Setter ...), aber ich weiß, dass es die Setter-Injection unterstützt.


Hallo Ryan, danke für deine Vorschläge. Meinen Sie damit, dass ich SpringBeanFactoryJob zusammen mit vorkonfigurierten Triggern und Jobs verwenden müsste, die QuartzJobBean erweitern, um die Abhängigkeitsinjektion zu ermöglichen? Das Problem bei diesem Ansatz ist, dass die Trigger statisch in den Konfigurationsdateien des Spring definiert sind, in denen ich Trigger mit dynamischen Zeitplänen zur Laufzeit definieren muss ... Weitere Informationen finden Sie in meiner nächsten Antwort unten - nicht genügend Speicherplatz in der Kommentarbereich ...
Marina

@Marina: Nein, so funktioniert das nicht. Verwenden Sie SpringBeanJobFactory und machen Sie es so, wie Sie es möchten. Es wird einfach funktionieren. Veröffentlichen Sie auch keine Antwort, die nur ein Update Ihrer Frage ist. Bearbeiten Sie stattdessen Ihre Frage.
Ryan Stewart

Entschuldigung, ich habe gerade Ihren Kommentar bemerkt! Ich werde es ausprobieren, wie Sie vorschlagen, und Sie über die Ergebnisse informieren. Vielen Dank für Ihre Hilfe! Oh, und ich werde versuchen, meine Frage zu bearbeiten, anstatt zu beantworten ...
Marina

7

für alle, die dies in Zukunft versuchen werden.

org.springframework.scheduling.quartz.JobDetailBean liefert eine Karte von Objekten, und diese Objekte können Spring Beans sein.

definiere etw wie

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

und dann drinnen

public void executeInternal(JobExecutionContext context)

Rufen myBean = (myBean) context.getMergedJobDataMap().get("myBean"); Sie an und Sie sind fertig. Ich weiß, es sieht hässlich aus, aber als Problemumgehung funktioniert es


Meiner Meinung nach ist diese Lösung sauberer und "natürlicher" als der Versuch, den Quarzjobs die Autowire-Funktion hinzuzufügen, daher denke ich nicht, dass es sich um eine Problemumgehung handelt.
wirklich schön

6
ApplicationContext springContext =

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

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

bean.method();

4

Danke, Rippon! Nach vielen Kämpfen habe ich es endlich auch geschafft, und meine Lösung kommt dem, was Sie vorgeschlagen haben, sehr nahe! Der Schlüssel war, meinen eigenen Job zu erstellen, um QuartzJobBean zu erweitern und die schedulerContextAsMap zu verwenden.

Ich bin durchgekommen, ohne die Eigenschaft applicationContextSchedulerContextKey anzugeben - es hat für mich ohne sie funktioniert.

Zum Nutzen anderer ist hier die endgültige Konfiguration, die für mich funktioniert hat:

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

Beachten Sie, dass die 'mailService'-Bean meine eigene Service-Bean ist, die von Spring verwaltet wird. Ich konnte in meinem Job wie folgt darauf zugreifen:

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

Diese Konfiguration ermöglichte es mir auch, Jobs dynamisch zu planen, indem ich Fabriken verwendete, um Trigger und JobDetails abzurufen und die erforderlichen Parameter programmgesteuert festzulegen:

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

Nochmals vielen Dank an alle, die geholfen haben,

Yachthafen


4

Eine einfache Lösung besteht darin, die Spring Bean in der Job Data Map festzulegen und dann beispielsweise die Bean in der Jobklasse abzurufen

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

Wenn man bedenkt, dass
Jobdaten

3

So sieht der Code mit @Component aus:

Hauptklasse, die den Job plant:

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

Der EmailJob ist der gleiche wie in meinem ersten Beitrag, mit Ausnahme der Annotation @Component:

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

Und die Konfigurationsdatei des Frühlings hat:

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

Danke für all die Hilfe!

Yachthafen


Werden Sie beim Start Ihrer App EmailJobinitialisiert? Eine einfache Möglichkeit zur Überprüfung besteht darin, dem Konstruktor eine Protokollzeile hinzuzufügen.
Atrain

@ Aaron: Ja, das tue ich - aber wie ich gerade entdeckt habe, wird es zweimal initialisiert! Einmal vom Spring-Framework selbst (und ich wette, in diese Instanz wird der Mail-Service eingefügt ...) und später, nachdem der Quarz selbst initialisiert wurde, wird der EMailJob erneut vom Quartz-Framework initialisiert - und das ist derjenige das hat nicht den Dienst injiziert ... Ich werde versuchen, eine Stapelspur des Starts des Frühlings hinzuzufügen, indem ich meine Frage bearbeite, wie Ryan vorgeschlagen hat ...
Marina

2

Eine Lösung von Hary https://stackoverflow.com/a/37797575/4252764 funktioniert sehr gut. Es ist einfacher, benötigt nicht so viele spezielle Factory Beans und unterstützt mehrere Trigger und Jobs. Ich möchte nur hinzufügen, dass der Quarzjob generisch gestaltet werden kann, wobei bestimmte Jobs als reguläre Spring Beans implementiert werden.

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
  }

}

Vielen Dank. Das habe ich auch bedacht. Aber in meinem Fall habe ich eine Bibliothek geschrieben, die jede Quarzimplementierung abstrahiert. Dies ist erforderlich, um den Namen des Schlüssels zu speichern und Objekte abzurufen. Ich konnte es auf reine Quarzmethode machen und habe es nur als Antwort gepostet. Bitte teilen Sie Ihre Gedanken!
Karthik R

1

Eine einfache Möglichkeit wäre, die Quarzjobs mit zu kommentieren @Component Anmerkungen zu versehen. Dann erledigt Spring die gesamte DI-Magie für Sie, da sie jetzt als Spring Bean erkannt wird. Ich musste etwas Ähnliches für eine tunAspectJ Aspekt - es war keine Frühlingsbohne, bis ich es mit dem Frühlingsstereotyp kommentierte @Component.


Danke, Aaron, ich habe es gerade versucht - aber leider passiert das gleiche NPE - und der Postdienst wird nicht in die Jobbohne gespritzt ...
Marina

Befindet sich Ihre EmailJobKlasse in einem Paket, das beim Start der App von Spring gescannt wird? Die Tatsache, dass Sie mit Anmerkungen versehen haben @Component, die injizierte Klasse jedoch immer noch null ist, zeigt an, dass sie nicht gescannt wird. Andernfalls würde der DI beim Start der App eine Ausnahme auslösen.
Atrain

Aaron: Ja, es soll gescannt werden - ich habe das <context: component-scan base-package = "com.mybasepackage">, das es tun sollte ... In meiner nächsten Antwort gebe ich einen vollständigen Code meines Hauptgeräts an Klasse, mit der Spring-Konfiguration - nur für den Fall, dass etwas Offensichtliches entdeckt werden kann ...
Marina

Die mit "@Autowired" gekennzeichneten Jobfelder werden nicht eingefügt, selbst wenn Sie den Job mit "@Component"
markieren

6
Dies funktioniert nicht, da das Erstellen von Jobobjekten von Quarts verwaltet wird und daher Felder nicht automatisch verdrahtet werden und Klassenanmerkungen nichts ohne zusätzliche Behandlung tun.
Msangel

1

Dies ist die richtige Antwort http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . und wird für die meisten Leute arbeiten. Wenn Ihre web.xml jedoch nicht alle applicationContext.xml-Dateien kennt, kann der Quarzjob diese Beans nicht aufrufen. Ich musste eine zusätzliche Ebene erstellen, um zusätzliche applicationContext-Dateien einzufügen

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

Sie können eine beliebige Anzahl von Kontextdateien hinzufügen, die Ihrem Quarz bekannt sein sollen.


1

Dies ist ein ziemlich alter Beitrag, der immer noch nützlich ist. Alle Lösungen, die diese beiden vorschlagen, hatten wenig Bedingung, die nicht allen entspricht:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Dies setzt voraus oder erfordert, dass es sich um ein Spring-Web-basiertes Projekt handelt
  • AutowiringSpringBeanJobFactory Der in der vorherigen Antwort erwähnte Ansatz ist sehr hilfreich, aber die Antwort ist spezifisch für diejenigen, die keine reine Vanillequarz-API verwenden, sondern Spring's Wrapper, damit der Quarz dasselbe tut.

Wenn Sie bei der reinen Quartz-Implementierung für die Planung bleiben möchten (Quartz mit Autowiring-Funktionen mit Spring), konnte ich dies wie folgt tun:

Ich habe versucht, es so weit wie möglich mit Quarz zu machen, und daher erweist sich wenig Hack als hilfreich.

 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);gibt uns eine automatisch verdrahtete Jobinstanz. Da a AutowiringSpringBeanJobFactoryimplizit a implementiert wird JobFactory, haben wir jetzt eine automatisch verdrahtbare Lösung aktiviert. Hoffe das hilft!


0

Stellen Sie sicher, dass Ihre

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

Abhängigkeit wird aus gezogen

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

und NICHT von

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

Es wollte, dass ich benutze

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

anstatt

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

Daher konnte die Jobinstanz nicht automatisch verdrahtet werden.


0

Wenn Sie in Ihrem Projekt bereits echtes AspectJ verwenden, können Sie die Job-Bean-Klasse mit Anmerkungen versehen @Configurable. Dann wird Spring in diese Klasse injizieren, selbst wenn es über konstruiert istnew


0

Ich habe mich dem ähnlichen Problem gestellt und bin mit folgendem Ansatz daraus hervorgegangen:

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

Im obigen Code injiziere ich dao.DAOFramework-Bean in JobA-Bean und in der ExecuteInternal-Methode können Sie Bean wie folgt injizieren lassen:

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

Ich hoffe, es hilft! Danke dir.


0

Die obige Lösung ist großartig, aber in meinem Fall hat die Injektion nicht funktioniert. Ich musste stattdessen autowireBeanProperties verwenden, wahrscheinlich aufgrund der Art und Weise, wie mein Kontext konfiguriert ist:

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

Alle oben genannten Lösungen funktionieren bei Spring 5 und Hibernate 5 sowie Quartz 2.2.3 nicht, wenn ich Transaktionsmethoden aufrufen möchte!

Ich habe daher diese Lösung implementiert, die den Scheduler automatisch startet und die Jobs auslöst. Ich habe viel von diesem Code bei dzone gefunden . Da ich Trigger und Jobs nicht dynamisch erstellen muss, wollte ich, dass die statischen Trigger über die Spring-Konfiguration vordefiniert werden und nur die Jobs als Spring-Komponenten verfügbar gemacht werden.

Meine Grundkonfiguration sieht so aus

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

}

Wie Sie sehen können, haben Sie den Scheduler und einen einfachen Testtrigger, der über einen Cron-Ausdruck definiert wird. Sie können natürlich einen beliebigen Planungsausdruck auswählen. Sie benötigen dann die AutowiringSpringBeanJobFactory, die so aussieht

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

}

Hier verbinden Sie Ihren normalen Bewerbungskontext und Ihren Job miteinander. Dies ist die wichtige Lücke, da Quartz normalerweise Worker-Threads startet, die keine Verbindung zu Ihrem Anwendungskontext haben. Aus diesem Grund können Sie keine Transaktionsmethoden ausführen. Das Letzte, was fehlt, ist ein Job. Es kann so aussehen

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

Es ist keine perfekte Lösung, da Sie eine zusätzliche Klasse nur zum Aufrufen Ihrer Servicemethode sind. Aber es funktioniert trotzdem.


0

Jdbc Jobstore

Wenn Sie jdbc jobstore verwenden, verwendet Quartz einen anderen Klassenladeprogramm. Dies verhindert alle Problemumgehungen für das automatische Verdrahten, da Objekte aus der Feder auf der Quarzseite nicht kompatibel sind, da sie von einem anderen Klassenlader stammen.

Um dies zu beheben, muss der Standardklassenlader in der Quarz-Eigenschaftendatei wie folgt festgelegt werden:

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

Als Referenz: https://github.com/quartz-scheduler/quartz/issues/221


0

Verlängern Sie einfach Ihren Job von 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());
    }

}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.