injetar referência de bean em um trabalho de Quartz no Spring?

94

Consegui configurar e agendar um job Quartz usando o armazenamento persistente JobStoreTX no Spring. Eu não uso jobs Quartz do Spring, porque preciso agendá-los dinamicamente, em tempo de execução, e todos os exemplos de integração do Spring com Quartz que encontrei estavam codificando as shcedules nos arquivos de configuração do Spring ... De qualquer forma, aqui está como Eu agendo o trabalho:

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

O EMailJob é um trabalho simples que envia e-mail usando a classe JavaMailSenderImpl do 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");

}

O problema é que preciso obter uma referência a uma instância dessa classe (JavaMailSenderImpl) em minha classe EMailJob. Quando tento injetar assim:

@Autowired
private JavaMailSenderImpl mailSenderImpl;

não é injetado - a referência é NULL. Presumo que isso esteja acontecendo porque não é o Spring que instancia a classe EMailJob, mas o Quartz, e o Quartz não sabe nada sobre injeção de dependência ...

Então, há alguma maneira de forçar essa injeção a acontecer?

obrigado!

Atualização 1: @Aaron: aqui está uma parte relevante do stacktrace da inicialização, que mostra que o EMailJob foi instanciado duas vezes:

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

obrigado!

Atualização nº 2: @Ryan:

Tentei usar SpringBeanJobFactory da seguinte maneira:

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

E eu modifiquei minha classe principal para obter o Scheduler desta fábrica, em vez do Quartz ':

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

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

Mas quando eu executo o aplicativo - obter erros, veja abaixo. Aqui está o rastreamento de pilha da inicialização do Spring. Parece que o próprio Scheduler foi criado corretamente, mas o erro ocorre quando ele tenta instanciar meu EMailJob:

2011-08-15 21:49:42,968 [main] INFO  org.springframework.scheduling.quartz.SchedulerFactoryBean - Loading Quartz config from [class path resource [spring/quartz.properties]]
2011-08-15 21:49:43,031 [main] INFO  com.mchange.v2.log.MLog - MLog clients using log4j logging.
2011-08-15 21:49:43,109 [main] INFO  com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.0.1 created.
2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Using thread monitor-based data access locking (synchronization).
2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.0.1) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
 Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
 NOT STARTED.
 Currently in standby mode.
 Number of jobs executed: 0
 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
 Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.

2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2011-08-15 21:49:43,187 [main] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.0.1
2011-08-15 21:49:43,187 [main] INFO  org.quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@566633
2011-08-15 21:49:43,265 [main] INFO  com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge13f8h1lsg7py1rg0iu0|1956391, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge13f8h1lsg7py1rg0iu0|1956391, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/2010rewrite2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 0 from dual, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2011-08-15 21:49:43,343 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Recovery complete.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 'complete' triggers.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.impl.jdbcjobstore.JobStoreTX - Removed 0 stale fired job entries.
2011-08-15 21:49:43,359 [main] INFO  org.quartz.core.QuartzScheduler - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2011-08-15 21:49:43,562 [schedulerFactoryBean_QuartzSchedulerThread] ERROR org.quartz.core.ErrorLogger - An error occured instantiating job to be executed. job= 'immediateEmailsGroup.DEFAULT.jobFor_1000new1'
org.quartz.SchedulerException: Problem instantiating class  'com.cambridgedata.notifications.EMailJob' -  [See nested exception:  java.lang.AbstractMethodError:  org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;]
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:141)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:381)
Caused by: java.lang.AbstractMethodError: org.springframework.scheduling.quartz.SpringBeanJobFactory.newJob(Lorg/quartz/spi/TriggerFiredBundle;Lorg/quartz/Scheduler;)Lorg/quartz/Job;
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:134)

obrigado!

Marina
fonte

Respostas:

129

Você pode usar isso SpringBeanJobFactorypara conectar automaticamente objetos de quartzo usando a mola:

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

Em seguida, anexe-o ao seu SchedulerBean(neste caso, com Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Trabalhando para mim, usando spring-3.2.1 e quartzo-2.1.6.

Confira a essência completa aqui .

Eu encontrei a solução nesta postagem do blog

geléias
fonte
13
Você deveria ganhar um prêmio por isso, é fantástico!
Nathan Feger,
2
A solução é realmente ótima! Todos os créditos para o autor da postagem do blog :)
jelies
3
Obrigado - isso me salvou dias! Por que o Spring não forneceu este OOB. Este é o requisito básico para o uso de quartzo na primavera.
HandyManDan
4
esta deve ser a implementação padrão :)
Diego Plentz
2
ótima solução, mas alguém tem alguma idéia de por que AutowireCapableBeanFactory beanFactory está marcado como "transiente"? AutowiringSpringBeanJobFactory não parece ser serializado de qualquer maneira, então nem a beanFactory jamais precisará ser serializada
Marios,
57

Acabei de colocar SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);como primeira linha do meu Job.execute(JobExecutionContext context)método.

msangel
fonte
7
Esta é a solução real. Testado com Spring 3.2.4.RELEASE e Quartz 2.2.0. ;)
aloplop85
3
@msangel - bem, ambos irão funcionar, mas o problema em usar SpringBeanAutowiringSupport em seu trabalho Quartz, é que a instância do trabalho agora precisa SABER sobre o Spring, o que vai contra a ideia de IoC (injeção de depósito). Se agora você precisa, por exemplo, usar CDI, todos os seus trabalhos de quartzo precisarão ser ajustados, em vez de apenas uma fábrica de trabalho.
demaniak
2
Isso não funcionou para mim em um teste de unidade, pois procura um contexto de aplicativo da web. Tive que usar a resposta de @jelies
Wim Deblauwe
5
Esta solução não funciona com a primavera 4.1.4 e Quartz 2.2.1
skywalker
1
Eu também tive esse problema e tentei essa solução. Está funcionando, MAS cria uma nova instância em vez de usar uma já criada (singleton padrão). De qualquer forma, você pode passar qualquer coisa para o seu trabalho usando scheduler.getContext (). Put ("objectName", object);
Krzysztof Cieśliński 01 de
13

O mesmo problema foi resolvido em LINK :

Eu encontrei outra opção na postagem no fórum do Spring que você pode passar uma referência ao contexto do aplicativo Spring através do SchedulerFactoryBean. Como o exemplo mostrado abaixo:

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

Então, usando o código abaixo em sua classe de trabalho, você pode obter o applicationContext e obter qualquer bean que desejar.

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

Espero que ajude. Você pode obter mais informações no blog de Mark Mclaren

Rips
fonte
1
Obrigado, @Rippon! Depois de muitas tentativas e falhas, usei uma abordagem muito semelhante à que você sugeriu: Não usei a propriedade applicationContextSchedulerContextKey e o contexto do aplicativo, mas usei o 'code' <property name = "schedulerContextAsMap"> <map> <entry key = "mailService" value-ref = "mailService" /> </map> </property>
Marina de
8

Você está certo em sua suposição sobre Spring vs. Quartz instanciar a classe. No entanto, o Spring fornece algumas classes que permitem que você faça uma injeção de dependência primitiva no Quartz. Verifique SchedulerFactoryBean.setJobFactory () junto com SpringBeanJobFactory . Essencialmente, usando SpringBeanJobFactory, você ativa a injeção de dependência em todas as propriedades da tarefa, mas apenas para valores que estão no contexto do planejador Quartz ou no mapa de dados da tarefa . Não sei quais são os estilos de DI que ele suporta (construtor, anotação, setter ...) mas sei que ele suporta injeção de setter.

Ryan Stewart
fonte
Olá, Ryan, obrigado por suas sugestões. Você quer dizer que eu teria que usar o SpringBeanFactoryJob, junto com Triggers pré-configurados e jobs que estendem QuartzJobBean para habilitar a injeção de dependência? O problema com esta abordagem é que os gatilhos são definidos estaticamente nos arquivos de configuração do Spring, onde preciso ser capaz de definir gatilhos com agendamentos dinâmicos em tempo de execução ... Veja minha próxima resposta abaixo para mais detalhes - espaço insuficiente no área de comentários ...
Marina
@Marina: Não, não é assim que funciona. Use SpringBeanJobFactory e faça-o da maneira que quiser. Isso vai funcionar. Além disso, não poste uma resposta que seja apenas uma atualização para sua pergunta. Em vez disso, edite sua pergunta.
Ryan Stewart de
desculpe, acabei de notar seu comentário! Vou experimentar como você sugere e vou informá-lo dos resultados. Obrigado pela ajuda! Ah, e vou tentar editar minha pergunta em vez de responder ...
Marina
7

para todos os que tentarem fazer isso no futuro.

org.springframework.scheduling.quartz.JobDetailBean fornece um mapa de objetos e esses objetos podem ser feijões de primavera.

definir smth como

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

e então, dentro

public void executeInternal(JobExecutionContext context)

ligue myBean = (myBean) context.getMergedJobDataMap().get("myBean"); e está tudo pronto. Eu sei, parece feio, mas como solução alternativa, funciona

user1196227
fonte
IMHO Eu acho esta solução mais limpa e "natural" do que tentar adicionar a capacidade de fiação automática aos trabalhos de quartzo, então não acho que seja uma solução alternativa.
realmente bom
6
ApplicationContext springContext =

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

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

bean.method();
Damian
fonte
4

Obrigado, Rippon! Finalmente consegui fazer funcionar também, depois de muitas lutas, e minha solução está muito próxima do que você sugeriu! A chave era fazer meu próprio trabalho para estender QuartzJobBean e usar o schedulerContextAsMap.

Eu escapei sem especificar a propriedade applicationContextSchedulerContextKey - funcionou sem ela para mim.

Para o benefício de outros, aqui está a configuração final que funcionou para mim:

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

Observe que o bean 'mailService "é meu próprio bean de serviço, gerenciado pelo Spring. Pude acessá-lo em meu trabalho da seguinte forma:

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

E essa configuração também me permitiu agendar tarefas dinamicamente, usando fábricas para obter Triggers e JobDetails e definir os parâmetros necessários de forma programática:

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

Muito obrigado novamente a todos que ajudaram,

Marina

Marina
fonte
4

Uma solução simples é definir o bean de primavera no Mapa de dados do trabalho e, em seguida, recuperar o bean na classe de trabalho, por exemplo

// 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
fonte
considerando que os dados do trabalho são armazenados como blob (ao usar a persistência do banco de dados), isso pode levar a problemas de desempenho do banco de dados (imagine que os dados são realmente enormes)
Sudip Bhandari
3

Aqui está a aparência do código com @Component:

Aula principal que programa o trabalho:

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

O EmailJob é o mesmo da minha primeira postagem, exceto pela anotação @Component:

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

E o arquivo de configuração do Spring tem:

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

Obrigado por toda a ajuda!

Marina

Marina
fonte
Quando seu aplicativo é inicializado, você vê que EmailJobestá sendo inicializado? Uma maneira fácil de verificar é adicionar uma linha de log no construtor.
atrain de
@Aaron: sim, mas como acabei de descobrir, ele está sendo inicializado duas vezes! Uma vez pelo próprio framework Spring (e aposto que esta instância tem o serviço de correio injetado nele ...) e então, mais tarde, depois que o próprio Quartz é inicializado - o EMailJob está sendo inicializado novamente pelo framework Quartz - e esse é o que não tem o serviço injetado ... Vou tentar adicionar um stack trace do startup do Spring editando minha pergunta, como o Ryan sugeriu ...
Marina
2

Uma solução de Hary https://stackoverflow.com/a/37797575/4252764 funciona muito bem. É mais simples, não precisa de tantos beans de fábrica especiais e oferece suporte a vários triggers e jobs. Seria apenas adicionar que o trabalho Quartz pode ser feito para ser genérico, com trabalhos específicos implementados como beans Spring regulares.

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
fonte
Obrigado. Eu também considerei isso. Mas, no meu caso, estava escrevendo uma biblioteca que abstrai qualquer implementação de quartzo. Isso é necessário para lembrar o nome da 'chave' para recuperar quaisquer objetos. Consegui fazer o método puro quartzo e apenas postei como uma resposta. Por favor, compartilhe seus pensamentos!
Karthik R
2

Este é um post bastante antigo que ainda é útil. Todas as soluções que propõe essas duas tinham poucas condições que não serviam a todas:

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); Isso pressupõe ou exige que seja um projeto baseado na web spring
  • AutowiringSpringBeanJobFactory A abordagem baseada na resposta mencionada na resposta anterior é muito útil, mas a resposta é específica para aqueles que não usam api de quartzo de baunilha puro, mas sim o invólucro de Spring para o quartzo fazer o mesmo.

Se você quiser continuar com a implementação de Quartz puro para agendamento (Quartz com recursos de Autowiring com Spring), consegui fazer da seguinte maneira:

Eu estava tentando fazer isso com o máximo de quartzo possível e, portanto, poucos truques se mostraram úteis.

 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);nos dá uma instância de trabalho autowired. Uma vez que AutowiringSpringBeanJobFactoryimplementa implicitamente a JobFactory, agora habilitamos uma solução autonegável. Espero que isto ajude!

Karthik R
fonte
1

Uma maneira simples de fazer isso seria apenas anotar os trabalhos do Quartz com @Componentanotações, e então o Spring fará toda a mágica de DI para você, pois agora é reconhecido como um bean Spring. Tive que fazer algo semelhante para um AspectJaspecto - não era um bean Spring até que eu o anotei com o @Componentestereótipo Spring .

um trem
fonte
Obrigado, Aaron, eu acabei de tentar - mas infelizmente acontece o mesmo NPE - e o serviço de e-mail não está sendo injetado no bean de trabalho ...
Marina
Sua EmailJobclasse está em um pacote que seria verificado pelo Spring na inicialização do aplicativo? O fato de que você anotou com, @Componentmas a classe injetada ainda é nula, indica que ela não está sendo verificada - caso contrário, o DI na inicialização do aplicativo lançaria uma exceção.
atrain de
Aaron: sim, ele deve ser verificado - eu tenho o <context: component-scan base-package = "com.mybasepackage"> que deve fazer isso ... Na minha próxima resposta, estou fornecendo um código completo do meu principal classe, com a configuração Spring - apenas no caso de algo óbvio poder ser notado ...
Marina
Os campos de trabalho marcados com "@Autowired" não são injetados, mesmo se você marcar o trabalho com "@Component"
aloplop85
6
Isso não funcionará porque a criação de objetos de trabalho é gerenciada por quartos e, portanto, os campos não são autowired e as anotações de classe não fazem nada sem manipulação extra.
msangel de
1

Esta é a resposta certa http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . e funcionará para a maioria das pessoas. Mas se seu web.xml não conhece todos os arquivos applicationContext.xml, o trabalho de quartzo não será capaz de invocar esses beans. Tive que fazer uma camada extra para injetar arquivos applicationContext adicionais

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

Você pode adicionar qualquer número de arquivos de contexto que deseja que seu quartzo conheça.

vsingh
fonte
0

Certifique-se de que o seu

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

dependência é puxada de

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

e NÃO de

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

Queria que eu usasse

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

ao invés de

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

o mesmo ocorreu com a falha ao autowire a instância do job.

Dmitry
fonte
0

Quando você já usa AspectJ real em seu projeto, pode anotar a classe do job bean com @Configurable. Então o Spring injetará nesta classe, mesmo se ela for construída vianew

Ralph
fonte
0

Enfrentei um problema semelhante e saí dele com a seguinte abordagem:

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

No código acima, injeto o bean dao.DAOFramework no bean JobA e no método ExecuteInternal, você pode obter o bean injetado como:

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

Espero que ajude! Obrigado.

Aman Goel
fonte
0

A solução acima é ótima, mas no meu caso a injeção não funcionou. Eu precisei usar autowireBeanProperties em vez disso, provavelmente devido à forma como meu contexto está configurado:

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

Todas essas soluções acima não funcionam para mim com Spring 5 e Hibernate 5 e Quartz 2.2.3 quando desejo chamar métodos transacionais!

Portanto, implementei esta solução que inicia automaticamente o planejador e aciona os trabalhos. Eu encontrei muito desse código em dzone . Como não preciso criar gatilhos e trabalhos dinamicamente, queria que os gatilhos estáticos fossem predefinidos por meio da Configuração do Spring e apenas os trabalhos fossem expostos como componentes Spring.

Minha configuração básica é parecida com esta

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

}

Como você pode ver, você tem o planejador e um gatilho de teste simples que é definido por meio de uma expressão cron. Obviamente, você pode escolher qualquer expressão de programação que desejar. Você então precisa do AutowiringSpringBeanJobFactory que funciona assim

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

}

Aqui, você conecta seu contexto de aplicativo normal e seu trabalho. Esta é a lacuna importante porque normalmente o Quartz inicia seus threads de trabalho que não têm conexão com o contexto do seu aplicativo. Essa é a razão pela qual você não pode executar mehtods transacionais. A última coisa que falta é um emprego. Pode ser assim

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

Não é uma solução perfeita porque você tem uma classe extra apenas para chamar seu método de serviço. Mesmo assim, funciona.

M46
fonte
0

Jdbc jobstore

Se você estiver usando o jdbc jobstore, o Quartz usa um carregador de classe diferente. Isso evita todas as soluções alternativas para o autowiring, uma vez que os objetos da mola não serão compatíveis no lado do quartzo, porque se originaram de um carregador de classes diferente.

Para consertar isso, o carregador de classe padrão deve ser definido no arquivo de propriedades do quartzo assim:

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

Como referência: https://github.com/quartz-scheduler/quartz/issues/221

Deli Kristóf Demeter
fonte
0

Basta estender seu trabalho de QuartzJobBean

public class MyJob extends QuartzJobBean {

    @Autowired
    private SomeBean someBean;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Some bean is " + someBean.toString());
    }

}
Mojtabye
fonte