Spring / Spring boot properly integrated Quartz and resolve failures @Autowired

Check the Friday before the Spring bootintegration Quartzproject and found configuration errors, so by reading the source code way to explore Springcorrect integrated Quartzway.

Problems found

Check the last year of the project code, found on QuartzJobBeanthe existence realized unreasonable.

(1) project dependencies:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
    </dependencies>

(2) the issue of code:

@Component
public class UnprocessedTaskJob extends QuartzJobBean {

    private TaskMapper taskMapper;

    @Autowired
    public UnprocessedTaskJob(TaskMapper taskMapper){
        this.taskMapper = taskMapper;
    }
}

private JobDetail generateUnprocessedJobDetail(Task task) {
    JobDataMap jobDataMap = new JobDataMap();
    jobDataMap.put(UnprocessedTaskJob.TASK_ID, task.getId());
    return JobBuilder.newJob(UnprocessedTaskJob.class)
            .withIdentity(UnprocessedTaskJob.UNPROCESSED_TASK_KEY_PREFIX + task.getId(), UnprocessedTaskJob.UNPROCESSED_TASK_JOB_GROUP)
            .usingJobData(jobDataMap)
            .storeDurably()
            .build();
    }

(3) refining questions:

Causes of errors than code, UnprocessedTaskJobadd @Componentannotations, which represents a Spring IOCcontainer 单例class.
However, Quartzcreated Jobby respective Quartz Job Beanof classreflection to create appropriate Job. That is, each time creating a new Jobtime, will yield the corresponding Jobexamples. Thereby , which UnprocessedTaskJobis 单例in conflict.
View Code to submit records, because at that time that does not add @Componentannotations, you can not through @Autowiredthe introduction of a Spring IOChosted taskMapper, that can not be achieved instance 依赖注入.

However, surprising is that when I go in the development environment in addition to UnprocessedTaskJobthe @Componentfollowing notes, after running the program finds TaskMapperinstance can still be injected into Jobthe program running ...

Spring-managed Quartz

Code Analysis

Online search Springhosting Quartzarticles, most of Spring MVCthe project, focusing on how to solve the Jobimplementation class through the @Autowiredrealization Springof 依赖注入.
Most rely on the Internet to achieve SpringBeanJobFactoryto achieve Springthe Quartzintegration.

/**
 * Subclass of {@link AdaptableJobFactory} that also supports Spring-style
 * dependency injection on bean properties. This is essentially the direct
 * equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
 * {@link org.quartz.spi.JobFactory}.
 *
 * <p>Applies scheduler context, job data map and trigger data map entries
 * as bean property values. If no matching bean property is found, the entry
 * is by default simply ignored. This is analogous to QuartzJobBean's behavior.
 *
 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
 *
 * @author Juergen Hoeller
 * @since 2.0
 * @see SchedulerFactoryBean#setJobFactory
 * @see QuartzJobBean
 */
public class SpringBeanJobFactory extends AdaptableJobFactory
        implements ApplicationContextAware, SchedulerContextAware {
}

/**
 * {@link JobFactory} implementation that supports {@link java.lang.Runnable}
 * objects as well as standard Quartz {@link org.quartz.Job} instances.
 *
 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
 *
 * @author Juergen Hoeller
 * @since 2.0
 * @see DelegatingJob
 * @see #adaptJob(Object)
 */
public class AdaptableJobFactory implements JobFactory {
}

It can be found by the above codes, and comments:

(1) AdaptableJobFactoryimplements JobFactorythe interface, you can take to create a standard Quartzinstance (only Quartz2.1.4 and above);

(2) SpringBeanJobFactoryinherited AdaptableJobFactoryto achieve the Quartzproperties dependent package instance injection.

(3) SpringBeanJobFactoryimplements ApplicationContextAwareand SchedulerContextAwareinterfaces ( Quartztask scheduling context), it can be created in Job Beanthe injection time ApplicationContexand SchedulerContext.

Tips:
The above code based Spring5.1.8 version
in Spring 4.1.0version, SpringBeanJobFactoryimplemented as shown in the following code:

public class SpringBeanJobFactory extends AdaptableJobFactory
    implements SchedulerContextAware{

    // 具体代码省略
}

Thus, in the early Springproject, the need to package SpringBeanJobFactoryand implement ApplicationContextAwarethe interface (no surprise surprise?).

Spring old version Solutions

Based on older versions Springgive a solution to Springintegrated Quartzsolutions.
Solutions from the thirty-ninth chapter: Based on the timing of the completion of the task SpringBoot & Quartz distributed single-node persistence provided (series quality Great God is great).

@Configuration
public class QuartzConfiguration
{
    /**
     * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory
     * 实现任务实例化方式
     */
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {

        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        /**
         * 将job实例交给spring ioc托管
         * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
         * @param bundle
         * @return
         * @throws Exception
         */
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            /**
             * 将job实例交付给spring ioc
             */
            beanFactory.autowireBean(job);
            return job;
        }
    }

    /**
     * 配置任务工厂实例
     * @param applicationContext spring上下文实例
     * @return
     */
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext)
    {
        /**
         * 采用自定义任务工厂 整合spring实例来完成构建任务
         * see {@link AutowiringSpringBeanJobFactory}
         */
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    /**
     * 配置任务调度器
     * 使用项目数据源作为quartz数据源
     * @param jobFactory 自定义配置任务工厂(其实就是AutowiringSpringBeanJobFactory)
     * @param dataSource 数据源实例
     * @return
     * @throws Exception
     */
    @Bean(destroyMethod = "destroy",autowire = Autowire.NO)
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception
    {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //将spring管理job自定义工厂交由调度器维护
        schedulerFactoryBean.setJobFactory(jobFactory);
        //设置覆盖已存在的任务
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        //项目启动完成后,等待2秒后开始执行调度器初始化
        schedulerFactoryBean.setStartupDelay(2);
        //设置调度器自动运行
        schedulerFactoryBean.setAutoStartup(true);
        //设置数据源,使用与项目统一数据源
        schedulerFactoryBean.setDataSource(dataSource);
        //设置上下文spring bean name
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
        //设置配置文件位置
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
        return schedulerFactoryBean;
    }
}

By the above code, it is achieved by SpringBeanJobFactorythe createJobInstancecreated Jobinstance, and the resulting Jobinstance by the delivery AutowireCapableBeanFactoryto host.
schedulerFactoryBeanIs set as JobFactory(in fact AutowiringSpringBeanJobFactory, the inside of the package applicationContext), and DataSource(a data source, if not set, the Quartzdefault RamJobStore).

RamJobStoreThe advantage is fast, the disadvantage is that scheduling tasks can not persistently saved.

Therefore, we can use within the regular task Spring IOCof @Autowirednotes like 依赖注入.

The new version of the Spring Solutions

(1) explain

If you are using Spring boot, and the version is greater than the good 2.0, it is recommended spring-boot-starter-quartz.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>

Auto-configuration support is now include for the Quartz Scheduler. We’ve also added a new spring-boot-starter-quartz starter POM.
You can use in-memory JobStores, or a full JDBC-based store. All JobDetail, Calendar and Trigger beans from your Spring application context will be automatically registered with the Scheduler.
For more details read the new “Quartz Scheduler” section of the reference documentation.

These are the spring-boot-starter-quartzintroduction, based on the presentation shows that if you do not turn off Quartzthe auto-configuration, SpringBootwill help you complete Schedulerthe automated configuration, such as JobDetail/ Calendar/ Trigger, etc. Beanwill be automatically registered to Shcedulerin. You can QuartzJobBeanuse the free @Autowiredand other 依赖注入annotations.

In fact, does not introduce spring-boot-starter-quartz, but only imported org.quartz-scheduler, Quartzautomated configuration will still take effect (this is the first analysis of the problem, remove the @Beancomments, the program is still running the reasons, tragedy lucky).

(2) Analysis Code

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler.
 *
 * @author Vedran Pavic
 * @author Stephane Nicoll
 * @since 2.0.0
 */
@Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration{

    // 此处省略部分代码

    @Bean
    @ConditionalOnMissingBean
    public SchedulerFactoryBean quartzScheduler() {
        // 因为新版本SchedulerFactoryBean已经实现ApplicationContextAware接口
        // 因此相对于老版本Spring解决方案中的AutowiringSpringBeanJobFactory进行封装
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
        // SpringBeanJobFactory中注入applicationContext,为依赖注入创造条件
        jobFactory.setApplicationContext(this.applicationContext);
        // schedulerFactoryBean中注入setJobFactory(注意此处没有配置DataSource,DataSource详见`JdbcStoreTypeConfiguration`)
        // 以上这几个步骤,与老版本的Spring解决方案类似
        schedulerFactoryBean.setJobFactory(jobFactory);

        // 后续都是Quartz的配置属性设置,不再叙述
        if (this.properties.getSchedulerName() != null) {
            schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
        }
        schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());schedulerFactoryBean.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(this.properties.isWaitForJobsToCompleteOnShutdown());
        schedulerFactoryBean.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
        if (!this.properties.getProperties().isEmpty()) {
            schedulerFactoryBean.setQuartzProperties(asProperties(this.properties.getProperties()));
        }
        if (this.jobDetails != null && this.jobDetails.length > 0) {
            schedulerFactoryBean.setJobDetails(this.jobDetails);
        }
        if (this.calendars != null && !this.calendars.isEmpty()) {
            schedulerFactoryBean.setCalendars(this.calendars);
        }
        if (this.triggers != null && this.triggers.length > 0) {
            schedulerFactoryBean.setTriggers(this.triggers);
        }
        customize(schedulerFactoryBean);
        return schedulerFactoryBean;
    }

    @Configuration
    @ConditionalOnSingleCandidate(DataSource.class)
    protected static class JdbcStoreTypeConfiguration {

        // 为Quartz的持久化配置DataSource,具体代码可以翻阅Spring源码得到
    }
}

Next, the SpringBeanJobFactoryanalysis, which generated Jobinstance, and performing 依赖注入key operation type.

/**
 * Subclass of {@link AdaptableJobFactory} that also supports Spring-style
 * dependency injection on bean properties. This is essentially the direct
 * equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
 * {@link org.quartz.spi.JobFactory}.
 *
 * <p>Applies scheduler context, job data map and trigger data map entries
 * as bean property values. If no matching bean property is found, the entry
 * is by default simply ignored. This is analogous to QuartzJobBean's behavior.
 *
 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
 *
 * @author Juergen Hoeller
 * @since 2.0
 * @see SchedulerFactoryBean#setJobFactory
 * @see QuartzJobBean
 */
public class SpringBeanJobFactory extends AdaptableJobFactory
        implements ApplicationContextAware, SchedulerContextAware {

    @Nullable
    private String[] ignoredUnknownProperties;

    @Nullable
    private ApplicationContext applicationContext;

    @Nullable
    private SchedulerContext schedulerContext;

    /**
     * Specify the unknown properties (not found in the bean) that should be ignored.
     * <p>Default is {@code null}, indicating that all unknown properties
     * should be ignored. Specify an empty array to throw an exception in case
     * of any unknown properties, or a list of property names that should be
     * ignored if there is no corresponding property found on the particular
     * job class (all other unknown properties will still trigger an exception).
     */
    public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) {
        this.ignoredUnknownProperties = ignoredUnknownProperties;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setSchedulerContext(SchedulerContext schedulerContext) {
        this.schedulerContext = schedulerContext;
    }

    /**
     * Create the job instance, populating it with property values taken
     * from the scheduler context, job data map and trigger data map.
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 创建Job实例
        // (1) 包含applicationContext,则通过AutowireCapableBeanFactory()创建相应Job实例,实现依赖注入
        // (2) 如果applicationContext为空,则使用AdaptableJobFactory创建相应的Bean(无法实现依赖注入)
        Object job = (this.applicationContext != null ?
                        this.applicationContext.getAutowireCapableBeanFactory().createBean(
                            bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
                        super.createJobInstance(bundle));

        if (isEligibleForPropertyPopulation(job)) {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
            MutablePropertyValues pvs = new MutablePropertyValues();
            if (this.schedulerContext != null) {
                pvs.addPropertyValues(this.schedulerContext);
            }
            pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
            pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
            if (this.ignoredUnknownProperties != null) {
                for (String propName : this.ignoredUnknownProperties) {
                    if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
                        pvs.removePropertyValue(propName);
                    }
                }
                bw.setPropertyValues(pvs);
            }
            else {
                bw.setPropertyValues(pvs, true);
            }
        }

        return job;
    }

    // 省略部分代码
}

/**
 * {@link JobFactory} implementation that supports {@link java.lang.Runnable}
 * objects as well as standard Quartz {@link org.quartz.Job} instances.
 *
 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
 *
 * @author Juergen Hoeller
 * @since 2.0
 * @see DelegatingJob
 * @see #adaptJob(Object)
 */
public class AdaptableJobFactory implements JobFactory {
    /**
     * Create an instance of the specified job class.
     * <p>Can be overridden to post-process the job instance.
     * @param bundle the TriggerFiredBundle from which the JobDetail
     * and other info relating to the trigger firing can be obtained
     * @return the job instance
     * @throws Exception if job instantiation failed
     */
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 获取`QuartzJobBean`的实现`class`,通过反射工具创建相应的类实例(自然无法注入Spring托管的Bean实例)
        Class<?> jobClass = bundle.getJobDetail().getJobClass();
        return ReflectionUtils.accessibleConstructor(jobClass).newInstance();
    }
}

Here you need to explain AutowireCapableBeanFactoryaction.
Projects, and some did not achieve Springintegration depth, it is not examples of Springcontainer management.
However, the need for which has not been Springmanaged in Beanthe need to introduce Springthe container Bean.
At this time, We need to achieve AutowireCapableBeanFactory, so that Springdependency injection and other functions.

We hope the above explanation and code analysis, let you know how the old version and the new version Springintegrated right in Quartz.
In addition, Spring bootautomated configuration can resolve the vast majority of configuration issues, but in plenty of time, it is recommended by reading the source code, etc. understanding 配置细节, thereby leading to more confidence.

PS:
If you think my article helpful to you, you can scan the code to receive support under a red envelope or scan code (random number, a penny is love), thank you!

Alipay red envelope Alipay Micro letter

Guess you like

Origin www.cnblogs.com/jason1990/p/11110196.html