inyectar referencia de frijol en un trabajo de Quartz en Spring?

94

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!

Puerto pequeño
fuente

Respuestas:

129

Puede usar esto SpringBeanJobFactorypara conectar automáticamente objetos de cuarzo usando resorte:

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

Luego, adjúntelo a su SchedulerBean(en este caso, con Java-config):

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

    ...

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

    ...

    return quartzScheduler;
}

Trabajando para mí, usando spring-3.2.1 y quartz-2.1.6.

Consulte la esencia completa aquí .

Encontré la solución en esta publicación de blog.

gelatinas
fuente
13
Deberías ganar un premio por esto, ¡es fantástico!
Nathan Feger
2
¡La solución es realmente genial! Todos los créditos para el autor de la publicación del blog :)
jelies
3
Gracias, ¡esto me salvó días! ¿Por qué Spring no ha proporcionado esta OOB? Este es el requisito muy básico para usar Quartz en Spring.
HandyManDan
4
esta debería ser la implementación predeterminada :)
Diego Plentz
2
gran solución, pero alguien tiene alguna idea de por qué AutowireCapableBeanFactory beanFactory está marcado como "transitorio"? AutowiringSpringBeanJobFactory no parece estar serializado de todos modos, por lo que tampoco será necesario serializar beanFactory
Marios
57

Solo puse SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);como primera línea de mi Job.execute(JobExecutionContext context)método.

msangel
fuente
7
Ésta es la verdadera solución. Probado con Spring 3.2.4.RELEASE y Quartz 2.2.0. ;)
aloplop85
3
@msangel - bueno, ambos funcionarán, pero el problema con el uso de SpringBeanAutowiringSupport en su trabajo de Quartz, es que la instancia de trabajo ahora necesita SABER acerca de Spring, lo que va en contra de la idea de IoC (inyección dep). Si ahora, por ejemplo, necesita usar CDI, todos sus trabajos de cuarzo deberán ajustarse, en lugar de solo la fábrica de un solo trabajo.
demaniak
2
Esto no me funcionó en una prueba unitaria ya que busca un contexto de aplicación web. Tuve que usar la respuesta de @jelies
Wim Deblauwe
5
Esta solución no funciona con Spring 4.1.4 y Quartz 2.2.1
skywalker
1
Yo también tuve este problema y probé esta solución. Está funcionando PERO crea una nueva instancia en lugar de usar una ya creada (singleton predeterminado). De todos modos, puede pasar cualquier cosa a su trabajo utilizando planificador.getContext (). Put ("objectName", object);
Krzysztof Cieśliński
13

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:

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

Luego, usando el siguiente código en su clase de trabajo, puede obtener el applicationContext y obtener el bean que desee.

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

Espero eso ayude. Puede obtener más información en el blog de Mark Mclaren

Rasgaduras
fuente
1
¡Gracias, @Rippon! Después de mucho intentarlo y fallar, he utilizado un enfoque muy similar al que sugirió: no he utilizado la propiedad applicationContextSchedulerContextKey y el contexto de la aplicación, pero utilicé el 'código' <nombre de propiedad = "planificadorContextAsMap"> <mapa> <clave de entrada = "mailService" value-ref = "mailService" /> </map> </property>
Marina
8

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.

Ryan Stewart
fuente
Hola, Ryan, gracias por tus sugerencias. ¿Quiere decir que tendría que usar SpringBeanFactoryJob, junto con Triggers preconfigurados y trabajos que extienden QuartzJobBean para habilitar la inyección de dependencia? El problema con este enfoque es que los desencadenantes se definen estáticamente en los archivos de configuración de Spring, donde necesito poder definir desencadenadores con horarios dinámicos en tiempo de ejecución ... Consulte mi siguiente respuesta a continuación para obtener más detalles: no hay suficiente espacio en el área de comentarios ...
Marina
@Marina: No, no es así como funciona. Utilice SpringBeanJobFactory y hágalo de la forma que desee. Simplemente funcionará. Además, no publique una respuesta que sea solo una actualización de su pregunta. En su lugar, edite su pregunta.
Ryan Stewart
lo siento, acabo de notar tu comentario! Lo probaré como sugieres y te haré saber los resultados. ¡Gracias por tu ayuda! Ah, e intentaré editar mi pregunta en lugar de responder ...
Marina
7

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

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

y luego, adentro

public void executeInternal(JobExecutionContext context)

llama myBean = (myBean) context.getMergedJobDataMap().get("myBean"); y listo. Lo sé, se ve feo, pero como solución funciona

usuario1196227
fuente
En mi humilde opinión, siento que esta solución es más limpia y "natural" que intentar agregar la capacidad de cableado automático a los trabajos de cuarzo, así que no creo que sea una solución alternativa.
realmente agradable
6
ApplicationContext springContext =

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

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

bean.method();
Damian
fuente
4

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

    <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 el bean 'mailService "es mi propio bean de servicio, administrado por Spring. Pude acceder a él en mi trabajo de la siguiente manera:

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

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:

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

Muchas gracias de nuevo a todos los que ayudaron.

Puerto pequeño

Puerto pequeño
fuente
4

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

// 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
fuente
teniendo en cuenta que los datos del trabajo se almacenan como blob (cuando se usa la persistencia de db), esto podría conducir a problemas de rendimiento de db (Imagine que los datos son realmente enormes)
Sudip Bhandari
3

Así es como se ve el código con @Component:

Clase principal que programa el trabajo:

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

El EmailJob es el mismo que en mi primera publicación, excepto por la anotación @Component:

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

Y el archivo de configuración de Spring tiene:

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

¡Gracias por toda la ayuda!

Puerto pequeño

Puerto pequeño
fuente
Cuando se inicia su aplicación, ¿ve que se EmailJobestá inicializando? Una forma fácil de verificar es agregar una línea de registro en el constructor.
Entra el
@ Aaron: sí, lo hago, pero como acabo de descubrir, ¡se está inicializando dos veces! Una vez por el propio marco de Spring (y apuesto a que esta instancia tiene el servicio de correo inyectado en él ...) y luego, más tarde, después de que se inicializa el propio Quartz, el EMailJob se inicializa nuevamente con el marco de Quartz, y ese es el único que no tiene el servicio inyectado ... Intentaré agregar un seguimiento de pila del inicio de Spring editando mi pregunta, como sugirió Ryan ...
Marina
2

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.

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
fuente
Gracias. También consideré eso. Pero en mi caso, estaba escribiendo una biblioteca que abstrae cualquier implementación de cuarzo. Esto es necesario para recordar el nombre de la 'clave' para recuperar cualquier objeto. Pude hacerlo en forma de cuarzo puro y lo publiqué como respuesta. ¡Comparte tus pensamientos!
Karthik R
2

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 primavera
  • AutowiringSpringBeanJobFactory 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.

 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 da una instancia de trabajo con cableado automático. Dado AutowiringSpringBeanJobFactoryque implementa implícitamente a JobFactory, ahora habilitamos una solución de cableado automático. ¡Espero que esto ayude!

Karthik R
fuente
1

Una forma sencilla de hacerlo sería simplemente anotar los trabajos de Quartz con una @Componentanotació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 un AspectJaspecto: no era un bean Spring hasta que lo anoté con el @Componentestereotipo Spring .

un tren
fuente
Gracias, Aaron, lo intenté, pero desafortunadamente sucede lo mismo NPE, y el servicio de correo no se inyecta en el bean de trabajo ...
Marina
¿Está su EmailJobclase en un paquete que Spring escaneará al iniciar la aplicación? El hecho de que haya anotado @Componentpero 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.
tren
Aaron: sí, se supone que debe escanearse. Tengo el <context: component-scan base-package = "com.mybasepackage"> que debería hacerlo ... En mi próxima respuesta, estoy proporcionando un código completo de mi principal clase, con la configuración de Spring, por si acaso se puede ver algo obvio ...
Marina
Los campos de trabajo marcados con "@Autowired" no se inyectan incluso si marca el trabajo con "@Component"
aloplop85
6
Esto no funcionará porque la creación de objetos de trabajo es administrada por cuartos y, por lo tanto, los campos no están conectados automáticamente y las anotaciones de clase no hacen nada sin un manejo adicional.
msangel
1

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

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

Puede agregar cualquier cantidad de archivos de contexto que desee que conozca su cuarzo.

vsingh
fuente
0

Asegúrese de que su

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

la dependencia se extrae de

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

y NO de

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

Quería que usara

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

en vez de

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

por lo que no se pudo conectar automáticamente la instancia de trabajo.

Dmitry
fuente
0

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

Ralph
fuente
0

Me enfrenté al problema similar y salí de él con el siguiente enfoque:

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

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:

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

¡Espero que ayude! Gracias.

Aman Goel
fuente
0

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:

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

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

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

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

}

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í

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

No es una solución perfecta porque tiene una clase adicional solo para llamar a su método de servicio. Sin embargo, funciona.

M46
fuente
0

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:

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

Como referencia: https://github.com/quartz-scheduler/quartz/issues/221

Deli Kristóf Demeter
fuente
0

Simplemente amplíe su trabajo desde 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
fuente