wstrzyknąć referencję fasoli do zadania kwarcowego wiosną?

95

Udało mi się skonfigurować i zaplanować zadanie Quartz za pomocą stałego magazynu JobStoreTX na wiosnę. Nie używam zadań Spring's Quartz, ponieważ muszę planować je dynamicznie, w czasie wykonywania, a wszystkie przykłady integracji Springa z Quartz, które znalazłem, to sztywne kodowanie shcedules w plikach konfiguracyjnych Spring ... W każdym razie, oto jak Planuję pracę:

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 to prosta praca polegająca na wysyłaniu wiadomości e-mail przy użyciu klasy JavaMailSenderImpl Springa.

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

}

Problem polega na tym, że muszę uzyskać odwołanie do wystąpienia tej klasy (JavaMailSenderImpl) w mojej klasie EMailJob. Kiedy próbuję wstrzyknąć to w ten sposób:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

nie jest wtryskiwany - odniesienie ma wartość NULL. Zakładam, że tak się dzieje, ponieważ to nie Spring tworzy instancję klasy EMailJob, ale Quartz, a Quartz nic nie wie o wstrzykiwaniu zależności ...

Czy jest więc jakiś sposób, aby wymusić ten zastrzyk?

dzięki!

Aktualizacja 1: @Aaron: tutaj jest odpowiednia część śledzenia stosu ze startu, która pokazuje, że zadanie EMailJob zostało utworzone dwukrotnie:

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

dzięki!

Aktualizacja # 2: @Ryan:

Próbowałem użyć SpringBeanJobFactory w następujący sposób:

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

I zmodyfikowałem moją główną klasę, aby pobrać harmonogram z tej fabryki zamiast Quartz:

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

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

Ale kiedy uruchamiam aplikację - pojawiają się błędy, patrz poniżej. Oto ślad stosu z uruchomienia Springa. Wygląda na to, że sam harmonogram został utworzony w porządku, ale błąd pojawia się, gdy próbuje utworzyć instancję mojego zadania e-mail:

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)

dzięki!

przystań
źródło

Odpowiedzi:

129

Możesz użyć tego SpringBeanJobFactorydo automatycznego automatycznego łączenia obiektów kwarcowych za pomocą sprężyny:

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

Następnie dołącz go do swojego SchedulerBean(w tym przypadku za pomocą Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Pracuję dla mnie, używając spring-3.2.1 i quartz-2.1.6.

Sprawdź całą istotę tutaj .

Rozwiązanie znalazłem w tym poście na blogu

galaretki
źródło
13
Powinieneś wygrać za to nagrodę, to fantastyczne!
Nathan Feger
2
Rozwiązanie jest naprawdę świetne! Wszystkie kredyty dla autora wpisu na blogu :)
jelies
3
Dzięki - to zaoszczędziło mi dni! Dlaczego Spring nie dostarczył tego OOB. Jest to bardzo podstawowy warunek używania Quartz na wiosnę.
HandyManDan
4
to powinna być domyślna implementacja :)
Diego Plentz
2
świetne rozwiązanie, ale ktoś ma pojęcie, dlaczego AutowireCapableBeanFactory beanFactory jest oznaczony jako „przejściowy”? Wydaje się, że AutowiringSpringBeanJobFactory i tak nie jest serializowany, więc ani beanFactory nigdy nie musi być serializowany
Marios
57

Po prostu wstawiłem SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);jako pierwszą linię mojej Job.execute(JobExecutionContext context)metody.

msangel
źródło
7
To jest prawdziwe rozwiązanie. Testowane za pomocą Spring 3.2.4.RELEASE i Quartz 2.2.0. ;)
aloplop85
3
@msangel - cóż, oba BĘDĄ działać, ale problem z używaniem SpringBeanAutowiringSupport w zadaniu Quartz polega na tym, że instancja zadania musi teraz WIEDZIEĆ o Spring, co jest sprzeczne z całą ideą IoC (wtrysk dep). Jeśli teraz potrzebujesz na przykład użyć CDI, wszystkie twoje zadania kwarcowe będą musiały zostać dostosowane, zamiast tylko jednej fabryki.
demaniak
2
To nie zadziałało w przypadku testu jednostkowego, ponieważ szuka kontekstu aplikacji internetowej. Musiałem skorzystać z odpowiedzi @jelies
Wim Deblauwe
5
To rozwiązanie nie działa ze sprężyną 4.1.4 i Quartz 2.2.1
skywalker
1
Ja też miałem ten problem i wypróbowałem to rozwiązanie. Działa, ALE tworzy nową instancję zamiast używać już utworzonej (domyślny singleton). W każdym razie możesz przekazać swoje zadanie do wszystkiego, używając scheduleer.getContext (). Put ("nazwa_obiektu", obiekt);
Krzysztof Cieśliński
13

Ten sam problem został rozwiązany w LINK :

Znalazłem inną opcję z postu na forum Spring, że możesz przekazać odwołanie do kontekstu aplikacji Spring za pośrednictwem SchedulerFactoryBean. Podobnie jak w przykładzie pokazanym poniżej:

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

Następnie używając poniższego kodu w swojej klasie pracy, możesz pobrać applicationContext i otrzymać dowolną fasolkę.

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

Mam nadzieję, że to pomoże. Więcej informacji znajdziesz w blogu Marka Mclaren'a

Zgrywanie
źródło
1
Dzięki, @Rippon! Po wielu próbach i niepowodzeniach zastosowałem bardzo podobne podejście, które zasugerowałeś: nie korzystałem z właściwości applicationContextSchedulerContextKey i kontekstu aplikacji, ale użyłem „code” <property name = "scheduleerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Marina,
8

Masz rację w swoich założeniach dotyczących tworzenia instancji klasy przez Spring vs. Quartz. Jednak Spring udostępnia kilka klas, które pozwalają wykonać pewne podstawowe iniekcje zależności w Quartz. Sprawdź SchedulerFactoryBean.setJobFactory () wraz z SpringBeanJobFactory . Zasadniczo, używając SpringBeanJobFactory, włączasz iniekcję zależności we wszystkich właściwościach zadania , ale tylko dla wartości, które znajdują się w kontekście harmonogramu kwarcowego lub na mapie danych zadania . Nie wiem, jakie wszystkie style DI obsługuje (konstruktor, adnotacja, ustawiacz ...), ale wiem, że obsługuje wstrzykiwanie settera.

Ryan Stewart
źródło
Cześć Ryan, dzięki za sugestie. Czy masz na myśli, że musiałbym używać SpringBeanFactoryJob wraz ze wstępnie skonfigurowanymi wyzwalaczami i zadaniami, które rozszerzają QuartzJobBean, aby umożliwić wstrzykiwanie zależności? Problem z tym podejściem polega na tym, że wyzwalacze są definiowane statycznie w plikach konfiguracyjnych Springa, gdzie muszę mieć możliwość definiowania wyzwalaczy z dynamicznymi harmonogramami w czasie wykonywania ... Zobacz moją następną odpowiedź poniżej, aby uzyskać więcej informacji - za mało miejsca w obszar komentarzy ...
Marina
@Marina: Nie, tak to nie działa. Użyj SpringBeanJobFactory i zrób to tak, jak chcesz. Po prostu zadziała. Nie publikuj też odpowiedzi, która jest tylko aktualizacją Twojego pytania. Zamiast tego edytuj swoje pytanie.
Ryan Stewart
przepraszam, właśnie zauważyłem twój komentarz! Wypróbuję to, jak sugerujesz, i poinformuję Cię o wynikach. Dziękuję za pomoc! Aha, spróbuję edytować moje pytanie zamiast odpowiadać ...
Marina
7

dla wszystkich, którzy spróbują tego w przyszłości.

org.springframework.scheduling.quartz.JobDetailBean dostarcza mapy obiektów, które mogą być wiosennymi fasolami.

zdefiniować coś takiego

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

a potem w środku

public void executeInternal(JobExecutionContext context)

zadzwoń myBean = (myBean) context.getMergedJobDataMap().get("myBean"); i wszystko gotowe. Wiem, wygląda brzydko, ale jako obejście działa

user1196227
źródło
IMHO Uważam, że to rozwiązanie jest bardziej czyste i „naturalne” niż próba dodania możliwości automatycznego okablowania do zadań kwarcowych, więc nie sądzę, aby było to obejście.
naprawdęnice
6
ApplicationContext springContext =

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

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

bean.method();
Damian
źródło
4

Dzięki, Rippon! W końcu to również zadziałało, po wielu zmaganiach, a moje rozwiązanie jest bardzo bliskie temu, co zasugerowałeś! Kluczem było stworzenie własnego zadania, które rozszerzyło QuartzJobBean i użycie SchedulerContextAsMap.

Udało mi się bez określenia właściwości applicationContextSchedulerContextKey - działało bez niej.

Z korzyścią dla innych, oto ostateczna konfiguracja, która zadziałała dla mnie:

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

Zauważ, że komponent bean „mailService” to mój własny komponent bean usług, zarządzany przez Spring. Miałem do niego dostęp w moim zadaniu w następujący sposób:

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

Ta konfiguracja pozwoliła mi również na dynamiczne planowanie zadań, używając fabryk do pobierania wyzwalaczy i szczegółów zadań oraz programowego ustawiania wymaganych parametrów:

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

Jeszcze raz wielkie dzięki dla wszystkich, którzy pomogli,

przystań

przystań
źródło
4

Prostym rozwiązaniem jest ustawienie fasoli wiosennej w mapie danych zadania, a następnie pobranie jej na przykład w klasie zadania

// 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");
Hari
źródło
biorąc pod uwagę, że dane zadanie jest zapisane jako blob (przy użyciu db wytrwałość) mogłoby to prowadzić do problemów z wydajnością dB (Imagine danych jest naprawdę ogromny)
Sudip Bhandari
3

Oto jak wygląda kod z @Component:

Główna klasa, która planuje pracę:

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 jest taki sam jak w moim pierwszym wpisie, z wyjątkiem adnotacji @Component:

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

A plik konfiguracyjny Springa zawiera:

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

Dzięki za całą pomoc!

przystań

przystań
źródło
Czy po uruchomieniu aplikacji widzisz EmailJobinicjalizację? Łatwym sposobem sprawdzenia jest dodanie wiersza dziennika w konstruktorze.
wytrenuj
@Aaron: tak, tak - ale jak właśnie odkryłem, inicjalizacja jest dwukrotnie! Raz przez samą strukturę Spring (i założę się, że ta instancja ma wstrzykniętą usługę pocztową ...), a później, po zainicjowaniu samego Quartz - zadanie EMailJob jest ponownie inicjowane przez framework Quartz - i to jest to który nie ma wstrzykniętej usługi ... spróbuję dodać ślad stosu uruchomienia Springa edytując moje pytanie, tak jak zasugerował Ryan ...
Marina
2

Rozwiązanie od Hary https://stackoverflow.com/a/37797575/4252764 działa bardzo dobrze. Jest prostszy, nie wymaga tak wielu specjalnych fabrycznych fasoli i obsługuje wiele wyzwalaczy i zadań. Chciałbym tylko dodać, że zadanie kwarcowe może być generyczne, z określonymi zadaniami zaimplementowanymi jako zwykłe ziarna wiosenne.

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
  }

}
Vuk Djapic
źródło
Dzięki. Też to rozważałem. Ale w moim przypadku pisałem bibliotekę, która abstrahuje każdą implementację kwarcu. Wymagało to zapamiętania nazwy „klucza” w celu pobrania jakichkolwiek obiektów. Byłem w stanie zrobić to czysto kwarcowo i po prostu opublikowałem to jako odpowiedź. Pls podziel się swoimi przemyśleniami!
Karthik R
2

To dość stary post, który wciąż jest przydatny. Wszystkie rozwiązania, które proponują te dwa, miały niewielki warunek, który nie pasowałby wszystkim:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Zakłada to lub wymaga, aby był to projekt oparty na sieci wiosennej
  • AutowiringSpringBeanJobFactory Podejście oparte na wspomnianym w poprzedniej odpowiedzi jest bardzo pomocne, ale odpowiedź jest specyficzna dla tych, którzy nie używają czystego waniliowego kwarcowego interfejsu API, ale raczej opakowanie Springa do kwarcu, aby zrobić to samo.

Jeśli chcesz pozostać przy implementacji czystego Quartz do planowania (Quartz z możliwościami Autowiring ze Spring), mogłem to zrobić w następujący sposób:

Chciałem zrobić to kwarcowo tak często, jak to możliwe, dlatego mały hack okazuje się pomocny.

 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);daje nam automatyczną instancję pracy. Ponieważ AutowiringSpringBeanJobFactoryniejawnie implementuje a JobFactory, teraz włączyliśmy rozwiązanie z możliwością automatycznego łączenia. Mam nadzieję że to pomoże!

Karthik R
źródło
1

Prostym sposobem na zrobienie tego byłoby po prostu dodanie adnotacji do Quartz Jobs @Componentadnotacją, a następnie Spring wykona za Ciebie całą magię DI, ponieważ jest teraz rozpoznawany jako ziarno wiosenne. Musiałem zrobić coś podobnego dla AspectJaspektu - to nie był wiosenny bean, dopóki nie oznaczyłem go @Componentstereotypem wiosny .

Pociąg
źródło
Dzięki, Aaron, właśnie próbowałem - ale niestety dzieje się to samo NPE - i usługa pocztowa nie jest wstrzykiwana do fasoli zadań ...
Marina
Czy Twoja EmailJobklasa znajduje się w pakiecie, który zostanie przeskanowany przez Spring podczas uruchamiania aplikacji? Fakt, że dodano adnotację, @Componentale wstrzyknięta klasa nadal ma wartość null, oznacza, że ​​nie jest skanowana - w przeciwnym razie DI podczas uruchamiania aplikacji zgłosi wyjątek.
wytrenuj
Aaron: tak, to powinno zostać zeskanowane - mam <context: component-scan base-package = "com.mybasepackage">, który powinien to zrobić ... W następnej odpowiedzi podaję pełny kod mojego głównego class, z konfiguracją Spring - na wypadek, gdyby można było dostrzec coś oczywistego ...
Marina
Pola pracy oznaczone jako „@Autowired” nie są wstawiane, nawet jeśli oznaczysz ofertę jako „@Component”
aloplop85
6
To nie zadziała, ponieważ tworzenie obiektów zadań jest zarządzane przez kwarty i dlatego pola nie są automatycznie przypisywane, a adnotacje klas nic nie robią bez dodatkowej obsługi.
msangel
1

Powyższe rozwiązanie jest świetne ale w moim przypadku wtrysk nie działał. Zamiast tego musiałem użyć autowireBeanProperties, prawdopodobnie ze względu na sposób skonfigurowania mojego kontekstu:

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;
    }
}
Luis Díaz
źródło
1

To jest właściwa odpowiedź http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . i będzie działać dla większości ludzi. Ale jeśli twój web.xml nie zna wszystkich plików applicationContext.xml, zadanie kwarcowe nie będzie mogło wywołać tych ziaren. Musiałem zrobić dodatkową warstwę, aby wstrzyknąć dodatkowe pliki 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;
    }
}

Możesz dodać dowolną liczbę plików kontekstowych, o których ma wiedzieć Twój kwarc.

vsingh
źródło
0

Upewnij się, że twój

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

zależność jest wyciągnięta z

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

a NIE z

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

Chciał, żebym użył

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

zamiast

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

więc nie powiodło się automatyczne podłączanie instancji zadania.

Dmitry
źródło
0

Jeśli używasz już prawdziwego AspectJ w swoim projekcie, możesz dodać adnotację do klasy Job Bean @Configurable. Następnie Spring wstrzyknie do tej klasy, nawet jeśli jest skonstruowana za pomocąnew

Ralph
źródło
0

Zmierzyłem się z podobnym problemem i wyszedłem z niego w następujący sposób:

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

W powyższym kodzie wstawiam bean dao.DAOFramework do fasoli JobA, aw metodzie ExecuteInternal możesz dostać taką bean:

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

Mam nadzieję, że to pomoże! Dziękuję Ci.

Aman Goel
źródło
0

Wszystkie powyższe rozwiązania nie działają dla mnie ze Spring 5 i Hibernate 5 oraz Quartz 2.2.3, gdy chcę wywołać metody transakcyjne!

Dlatego wdrożyłem to rozwiązanie, które automatycznie uruchamia harmonogram i wyzwala zadania. Znalazłem dużo tego kodu w dzone . Ponieważ nie muszę dynamicznie tworzyć wyzwalaczy i zadań, chciałem, aby statyczne wyzwalacze były wstępnie zdefiniowane za pomocą konfiguracji Spring, a tylko zadania były ujawniane jako komponenty Spring.

Moja podstawowa konfiguracja wygląda tak

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

}

Jak widać, masz harmonogram i prosty wyzwalacz testowy, który jest definiowany za pomocą wyrażenia cron. Możesz oczywiście wybrać dowolne wyrażenie planowania. Następnie potrzebujesz AutowiringSpringBeanJobFactory, która działa w ten sposób

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

}

Tutaj łączysz swój normalny kontekst aplikacji i swoją pracę. Jest to ważna luka, ponieważ normalnie Quartz uruchamia swoje wątki robocze, które nie mają połączenia z kontekstem aplikacji. To jest powód, dla którego nie możesz wykonywać mehtodów transakcyjnych. Ostatnią rzeczą, której brakuje, jest praca. Może tak wyglądać

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

To nie jest idealne rozwiązanie, ponieważ masz dodatkową klasę tylko do wywoływania metody usługi. Niemniej jednak działa.

M46
źródło
0

Jdbc jobstore

Jeśli korzystasz z jdbc jobstore, Quartz używa innego modułu ładującego klasy. Zapobiega to wszelkim obejściom automatycznego okablowania, ponieważ obiekty ze sprężyny nie będą kompatybilne po stronie kwarcu, ponieważ pochodzą z innego modułu ładującego klasy.

Aby to naprawić, domyślny program ładujący klasy musi być ustawiony w pliku właściwości kwarcu w następujący sposób:

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

Jako odniesienie: https://github.com/quartz-scheduler/quartz/issues/221

Deli Kristóf Demeter
źródło
0

Po prostu przedłuż swoją pracę z 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());
    }

}
Mojtabye
źródło