/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.scheduling.quartz;

import java.lang.reflect.InvocationTargetException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Scheduler;
import org.quartz.impl.JobDetailImpl;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.support.ArgumentConvertingMethodInvoker;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;

FactoryBean that exposes a JobDetail object which delegates job execution to a specified (static or non-static) method. Avoids the need for implementing a one-line Quartz Job that just invokes an existing service method on a Spring-managed target bean.

Inherits common configuration properties from the MethodInvoker base class, such as "targetObject" and "targetMethod", adding support for lookup of the target bean by name through the "targetBeanName" property (as alternative to specifying a "targetObject" directly, allowing for non-singleton target objects).

Supports both concurrently running jobs and non-currently running jobs through the "concurrent" property. Jobs created by this MethodInvokingJobDetailFactoryBean are by default volatile and durable (according to Quartz terminology).

NOTE: JobDetails created via this FactoryBean are not serializable and thus not suitable for persistent job stores. You need to implement your own Quartz Job as a thin wrapper for each case where you want a persistent job to delegate to a specific service method.

Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.

Author:Juergen Hoeller, Alef Arendsen
See Also:
Since:18.02.2004
/** * {@link org.springframework.beans.factory.FactoryBean} that exposes a * {@link org.quartz.JobDetail} object which delegates job execution to a * specified (static or non-static) method. Avoids the need for implementing * a one-line Quartz Job that just invokes an existing service method on a * Spring-managed target bean. * * <p>Inherits common configuration properties from the {@link MethodInvoker} * base class, such as {@link #setTargetObject "targetObject"} and * {@link #setTargetMethod "targetMethod"}, adding support for lookup of the target * bean by name through the {@link #setTargetBeanName "targetBeanName"} property * (as alternative to specifying a "targetObject" directly, allowing for * non-singleton target objects). * * <p>Supports both concurrently running jobs and non-currently running * jobs through the "concurrent" property. Jobs created by this * MethodInvokingJobDetailFactoryBean are by default volatile and durable * (according to Quartz terminology). * * <p><b>NOTE: JobDetails created via this FactoryBean are <i>not</i> * serializable and thus not suitable for persistent job stores.</b> * You need to implement your own Quartz Job as a thin wrapper for each case * where you want a persistent job to delegate to a specific service method. * * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1. * * @author Juergen Hoeller * @author Alef Arendsen * @since 18.02.2004 * @see #setTargetBeanName * @see #setTargetObject * @see #setTargetMethod * @see #setConcurrent */
public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker implements FactoryBean<JobDetail>, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean { @Nullable private String name; private String group = Scheduler.DEFAULT_GROUP; private boolean concurrent = true; @Nullable private String targetBeanName; @Nullable private String beanName; @Nullable private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); @Nullable private BeanFactory beanFactory; @Nullable private JobDetail jobDetail;
Set the name of the job.

Default is the bean name of this FactoryBean.

/** * Set the name of the job. * <p>Default is the bean name of this FactoryBean. */
public void setName(String name) { this.name = name; }
Set the group of the job.

Default is the default group of the Scheduler.

See Also:
  • DEFAULT_GROUP.DEFAULT_GROUP
/** * Set the group of the job. * <p>Default is the default group of the Scheduler. * @see org.quartz.Scheduler#DEFAULT_GROUP */
public void setGroup(String group) { this.group = group; }
Specify whether or not multiple jobs should be run in a concurrent fashion. The behavior when one does not want concurrent jobs to be executed is realized through adding the @PersistJobDataAfterExecution and @DisallowConcurrentExecution markers. More information on stateful versus stateless jobs can be found here.

The default setting is to run jobs concurrently.

/** * Specify whether or not multiple jobs should be run in a concurrent fashion. * The behavior when one does not want concurrent jobs to be executed is * realized through adding the {@code @PersistJobDataAfterExecution} and * {@code @DisallowConcurrentExecution} markers. * More information on stateful versus stateless jobs can be found * <a href="http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-03">here</a>. * <p>The default setting is to run jobs concurrently. */
public void setConcurrent(boolean concurrent) { this.concurrent = concurrent; }
Set the name of the target bean in the Spring BeanFactory.

This is an alternative to specifying "targetObject", allowing for non-singleton beans to be invoked. Note that specified "targetObject" and "targetClass" values will override the corresponding effect of this "targetBeanName" setting (i.e. statically pre-define the bean type or even the bean object).

/** * Set the name of the target bean in the Spring BeanFactory. * <p>This is an alternative to specifying {@link #setTargetObject "targetObject"}, * allowing for non-singleton beans to be invoked. Note that specified * "targetObject" and {@link #setTargetClass "targetClass"} values will * override the corresponding effect of this "targetBeanName" setting * (i.e. statically pre-define the bean type or even the bean object). */
public void setTargetBeanName(String targetBeanName) { this.targetBeanName = targetBeanName; } @Override public void setBeanName(String beanName) { this.beanName = beanName; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } @Override protected Class<?> resolveClassName(String className) throws ClassNotFoundException { return ClassUtils.forName(className, this.beanClassLoader); } @Override @SuppressWarnings("unchecked") public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException { prepare(); // Use specific name if given, else fall back to bean name. String name = (this.name != null ? this.name : this.beanName); // Consider the concurrent flag to choose between stateful and stateless job. Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); // Build JobDetail instance. JobDetailImpl jdi = new JobDetailImpl(); jdi.setName(name != null ? name : toString()); jdi.setGroup(this.group); jdi.setJobClass((Class) jobClass); jdi.setDurability(true); jdi.getJobDataMap().put("methodInvoker", this); this.jobDetail = jdi; postProcessJobDetail(this.jobDetail); }
Callback for post-processing the JobDetail to be exposed by this FactoryBean.

The default implementation is empty. Can be overridden in subclasses.

Params:
  • jobDetail – the JobDetail prepared by this FactoryBean
/** * Callback for post-processing the JobDetail to be exposed by this FactoryBean. * <p>The default implementation is empty. Can be overridden in subclasses. * @param jobDetail the JobDetail prepared by this FactoryBean */
protected void postProcessJobDetail(JobDetail jobDetail) { }
Overridden to support the "targetBeanName" feature.
/** * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. */
@Override public Class<?> getTargetClass() { Class<?> targetClass = super.getTargetClass(); if (targetClass == null && this.targetBeanName != null) { Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); targetClass = this.beanFactory.getType(this.targetBeanName); } return targetClass; }
Overridden to support the "targetBeanName" feature.
/** * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. */
@Override public Object getTargetObject() { Object targetObject = super.getTargetObject(); if (targetObject == null && this.targetBeanName != null) { Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); targetObject = this.beanFactory.getBean(this.targetBeanName); } return targetObject; } @Override @Nullable public JobDetail getObject() { return this.jobDetail; } @Override public Class<? extends JobDetail> getObjectType() { return (this.jobDetail != null ? this.jobDetail.getClass() : JobDetail.class); } @Override public boolean isSingleton() { return true; }
Quartz Job implementation that invokes a specified method. Automatically applied by MethodInvokingJobDetailFactoryBean.
/** * Quartz Job implementation that invokes a specified method. * Automatically applied by MethodInvokingJobDetailFactoryBean. */
public static class MethodInvokingJob extends QuartzJobBean { protected static final Log logger = LogFactory.getLog(MethodInvokingJob.class); @Nullable private MethodInvoker methodInvoker;
Set the MethodInvoker to use.
/** * Set the MethodInvoker to use. */
public void setMethodInvoker(MethodInvoker methodInvoker) { this.methodInvoker = methodInvoker; }
Invoke the method via the MethodInvoker.
/** * Invoke the method via the MethodInvoker. */
@Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { Assert.state(this.methodInvoker != null, "No MethodInvoker set"); try { context.setResult(this.methodInvoker.invoke()); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof JobExecutionException) { // -> JobExecutionException, to be logged at info level by Quartz throw (JobExecutionException) ex.getTargetException(); } else { // -> "unhandled exception", to be logged at error level by Quartz throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException()); } } catch (Exception ex) { // -> "unhandled exception", to be logged at error level by Quartz throw new JobMethodInvocationFailedException(this.methodInvoker, ex); } } }
Extension of the MethodInvokingJob, implementing the StatefulJob interface. Quartz checks whether or not jobs are stateful and if so, won't let jobs interfere with each other.
/** * Extension of the MethodInvokingJob, implementing the StatefulJob interface. * Quartz checks whether or not jobs are stateful and if so, * won't let jobs interfere with each other. */
@PersistJobDataAfterExecution @DisallowConcurrentExecution public static class StatefulMethodInvokingJob extends MethodInvokingJob { // No implementation, just an addition of the tag interface StatefulJob // in order to allow stateful method invoking jobs. } }