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!
fonte
Acabei de colocar
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
como primeira linha do meuJob.execute(JobExecutionContext context)
método.fonte
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:
Então, usando o código abaixo em sua classe de trabalho, você pode obter o applicationContext e obter qualquer bean que desejar.
Espero que ajude. Você pode obter mais informações no blog de Mark Mclaren
fonte
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.
fonte
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
e então, dentro
ligue
myBean = (myBean) context.getMergedJobDataMap().get("myBean");
e está tudo pronto. Eu sei, parece feio, mas como solução alternativa, funcionafonte
fonte
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:
Observe que o bean 'mailService "é meu próprio bean de serviço, gerenciado pelo Spring. Pude acessá-lo em meu trabalho da seguinte forma:
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:
Muito obrigado novamente a todos que ajudaram,
Marina
fonte
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
`
fonte
Aqui está a aparência do código com @Component:
Aula principal que programa o trabalho:
O EmailJob é o mesmo da minha primeira postagem, exceto pela anotação @Component:
E o arquivo de configuração do Spring tem:
Obrigado por toda a ajuda!
Marina
fonte
EmailJob
está sendo inicializado? Uma maneira fácil de verificar é adicionar uma linha de log no construtor.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.
fonte
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 springAutowiringSpringBeanJobFactory
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.
quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
nos dá uma instância de trabalho autowired. Uma vez queAutowiringSpringBeanJobFactory
implementa implicitamente aJobFactory
, agora habilitamos uma solução autonegável. Espero que isto ajude!fonte
Uma maneira simples de fazer isso seria apenas anotar os trabalhos do Quartz com
@Component
anotaçõ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 umAspectJ
aspecto - não era um bean Spring até que eu o anotei com o@Component
estereótipo Spring .fonte
EmailJob
classe está em um pacote que seria verificado pelo Spring na inicialização do aplicativo? O fato de que você anotou com,@Component
mas 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.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
Você pode adicionar qualquer número de arquivos de contexto que deseja que seu quartzo conheça.
fonte
Certifique-se de que o seu
dependência é puxada de
e NÃO de
Queria que eu usasse
ao invés de
o mesmo ocorreu com a falha ao autowire a instância do job.
fonte
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
fonte
Enfrentei um problema semelhante e saí dele com a seguinte abordagem:
No código acima, injeto o bean dao.DAOFramework no bean JobA e no método ExecuteInternal, você pode obter o bean injetado como:
Espero que ajude! Obrigado.
fonte
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:
fonte
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
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
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
Não é uma solução perfeita porque você tem uma classe extra apenas para chamar seu método de serviço. Mesmo assim, funciona.
fonte
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:
Como referência: https://github.com/quartz-scheduler/quartz/issues/221
fonte
Basta estender seu trabalho de
QuartzJobBean
fonte