Udało mi się skonfigurować i zaplanować zadanie Quartz za pomocą stałego magazynu JobStoreTX na wiosnę. Nie używam zadań Spring's Quartz, ponieważ muszę planować je dynamicznie, w czasie wykonywania, a wszystkie przykłady integracji Springa z Quartz, które znalazłem, to sztywne kodowanie shcedules w plikach konfiguracyjnych Spring ... W każdym razie, oto jak Planuję pracę:
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 to prosta praca polegająca na wysyłaniu wiadomości e-mail przy użyciu klasy JavaMailSenderImpl Springa.
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");
}
Problem polega na tym, że muszę uzyskać odwołanie do wystąpienia tej klasy (JavaMailSenderImpl) w mojej klasie EMailJob. Kiedy próbuję wstrzyknąć to w ten sposób:
@Autowired
private JavaMailSenderImpl mailSenderImpl;
nie jest wtryskiwany - odniesienie ma wartość NULL. Zakładam, że tak się dzieje, ponieważ to nie Spring tworzy instancję klasy EMailJob, ale Quartz, a Quartz nic nie wie o wstrzykiwaniu zależności ...
Czy jest więc jakiś sposób, aby wymusić ten zastrzyk?
dzięki!
Aktualizacja 1: @Aaron: tutaj jest odpowiednia część śledzenia stosu ze startu, która pokazuje, że zadanie EMailJob zostało utworzone dwukrotnie:
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 ...
dzięki!
Aktualizacja # 2: @Ryan:
Próbowałem użyć SpringBeanJobFactory w następujący sposób:
<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>
I zmodyfikowałem moją główną klasę, aby pobrać harmonogram z tej fabryki zamiast Quartz:
@PostConstruct
public void initNotificationScheduler() {
try {
//sf = new StdSchedulerFactory("spring/quartz.properties");
//scheduler = sf.getScheduler();
scheduler = schedulerFactoryBean.getScheduler();
scheduler.start();
....
Ale kiedy uruchamiam aplikację - pojawiają się błędy, patrz poniżej. Oto ślad stosu z uruchomienia Springa. Wygląda na to, że sam harmonogram został utworzony w porządku, ale błąd pojawia się, gdy próbuje utworzyć instancję mojego zadania e-mail:
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)
dzięki!
źródło
Po prostu wstawiłem
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
jako pierwszą linię mojejJob.execute(JobExecutionContext context)
metody.źródło
Ten sam problem został rozwiązany w LINK :
Znalazłem inną opcję z postu na forum Spring, że możesz przekazać odwołanie do kontekstu aplikacji Spring za pośrednictwem SchedulerFactoryBean. Podobnie jak w przykładzie pokazanym poniżej:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <propertyy name="triggers"> <list> <ref bean="simpleTrigger"/> </list> </property> <property name="applicationContextSchedulerContextKey"> <value>applicationContext</value> </property>
Następnie używając poniższego kodu w swojej klasie pracy, możesz pobrać applicationContext i otrzymać dowolną fasolkę.
appCtx = (ApplicationContext)context.getScheduler().getContext().get("applicationContextSchedulerContextKey");
Mam nadzieję, że to pomoże. Więcej informacji znajdziesz w blogu Marka Mclaren'a
źródło
Masz rację w swoich założeniach dotyczących tworzenia instancji klasy przez Spring vs. Quartz. Jednak Spring udostępnia kilka klas, które pozwalają wykonać pewne podstawowe iniekcje zależności w Quartz. Sprawdź SchedulerFactoryBean.setJobFactory () wraz z SpringBeanJobFactory . Zasadniczo, używając SpringBeanJobFactory, włączasz iniekcję zależności we wszystkich właściwościach zadania , ale tylko dla wartości, które znajdują się w kontekście harmonogramu kwarcowego lub na mapie danych zadania . Nie wiem, jakie wszystkie style DI obsługuje (konstruktor, adnotacja, ustawiacz ...), ale wiem, że obsługuje wstrzykiwanie settera.
źródło
dla wszystkich, którzy spróbują tego w przyszłości.
org.springframework.scheduling.quartz.JobDetailBean dostarcza mapy obiektów, które mogą być wiosennymi fasolami.
zdefiniować coś takiego
<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>
a potem w środku
public void executeInternal(JobExecutionContext context)
zadzwoń
myBean = (myBean) context.getMergedJobDataMap().get("myBean");
i wszystko gotowe. Wiem, wygląda brzydko, ale jako obejście działaźródło
ApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(ContextLoaderListener .getCurrentWebApplicationContext().getServletContext()); Bean bean = (Bean) springContext.getBean("beanName"); bean.method();
źródło
Dzięki, Rippon! W końcu to również zadziałało, po wielu zmaganiach, a moje rozwiązanie jest bardzo bliskie temu, co zasugerowałeś! Kluczem było stworzenie własnego zadania, które rozszerzyło QuartzJobBean i użycie SchedulerContextAsMap.
Udało mi się bez określenia właściwości applicationContextSchedulerContextKey - działało bez niej.
Z korzyścią dla innych, oto ostateczna konfiguracja, która zadziałała dla mnie:
<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>
Zauważ, że komponent bean „mailService” to mój własny komponent bean usług, zarządzany przez Spring. Miałem do niego dostęp w moim zadaniu w następujący sposób:
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"); ....
Ta konfiguracja pozwoliła mi również na dynamiczne planowanie zadań, używając fabryk do pobierania wyzwalaczy i szczegółów zadań oraz programowego ustawiania wymaganych parametrów:
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"); ...
Jeszcze raz wielkie dzięki dla wszystkich, którzy pomogli,
przystań
źródło
Prostym rozwiązaniem jest ustawienie fasoli wiosennej w mapie danych zadania, a następnie pobranie jej na przykład w klasie zadania
// 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");
źródło
Oto jak wygląda kod z @Component:
Główna klasa, która planuje pracę:
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 jest taki sam jak w moim pierwszym wpisie, z wyjątkiem adnotacji @Component:
@Component public class EMailJob implements Job { @Autowired private JavaMailSenderImpl mailSenderImpl; ... }
A plik konfiguracyjny Springa zawiera:
... <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>
Dzięki za całą pomoc!
przystań
źródło
EmailJob
inicjalizację? Łatwym sposobem sprawdzenia jest dodanie wiersza dziennika w konstruktorze.Rozwiązanie od Hary https://stackoverflow.com/a/37797575/4252764 działa bardzo dobrze. Jest prostszy, nie wymaga tak wielu specjalnych fabrycznych fasoli i obsługuje wiele wyzwalaczy i zadań. Chciałbym tylko dodać, że zadanie kwarcowe może być generyczne, z określonymi zadaniami zaimplementowanymi jako zwykłe ziarna wiosenne.
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 } }
źródło
To dość stary post, który wciąż jest przydatny. Wszystkie rozwiązania, które proponują te dwa, miały niewielki warunek, który nie pasowałby wszystkim:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
Zakłada to lub wymaga, aby był to projekt oparty na sieci wiosennejAutowiringSpringBeanJobFactory
Podejście oparte na wspomnianym w poprzedniej odpowiedzi jest bardzo pomocne, ale odpowiedź jest specyficzna dla tych, którzy nie używają czystego waniliowego kwarcowego interfejsu API, ale raczej opakowanie Springa do kwarcu, aby zrobić to samo.Jeśli chcesz pozostać przy implementacji czystego Quartz do planowania (Quartz z możliwościami Autowiring ze Spring), mogłem to zrobić w następujący sposób:
Chciałem zrobić to kwarcowo tak często, jak to możliwe, dlatego mały hack okazuje się pomocny.
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);
daje nam automatyczną instancję pracy. PonieważAutowiringSpringBeanJobFactory
niejawnie implementuje aJobFactory
, teraz włączyliśmy rozwiązanie z możliwością automatycznego łączenia. Mam nadzieję że to pomoże!źródło
Prostym sposobem na zrobienie tego byłoby po prostu dodanie adnotacji do Quartz Jobs
@Component
adnotacją, a następnie Spring wykona za Ciebie całą magię DI, ponieważ jest teraz rozpoznawany jako ziarno wiosenne. Musiałem zrobić coś podobnego dlaAspectJ
aspektu - to nie był wiosenny bean, dopóki nie oznaczyłem go@Component
stereotypem wiosny .źródło
EmailJob
klasa znajduje się w pakiecie, który zostanie przeskanowany przez Spring podczas uruchamiania aplikacji? Fakt, że dodano adnotację,@Component
ale wstrzyknięta klasa nadal ma wartość null, oznacza, że nie jest skanowana - w przeciwnym razie DI podczas uruchamiania aplikacji zgłosi wyjątek.Powyższe rozwiązanie jest świetne ale w moim przypadku wtrysk nie działał. Zamiast tego musiałem użyć autowireBeanProperties, prawdopodobnie ze względu na sposób skonfigurowania mojego kontekstu:
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; } }
źródło
To jest właściwa odpowiedź http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring/15211030#15211030 . i będzie działać dla większości ludzi. Ale jeśli twój web.xml nie zna wszystkich plików applicationContext.xml, zadanie kwarcowe nie będzie mogło wywołać tych ziaren. Musiałem zrobić dodatkową warstwę, aby wstrzyknąć dodatkowe pliki 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; } }
Możesz dodać dowolną liczbę plików kontekstowych, o których ma wiedzieć Twój kwarc.
źródło
Upewnij się, że twój
AutowiringSpringBeanJobFactory extends SpringBeanJobFactory
zależność jest wyciągnięta z
"org.springframework:spring-context-support:4..."
a NIE z
"org.springframework:spring-support:2..."
Chciał, żebym użył
@Override public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler)
zamiast
@Override protected Object createJobInstance(final TriggerFiredBundle bundle)
więc nie powiodło się automatyczne podłączanie instancji zadania.
źródło
Jeśli używasz już prawdziwego AspectJ w swoim projekcie, możesz dodać adnotację do klasy Job Bean
@Configurable
. Następnie Spring wstrzyknie do tej klasy, nawet jeśli jest skonstruowana za pomocąnew
źródło
Zmierzyłem się z podobnym problemem i wyszedłem z niego w następujący sposób:
<!-- 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>
W powyższym kodzie wstawiam bean dao.DAOFramework do fasoli JobA, aw metodzie ExecuteInternal możesz dostać taką bean:
daoFramework = (DAOFramework)context.getMergedJobDataMap().get("daoBean");
Mam nadzieję, że to pomoże! Dziękuję Ci.
źródło
Wszystkie powyższe rozwiązania nie działają dla mnie ze Spring 5 i Hibernate 5 oraz Quartz 2.2.3, gdy chcę wywołać metody transakcyjne!
Dlatego wdrożyłem to rozwiązanie, które automatycznie uruchamia harmonogram i wyzwala zadania. Znalazłem dużo tego kodu w dzone . Ponieważ nie muszę dynamicznie tworzyć wyzwalaczy i zadań, chciałem, aby statyczne wyzwalacze były wstępnie zdefiniowane za pomocą konfiguracji Spring, a tylko zadania były ujawniane jako komponenty Spring.
Moja podstawowa konfiguracja wygląda tak
@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; } }
Jak widać, masz harmonogram i prosty wyzwalacz testowy, który jest definiowany za pomocą wyrażenia cron. Możesz oczywiście wybrać dowolne wyrażenie planowania. Następnie potrzebujesz AutowiringSpringBeanJobFactory, która działa w ten sposób
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); } }
Tutaj łączysz swój normalny kontekst aplikacji i swoją pracę. Jest to ważna luka, ponieważ normalnie Quartz uruchamia swoje wątki robocze, które nie mają połączenia z kontekstem aplikacji. To jest powód, dla którego nie możesz wykonywać mehtodów transakcyjnych. Ostatnią rzeczą, której brakuje, jest praca. Może tak wyglądać
@Component public class CronTest implements Job { @Autowired private MyService s; public CronTest() { } @Override public void execute(JobExecutionContext context) throws JobExecutionException { s.execute(); } }
To nie jest idealne rozwiązanie, ponieważ masz dodatkową klasę tylko do wywoływania metody usługi. Niemniej jednak działa.
źródło
Jdbc jobstore
Jeśli korzystasz z jdbc jobstore, Quartz używa innego modułu ładującego klasy. Zapobiega to wszelkim obejściom automatycznego okablowania, ponieważ obiekty ze sprężyny nie będą kompatybilne po stronie kwarcu, ponieważ pochodzą z innego modułu ładującego klasy.
Aby to naprawić, domyślny program ładujący klasy musi być ustawiony w pliku właściwości kwarcu w następujący sposób:
org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.ThreadContextClassLoadHelper
Jako odniesienie: https://github.com/quartz-scheduler/quartz/issues/221
źródło
Po prostu przedłuż swoją pracę z
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()); } }
źródło