Spring의 Quartz 작업에 Bean 참조를 삽입합니까?


93

Spring에서 JobStoreTX 영구 저장소를 사용하여 Quartz 작업을 구성하고 예약했습니다. 나는 Spring의 Quartz 작업을 사용하지 않는다. 왜냐하면 런타임에 그것들을 동적으로 스케쥴해야하기 때문이다. 그리고 내가 발견 한 Spring과 Quartz를 통합하는 모든 예는 Spring 설정 파일에서 shcedules를 하드 코딩하는 것이었다. 어쨌든, 여기에 방법이있다. 작업 일정을 잡습니다.

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은 Spring의 JavaMailSenderImpl 클래스를 사용하여 이메일을 보내는 간단한 작업입니다.

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

}

문제는 내 EMailJob 클래스에서이 클래스 (JavaMailSenderImpl)의 인스턴스에 대한 참조를 가져와야한다는 것입니다. 다음과 같이 주입하려고 할 때 :

@Autowired
private JavaMailSenderImpl mailSenderImpl;

주입되지 않음-참조가 NULL입니다. EMailJob 클래스를 인스턴스화하는 것은 Spring이 아니기 때문에 이것이 일어나고 있다고 가정하고 있지만 Quartz와 Quartz는 의존성 주입에 대해 아무것도 모릅니다 ...

그렇다면이 주입을 강제 할 수있는 방법이 있습니까?

감사!

업데이트 1 : @Aaron : 다음은 EMailJob이 두 번 인스턴스화되었음을 보여주는 시작의 스택 추적 관련 부분입니다.

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

감사!

업데이트 # 2 : @Ryan :

다음과 같이 SpringBeanJobFactory를 사용하려고했습니다.

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

그리고 Quartz 대신이 공장에서 Scheduler를 가져 오도록 메인 클래스를 수정했습니다.

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

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

그러나 앱을 실행할 때 오류가 발생하면 아래를 참조하십시오. 다음은 Spring 시작의 스택 추적입니다. 스케줄러 자체가 잘 생성 된 것처럼 보이지만 내 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)

감사!

답변:


129

이것을 SpringBeanJobFactory사용하여 스프링을 사용하여 석영 오브젝트를 자동으로 자동 배선 할 수 있습니다 .

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

그런 다음 SchedulerBean(이 경우 Java-config 를 사용하여) 다음과 같이 연결하십시오 .

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

    ...

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

    ...

    return quartzScheduler;
}

나를 위해 일하면서 spring-3.2.1과 quartz-2.1.6을 사용했습니다.

여기 에서 전체 요점을 확인 하십시오 .

블로그 게시물 에서 해결책을 찾았 습니다.


13
이것에 대한 상을 수상해야합니다. 환상적입니다!
Nathan Feger 2013 년

2
정말 대단한 솔루션입니다! 블로그 게시물 작성자의 모든 크레딧 :)
jelies

3
감사합니다. 이로 인해 며칠이 절약되었습니다! Spring이이 OOB를 제공하지 않은 이유는 무엇입니까? 이것은 Spring에서 Quartz를 사용하기위한 매우 기본적인 요구 사항입니다.
HandyManDan 2013

4
이것이 기본 구현이어야합니다. :)
Diego Plentz 2014 년

2
훌륭한 솔루션이지만 AutowireCapableBeanFactory beanFactory가 "일시적"으로 표시된 이유를 아는 사람이 있습니까? AutowiringSpringBeanJobFactory는 어쨌든 직렬화되지 않은 것 같습니다. 그래서 beanFactory도 직렬화 될 필요가 없습니다
Marios

57

나는 SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);Job.execute(JobExecutionContext context)방법의 첫 번째 줄에 넣었습니다 .


7
이것이 진정한 해결책입니다. Spring 3.2.4.RELEASE 및 Quartz 2.2.0으로 테스트되었습니다. ;)
aloplop85 2013 년

3
@msangel-음, 둘 다 작동하지만 Quartz 작업에서 SpringBeanAutowiringSupport를 사용할 때의 문제는 작업 인스턴스가 이제 Spring에 대해 알아야한다는 것인데, 이는 IoC (dep injection)의 전체 아이디어에 위배됩니다. 예를 들어 지금 CDI를 사용해야하는 경우 하나의 작업 공장이 아닌 모든 석영 작업을 조정해야합니다.
demaniak

2
웹 응용 프로그램 컨텍스트를 검색하기 때문에 단위 테스트에서 작동하지 않았습니다. 나는 @jelies에서 답변을 사용했다
빔 Deblauwe

5
이 솔루션은 스프링 4.1.4 및 Quartz 2.2.1에서 작동하지 않습니다
skywalker

1
나도이 문제가 있었고이 해결책을 시도했습니다. 작동하지만 이미 생성 된 인스턴스 (기본 싱글 톤)를 사용하는 대신 새 인스턴스를 생성합니다. 어쨌든 scheduler.getContext (). put ( "objectName", object); 사용하여 작업에 무엇이든 전달할 수 있습니다.
Krzysztof Cieśliński

13

LINK 에서 동일한 문제가 해결되었습니다 .

Spring 포럼의 게시물에서 SchedulerFactoryBean을 통해 Spring 애플리케이션 컨텍스트에 대한 참조를 전달할 수있는 다른 옵션을 찾을 수있었습니다. 아래 표시된 예와 같습니다.

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

그런 다음 작업 클래스에서 아래 코드를 사용하여 applicationContext를 얻고 원하는 빈을 얻을 수 있습니다.

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

도움이되기를 바랍니다. Mark Mclaren의 블로그 에서 더 많은 정보를 얻을 수 있습니다.


1
감사합니다, @Rippon! 많은 시도와 실패 끝에 나는 당신이 제안한 매우 유사한 접근 방식을 사용했습니다. 나는 applicationContextSchedulerContextKey 속성과 응용 프로그램 컨텍스트를 사용하지 않았지만 'code'<property name = "schedulerContextAsMap"> <map> <entry key =를 사용했습니다. "MailService 인터페이스"값-REF = "MailService 인터페이스"/> </지도> </ 속성>
마리나

8

클래스를 인스턴스화하는 Spring 대 Quartz에 대한 가정이 맞습니다. 그러나 Spring은 Quartz에서 기본적인 의존성 주입을 할 수있는 몇 가지 클래스를 제공합니다. SpringBeanJobFactory 와 함께 SchedulerFactoryBean.setJobFactory () 를 확인하십시오 . 기본적으로 SpringBeanJobFactory를 사용하여 모든 Job 속성에 대한 종속성 주입을 활성화하지만 Quartz 스케줄러 컨텍스트 또는 작업 데이터 맵 에있는 값에 대해서만 가능합니다 . 나는 그것이 지원하는 모든 DI 스타일 (생성자, 주석, 세터 ...)을 모르지만 그것이 세터 주입을 지원한다는 것을 압니다.


안녕, 라이언, 제안 해주셔서 감사합니다. 의존성 주입을 활성화하기 위해 미리 구성된 트리거 및 QuartzJobBean을 확장하는 작업과 함께 SpringBeanFactoryJob을 사용해야한다는 의미입니까? 이 접근 방식의 문제는 트리거가 Spring의 구성 파일에서 정적으로 정의된다는 것입니다. 여기서 런타임에 동적 일정으로 트리거를 정의 할 수 있어야합니다. 자세한 내용은 아래의 내 다음 답변을 참조하십시오. 댓글 영역 ...
Marina

@Marina : 아니, 그게 작동하는 방식이 아닙니다. SpringBeanJobFactory를 사용하고 원하는 방식으로 수행하십시오. 그냥 작동합니다. 또한 질문에 대한 업데이트 일 뿐인 답변을 게시하지 마십시오. 대신 질문을 수정하세요.
Ryan Stewart

죄송합니다, 방금 귀하의 의견을 발견했습니다! 나는 당신이 제안한대로 그것을 시도하고 결과를 알려줄 것입니다. ! 당신의 도움을 주셔서 감사합니다 아, 그리고 ... 대답 대신 내 질문을 수정하려고합니다
마리나

7

미래에 이것을 시도 할 모든 사람들을 위해.

org.springframework.scheduling.quartz.JobDetailBean은 객체의 맵을 제공하며 이러한 객체는 스프링 빈일 수 있습니다.

같은 것을 정의하다

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

그리고 내부

public void executeInternal(JobExecutionContext context)

전화 myBean = (myBean) context.getMergedJobDataMap().get("myBean"); 하면 모두 준비됩니다. 보기 흉해 보이지만 해결 방법으로 작동합니다.


IMHO 저는이 솔루션이 석영 작업에 자동 배선 기능을 추가하려는 것보다 더 깨끗하고 "자연 스럽습니다"라고 생각하므로 해결 방법이 아니라고 생각합니다.
reallynice

6
ApplicationContext springContext =

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

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

bean.method();

4

감사합니다, Rippon! 많은 어려움을 겪은 후에도 마침내이 작업을 수행했으며 내 솔루션은 귀하가 제안한 것과 매우 유사합니다! 핵심은 QuartzJobBean을 확장하고 schedulerContextAsMap을 사용하기 위해 내 자신의 Job을 만드는 것이 었습니다.

나는 applicationContextSchedulerContextKey 속성을 지정하지 않고 도망 쳤습니다.

다른 사람들의 이익을 위해 다음은 저에게 도움이 된 최종 구성입니다.

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

'mailService'bean은 Spring에서 관리하는 내 서비스 bean입니다. 다음과 같이 내 Job에서 액세스 할 수있었습니다.

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

또한이 구성을 사용하면 팩토리를 사용하여 Triggers 및 JobDetails를 가져오고 프로그래밍 방식으로 필요한 매개 변수를 설정하여 작업을 동적으로 예약 할 수 있습니다.

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

도와 주신 모든 분들께 다시 한번 감사드립니다.

마리나


4

간단한 해결책은 작업 데이터 맵에 스프링 빈을 설정 한 다음 작업 클래스에서 빈을 검색하는 것입니다.

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

그 작업 데이터를 고려하는 BLOB로 저장됩니다 (사용하는 경우 DB 지속성)이 (정말 거대한 데이터를 상상) DB 성능 문제로 이어질 수
Sudip 반 다리

3

@Component의 코드는 다음과 같습니다.

작업을 예약하는 기본 클래스 :

public class NotificationScheduler {

private SchedulerFactory sf;
private Scheduler scheduler;

@PostConstruct
public void initNotificationScheduler() {
    try {
    sf = new StdSchedulerFactory("spring/quartz.properties");
    scheduler = sf.getScheduler();
    scheduler.start();
            // test out sending a notification at startup, prepare some parameters...
    this.scheduleImmediateNotificationJob(messageParameters, recipients);
        try {
            // wait 20 seconds to show jobs
            logger.info("sleeping...");
            Thread.sleep(40L * 1000L); 
            logger.info("finished sleeping");
           // executing...
        } catch (Exception ignore) {
        }

      } catch (SchedulerException e) {
    e.printStackTrace();
    throw new RuntimeException("NotificationScheduler failed to retrieve a Scheduler instance: ", e);
    }
}


public void scheduleImmediateNotificationJob(){
  try {
    JobKey jobKey = new JobKey("key");
    Date fireTime = DateBuilder.futureDate(delayInSeconds, IntervalUnit.SECOND);
    JobDetail emailJob = JobBuilder.newJob(EMailJob.class)
    .withIdentity(jobKey.toString(), "immediateEmailsGroup")
        .build();

    TriggerKey triggerKey = new TriggerKey("triggerKey");
    SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger() 
        .withIdentity(triggerKey.toString(), "immediateEmailsGroup")
        .startAt(fireTime)
        .build();

    // schedule the job to run
    Date scheduleTime1 = scheduler.scheduleJob(emailJob, trigger);
  } catch (SchedulerException e) {
    logger.error("error scheduling job: " + e.getMessage(), e);
    e.printStackTrace();
      }
}

@PreDestroy
public void cleanup(){
    sf = null;
    try {
        scheduler.shutdown();
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
}

EmailJob은 @Component 주석을 제외하고 첫 번째 게시물과 동일합니다.

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

그리고 Spring의 구성 파일에는 다음이 있습니다.

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

모든 도움에 감사드립니다!

마리나


앱이 시작될 때 EmailJob초기화되는 것이 보이 나요? 확인하는 쉬운 방법은 생성자에 로그 줄을 추가하는 것입니다.
atrain

@Aaron : 예, 그렇습니다.하지만 방금 발견했듯이 두 번 초기화되고 있습니다! 한 번 Spring 프레임 워크 자체에 의해 (그리고이 인스턴스에 메일 서비스가 주입되었다고 확신합니다 ...) 나중에 Quartz 자체가 초기화 된 후 EMailJob이 Quartz 프레임 워크에 의해 다시 초기화됩니다. 서비스가 주입되지 않은 경우 ... Ryan이 제안한대로 내 질문을 편집하여 Spring의 시작에 대한 스택 추적을 추가하려고합니다 ...
Marina

2

Hary https://stackoverflow.com/a/37797575/4252764 의 솔루션 이 매우 잘 작동합니다. 더 간단하고 특별한 팩토리 빈이 많이 필요하지 않으며 여러 트리거 및 작업을 지원합니다. Quartz 작업이 일반 Spring Bean으로 구현 된 특정 작업으로 일반화 될 수 있다고 추가 할 수 있습니다.

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
  }

}

감사. 나도 그것을 고려했다. 하지만 제 경우에는 석영 구현을 추상화하는 라이브러리를 작성했습니다. 이것은 객체를 검색하기 위해 '키'이름을 기억하는 데 필요했습니다. 순수한 석영 방식으로 할 수 있었고 답변으로 게시했습니다. Pls는 당신의 생각을 공유합니다!
Karthik R

1

이를 수행하는 간단한 방법은 Quartz Job에 @Component주석을 달아주는 것입니다. 그러면 Spring은 이제 Spring Bean으로 인식되므로 모든 DI 마법을 수행합니다. 나는 AspectJaspect에 대해 비슷한 것을해야했다 @Component. Spring 스테레오 타입으로 주석을 달기 전까지는 Spring bean이 아니었다 .


고마워요, 아론, 방금 시도했지만 안타깝게도 동일한 NPE가 발생하고 메일 서비스가 작업 빈에 주입되지 않습니다 ...
Marina

사용자가 EmailJob응용 프로그램 시작에 봄에서 스캔 할 패키지의 클래스는? 주석을 달았 @Component지만 삽입 된 클래스가 여전히 null 이라는 사실은 해당 클래스가 스캔되지 않음을 나타냅니다. 그렇지 않으면 앱 시작시 DI에서 예외가 발생합니다.
atrain

Aaron : 예, 스캔해야합니다. <context : component-scan base-package = "com.mybasepackage">이 작업을 수행해야합니다 ... 다음 답변에서는 메인의 전체 코드를 제공합니다. 클래스, Spring 구성-명백한 것을 발견 할 수있는 경우를 대비하여 ...
Marina

"@Autowired"로 표시된 작업 필드는 "@Component"로 작업을 표시해도 삽입되지 않습니다.
aloplop85

6
Job 객체 생성은 쿼트에 의해 관리되므로 필드가 자동 연결되지 않고 클래스 주석은 추가 처리 없이는 아무 작업도 수행하지 않으므로 작동하지 않습니다.
msangel 2013 년

1

이것은 정답입니다 http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . 대부분의 사람들을 위해 일할 것입니다. 그러나 web.xml이 모든 applicationContext.xml 파일을 인식하지 못하는 경우 Quartz 작업은 해당 Bean을 호출 할 수 없습니다. 추가 applicationContext 파일을 주입하기 위해 추가 레이어를 수행해야했습니다.

public class MYSpringBeanJobFactory extends SpringBeanJobFactory
        implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {

        try {
                PathMatchingResourcePatternResolver pmrl = new PathMatchingResourcePatternResolver(context.getClassLoader());
                Resource[] resources = new Resource[0];
                GenericApplicationContext createdContext = null ;
                    resources = pmrl.getResources(
                            "classpath*:my-abc-integration-applicationContext.xml"
                    );

                    for (Resource r : resources) {
                        createdContext = new GenericApplicationContext(context);
                        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(createdContext);
                        int i = reader.loadBeanDefinitions(r);
                    }

            createdContext.refresh();//important else you will get exceptions.
            beanFactory = createdContext.getAutowireCapableBeanFactory();

        } catch (IOException e) {
            e.printStackTrace();
        }



    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle)
            throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

Quartz가 인식 할 수있는 컨텍스트 파일을 원하는만큼 추가 할 수 있습니다.


1

이것은 여전히 ​​유용한 꽤 오래된 게시물입니다. 이 두 가지를 제안하는 모든 솔루션에는 모두 적합하지 않은 조건이 거의 없었습니다.

  • SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); 이것은 봄-웹 기반 프로젝트라고 가정하거나 요구합니다.
  • AutowiringSpringBeanJobFactory 이전 답변에서 언급 한 기반 접근 방식은 매우 유용하지만 대답은 순수한 바닐라 석영 API를 사용하지 않고 석영이 동일한 작업을 수행하는 Spring의 래퍼를 사용하는 사람들에게만 해당됩니다.

스케줄링 (Spring과 Autowiring 기능이있는 Quartz)을위한 순수 Quartz 구현을 유지하고 싶다면 다음과 같이 할 수있었습니다.

나는 가능한 한 석영 방식으로 그것을 찾고 있었으므로 약간의 해킹이 도움이되는 것으로 입증되었습니다.

 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);자동 연결 작업 인스턴스를 제공합니다. 를 AutowiringSpringBeanJobFactory암시 적으로 구현 하므로 JobFactory이제 자동 연결 솔루션을 활성화했습니다. 도움이 되었기를 바랍니다!


0

당신의

AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 

의존성이

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

에서가 아니라

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

내가 사용하고 싶었어

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

대신에

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)

그래서 작업 인스턴스를 자동 연결하지 못했습니다.


0

프로젝트에서 실제 AspectJ를 이미 사용하고 있다면 작업 빈 클래스에 @Configurable. 그러면 Spring은 다음을 통해 구성 되더라도이 클래스에 주입합니다.new


0

나는 비슷한 문제에 직면했고 다음과 같은 접근 방식으로 나왔습니다.

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

위 코드에서 나는 dao.DAOFramework 빈을 JobA 빈에 주입하고 ExecuteInternal 메서드 내부에서 다음과 같이 주입 된 빈을 얻을 수 있습니다.

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

도움이 되었기를 바랍니다. 감사합니다.


0

위의 솔루션은 훌륭하지만 제 경우에는 주사가 작동하지 않았습니다. 내 컨텍스트가 구성된 방식으로 인해 대신 autowireBeanProperties를 사용해야했습니다.

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

0

위의 모든 솔루션은 트랜잭션 메서드를 호출 할 때 Spring 5와 Hibernate 5 및 Quartz 2.2.3에서 작동하지 않습니다!

따라서 스케줄러를 자동으로 시작하고 작업을 트리거하는이 솔루션을 구현했습니다. dzone 에서 그 코드를 많이 찾았습니다. . 동적으로 트리거와 작업을 만들 필요가 없기 때문에 정적 트리거가 Spring 구성을 통해 미리 정의되고 작업 만 Spring 구성 요소로 노출되기를 원했습니다.

내 기본 구성은 다음과 같습니다.

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

}

보시다시피 스케줄러와 cron 표현식을 통해 정의 된 간단한 테스트 트리거가 있습니다. 원하는 일정표를 선택할 수 있습니다. 그런 다음 다음과 같은 AutowiringSpringBeanJobFactory가 필요합니다.

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

}

여기에서 일반적인 애플리케이션 컨텍스트와 작업을 함께 연결합니다. 일반적으로 Quartz가 애플리케이션 컨텍스트에 연결되지 않은 작업자 스레드를 시작하기 때문에 이것은 중요한 차이입니다. 이것이 트랜잭션 방식을 실행할 수없는 이유입니다. 마지막으로 빠진 것은 직업입니다. 그렇게 보일 수 있습니다

@Component
public class CronTest implements Job {

  @Autowired
  private MyService s;

  public CronTest() {
  }

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

}

서비스 메서드를 호출하기위한 추가 클래스이기 때문에 완벽한 솔루션은 아닙니다. 그러나 그럼에도 불구하고 작동합니다.


0

Jdbc 작업 저장소

jdbc jobstore를 사용하는 경우 Quartz는 다른 클래스 로더를 사용합니다. 이는 스프링의 객체가 다른 클래스 로더에서 시작 되었기 때문에 쿼츠 측에서 호환되지 않기 때문에 자동 배선에 대한 모든 해결 방법을 방지합니다.

이를 수정하려면 다음과 같이 quartz 속성 파일에 기본 클래스 로더를 설정해야합니다.

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

참고로 : https://github.com/quartz-scheduler/quartz/issues/221


0

간단히 당신의 직업을 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());
    }

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.