Logré configurar y programar un trabajo Quartz usando la tienda persistente JobStoreTX en Spring. No uso los trabajos de Spring Quartz, porque necesito programarlos dinámicamente, en tiempo de ejecución, y todos los ejemplos de integración de Spring con Quartz que encontré fueron codificación rígida de los shcedules en los archivos de configuración de Spring ... De todos modos, aquí es cómo Yo programo el trabajo:
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 es un trabajo simple que consiste en enviar correo electrónico utilizando la clase JavaMailSenderImpl de 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");
}
El problema es que necesito obtener una referencia a una instancia de esta clase (JavaMailSenderImpl) en mi clase EMailJob. Cuando trato de inyectarlo así:
@Autowired
private JavaMailSenderImpl mailSenderImpl;
no se inyecta - la referencia es NULL. Supongo que esto está sucediendo porque no es Spring quien instancia la clase EMailJob, sino Quartz, y Quartz no sabe nada sobre la inyección de dependencia ...
Entonces, ¿hay alguna forma de forzar que se produzca esta inyección?
¡Gracias!
Actualización 1: @Aaron: aquí hay una parte relevante del seguimiento de la pila desde el inicio, que muestra que el EMailJob se instancia dos veces:
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 ...
¡Gracias!
Actualización # 2: @Ryan:
Traté de usar SpringBeanJobFactory de la siguiente manera:
<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>
Y modifiqué mi clase principal para obtener el Programador de esta fábrica, en lugar de Quartz ':
@PostConstruct
public void initNotificationScheduler() {
try {
//sf = new StdSchedulerFactory("spring/quartz.properties");
//scheduler = sf.getScheduler();
scheduler = schedulerFactoryBean.getScheduler();
scheduler.start();
....
Pero cuando ejecuto la aplicación, obtengo errores, vea a continuación. Aquí está el stacktrace del inicio de Spring. Parece que el Programador en sí está bien creado, pero el error se produce cuando intenta crear una instancia de mi 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)
¡Gracias!
fuente
Solo puse
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
como primera línea de miJob.execute(JobExecutionContext context)
método.fuente
El mismo problema se ha resuelto en LINK :
Pude encontrar otra opción de la publicación en el foro de Spring que puede pasar una referencia al contexto de la aplicación Spring a través de SchedulerFactoryBean. Como el ejemplo que se muestra a continuación:
Luego, usando el siguiente código en su clase de trabajo, puede obtener el applicationContext y obtener el bean que desee.
Espero eso ayude. Puede obtener más información en el blog de Mark Mclaren
fuente
Tienes razón en tu suposición sobre Spring vs. Quartz instanciando la clase. Sin embargo, Spring proporciona algunas clases que le permiten realizar una inyección de dependencia primitiva en Quartz. Consulte SchedulerFactoryBean.setJobFactory () junto con SpringBeanJobFactory . Básicamente, al usar SpringBeanJobFactory, habilita la inyección de dependencia en todas las propiedades del trabajo, pero solo para los valores que están en el contexto del programador Quartz o el mapa de datos del trabajo . No sé cuáles son todos los estilos DI que admite (constructor, anotación, setter ...) pero sé que admite la inyección de setter.
fuente
para todos los que intentarán esto en el futuro.
org.springframework.scheduling.quartz.JobDetailBean proporciona un mapa de objetos y esos objetos pueden ser frijoles de primavera.
definir algo como
y luego, adentro
llama
myBean = (myBean) context.getMergedJobDataMap().get("myBean");
y listo. Lo sé, se ve feo, pero como solución funcionafuente
fuente
¡Gracias, Rippon! Finalmente conseguí que esto también funcionara, después de muchas luchas, ¡y mi solución está muy cerca de lo que sugirió! La clave fue hacer mi propio trabajo para extender QuartzJobBean y usar el planificadorContextAsMap.
Me escapé sin especificar la propiedad applicationContextSchedulerContextKey; funcionó sin ella para mí.
Para beneficio de otros, aquí está la configuración final que me ha funcionado:
Observe que el bean 'mailService "es mi propio bean de servicio, administrado por Spring. Pude acceder a él en mi trabajo de la siguiente manera:
Y esta configuración también me permitió programar trabajos dinámicamente, usando fábricas para obtener Triggers y JobDetails y estableciendo los parámetros requeridos en ellos mediante programación:
Muchas gracias de nuevo a todos los que ayudaron.
Puerto pequeño
fuente
Una solución simple es configurar el bean de primavera en el mapa de datos del trabajo y luego recuperar el bean en la clase de trabajo, por ejemplo
'
fuente
Así es como se ve el código con @Component:
Clase principal que programa el trabajo:
El EmailJob es el mismo que en mi primera publicación, excepto por la anotación @Component:
Y el archivo de configuración de Spring tiene:
¡Gracias por toda la ayuda!
Puerto pequeño
fuente
EmailJob
está inicializando? Una forma fácil de verificar es agregar una línea de registro en el constructor.Una solución de Hary https://stackoverflow.com/a/37797575/4252764 funciona muy bien. Es más simple, no necesita tantos beans de fábrica especiales y admite múltiples activadores y trabajos. Solo agregaría que el trabajo de Quartz puede hacerse genérico, con trabajos específicos implementados como Spring beans regulares.
fuente
Esta es una publicación bastante antigua que sigue siendo útil. Todas las soluciones que proponen estos dos tenían poca condición que no encajaran con todas:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
Esto asume o requiere que sea un proyecto basado en web de primaveraAutowiringSpringBeanJobFactory
El enfoque basado en la respuesta anterior es muy útil, pero la respuesta es específica para aquellos que no usan la API de cuarzo vainilla pura, sino la envoltura de Spring para que el cuarzo haga lo mismo.Si desea permanecer con la implementación pura de Quartz para la programación (Quartz con capacidades de cableado automático con Spring), pude hacerlo de la siguiente manera:
Estaba buscando hacerlo de forma de cuarzo tanto como fuera posible y, por lo tanto, un pequeño truco resulta útil.
quartzScheduler.setJobFactory(autowiringSpringBeanJobFactory);
nos da una instancia de trabajo con cableado automático. DadoAutowiringSpringBeanJobFactory
que implementa implícitamente aJobFactory
, ahora habilitamos una solución de cableado automático. ¡Espero que esto ayude!fuente
Una forma sencilla de hacerlo sería simplemente anotar los trabajos de Quartz con una
@Component
anotación, y luego Spring hará toda la magia DI por usted, ya que ahora se reconoce como un bean Spring. Tuve que hacer algo similar para unAspectJ
aspecto: no era un bean Spring hasta que lo anoté con el@Component
estereotipo Spring .fuente
EmailJob
clase en un paquete que Spring escaneará al iniciar la aplicación? El hecho de que haya anotado@Component
pero la clase inyectada sigue siendo nula indica que no se está escaneando; de lo contrario, la DI al inicio de la aplicación arrojaría una excepción.Esta es la respuesta correcta http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . y funcionará para la mayoría de la gente. Pero si su web.xml no conoce todos los archivos applicationContext.xml, el trabajo de cuarzo no podrá invocar esos beans. Tuve que hacer una capa adicional para inyectar archivos applicationContext adicionales
Puede agregar cualquier cantidad de archivos de contexto que desee que conozca su cuarzo.
fuente
Asegúrese de que su
la dependencia se extrae de
y NO de
Quería que usara
en vez de
por lo que no se pudo conectar automáticamente la instancia de trabajo.
fuente
Cuando ya usa AspectJ real en su proyecto, puede anotar la clase de bean de trabajo con
@Configurable
. Entonces Spring inyectará en esta clase, incluso si se construye a través denew
fuente
Me enfrenté al problema similar y salí de él con el siguiente enfoque:
En el código anterior, inyecto el bean dao.DAOFramework en el bean JobA y dentro del método ExecuteInternal puede obtener un bean inyectado como:
¡Espero que ayude! Gracias.
fuente
La solución anterior es excelente, pero en mi caso la inyección no funcionó. Necesitaba usar autowireBeanProperties en su lugar, probablemente debido a la forma en que está configurado mi contexto:
fuente
¡Todas esas soluciones anteriores no me funcionan con Spring 5 e Hibernate 5 y Quartz 2.2.3 cuando quiero llamar a métodos transaccionales!
Por lo tanto, implementé esta solución que inicia automáticamente el programador y activa los trabajos. Encontré mucho de ese código en dzone . Debido a que no necesito crear activadores y trabajos dinámicamente, quería que los activadores estáticos estuvieran predefinidos a través de Spring Configuration y que solo los trabajos se expongan como Spring Components.
Mi configuración básica se ve así
Como puede ver, tiene el programador y un activador de prueba simple que se define mediante una expresión cron. Obviamente, puede elegir cualquier expresión de programación que desee. Luego necesita AutowiringSpringBeanJobFactory que es así
Aquí puede conectar su contexto de aplicación normal y su trabajo. Esta es la brecha importante porque normalmente Quartz inicia sus subprocesos de trabajo que no tienen conexión con el contexto de su aplicación. Esa es la razón por la que no puede ejecutar métodos transaccionales. Lo último que falta es un trabajo. Puede verse así
No es una solución perfecta porque tiene una clase adicional solo para llamar a su método de servicio. Sin embargo, funciona.
fuente
Tienda de trabajo Jdbc
Si está utilizando jdbc jobstore, Quartz utiliza un cargador de clases diferente. Eso evita todas las soluciones para el cableado automático, ya que los objetos de Spring no serán compatibles en el lado del cuarzo, porque se originaron en un cargador de clases diferente.
Para solucionarlo, el cargador de clases predeterminado debe configurarse en el archivo de propiedades de cuarzo de esta manera:
Como referencia: https://github.com/quartz-scheduler/quartz/issues/221
fuente
Simplemente amplíe su trabajo desde
QuartzJobBean
fuente