springboot-Quartz integrated source code analysis and demo

Disclaimer: This article is a blogger original article, welcome to reprint. https://blog.csdn.net/guo_xl/article/details/85068719

springboot-Quartz integrated source tracking

Added in pom.xml

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

Use starter in principle
have spring.factories in

org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration

The start time will load QuartzAutoConfiguration, look below QuartzAutoConfiguration

@Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class,
		PlatformTransactionManager.class })
		//加入QuartzProperties.class
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration {

private final QuartzProperties properties;

//构造器注入QuartzProperties
public QuartzAutoConfiguration(QuartzProperties properties,
			ObjectProvider<SchedulerFactoryBeanCustomizer> customizers,
			ObjectProvider<JobDetail[]> jobDetails,
			ObjectProvider<Map<String, Calendar>> calendars,
			ObjectProvider<Trigger[]> triggers, ApplicationContext applicationContext) {
		this.properties = properties;
		this.customizers = customizers;
		this.jobDetails = jobDetails.getIfAvailable();
		this.calendars = calendars.getIfAvailable();
		this.triggers = triggers.getIfAvailable();
		this.applicationContext = applicationContext;
	}
	
//定义一个SchedulerFactoryBean,前提是没有SchedulerFactoryBean	
@Bean
	@ConditionalOnMissingBean
	public SchedulerFactoryBean quartzScheduler() {
	//new 了一个SchedulerFactoryBean
		SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
		SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
		jobFactory.setApplicationContext(this.applicationContext);
		schedulerFactoryBean.setJobFactory(jobFactory);
		
		//这里可以定义很多quartz.properteis里的属性
		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;
	}	
	
	
}

The following are SchedulerFactoryBean, you can see that
implements FactoryBean, that is, which is the creator of this definition Scheduler

public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
		BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
	//getObject() 是FactoryBean<Scheduler>的接口方法
	public Scheduler getObject() {
		return this.scheduler;
	}
}

FactoryBean BeanFactory difference and, in fact, two persons in addition to the name upside down, but not necessarily related. BeanFactory is a factory class, as the name suggests is the production of Bean factory. And FactoryBean BeanDefinition class is generated.

When such a code, there @Autowired injection Scheduler follows

 @Autowired
 Scheduler scheduler;

BeanFactory is based on name Schedulerto get Scheduler, Scheduler will eventually be obtained by SchedulerFactoryBean.getObject ().

Come FactoryBean.getObject () is obtained by analysis in this.scheduler.

public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
		BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
	private Scheduler scheduler;
	public Scheduler getObject() {
			return this.scheduler;
		}
}

But the Scheduler is configured to handle where?

The implements the SchedulerFactoryBean InitializingBean, in InitializingBeanthereafterPropertiesSet()

public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
		BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
		
		public void afterPropertiesSet() throws Exception {
        ...
		// Initialize the Scheduler instance...
		//这里初始化了Scheduler
		this.scheduler = prepareScheduler(prepareSchedulerFactory());
		try {
			registerListeners();
			registerJobsAndTriggers();
		}
		catch (Exception ex) {
			...
		}
	}
	
}

bean simple life cycle is understood as follows. Note InitializingBean # afterPropertiesSet () in which the position of

->construcor
->initialization(各种autowired)
->BeanPostProcessor#postProcessBeforeInitialization
->@postConsruct 或 InitializingBean#afterPropertiesSet() 或 @Bean(initMethod="xxx")
->BeanPostProcessor#postProcessAfterInitialization
->@PreDestroy

Look prepareSchedulerFactory () method, and finally return to the SchedulerFactoryassignment given SchedulerFactoryBeanthe scheduler

public class SchedulerFactoryBean{
    
	private Class<? extends SchedulerFactory> schedulerFactoryClass = 
	StdSchedulerFactory.class;
	
    private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
        	private SchedulerFactory schedulerFactory;
        //这里这个SchedulerFactory肯定为空,当然有办法可以让它不为空,通过定义SchedulerFactoryBeanCustomizer来实现   
		SchedulerFactory schedulerFactory = this.schedulerFactory;
		if (schedulerFactory == null) {
			// Create local SchedulerFactory instance (typically a StdSchedulerFactory)
			//这里也写了这里是实例化出StdSchedulerFactory
			schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
			if (schedulerFactory instanceof StdSchedulerFactory) {
			//调用initSchedulerFactory来填充StdSchedulerFactory)
			//看过Quartz的官方demo,就知道StdSchedulerFactory用来生产出sheduler
			initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
			}
			else if (this.configLocation != null || this.quartzProperties != null ||
					this.taskExecutor != null || this.dataSource != null) {
				throw new IllegalArgumentException(
						"StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
			}
			// Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties)
		}
		// Otherwise, assume that externally provided factory has been initialized with appropriate settings
		return schedulerFactory;
	}
}

initSchedulerFactory mainly to the configuration information in schedulerFactory

private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException {
		Properties mergedProps = new Properties();
        ...    
		CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
        ...
        //这里很重要,
        //可以直接通过 application.properties里的配置来配置quartz.properties里的配置spring.quartz.properties.xxx
		schedulerFactory.initialize(mergedProps);
	}

So just see here is the result of the integration of official documents springboot of quartz is too simple, only a few words

Quartz Scheduler configuration can be customized using spring.quartz properties and SchedulerFactoryBeanCustomizer beans, 
which allow programmatic SchedulerFactoryBean customization.
Advanced Quartz configuration properties can be customized using spring.quartz.properties.*.

Translation is

Quartz Scheduler can be achieved SchedulerFactoryBean customized configurations by using the properties spring.quartz and write SchedulerFactoryBeanCustomizer this class. Further configuration can be spring.quartz.properties. *

Such examples are not even write, a bit too simple. I did not see the source did not know what to say

prepareScheduler (prepareSchedulerFactory ()) after the call will go to a series of StdSchedulerFactory of
private Scheduler instantiate (). This is a very long way
, but the logic is fairly simple, various initialization, the following describes only the configuration jobStore

private Scheduler instantiate() throws SchedulerException {
  //cfg 就是上个代码片段里的mergedProps
        if (cfg == null) {
            initialize();
        }
        //jobstore,如果不配,默认是RAMJobStore
        //在application.properties里可以配置为
        //spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
           String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS,
                RAMJobStore.class.getName());
    
    try {
            js = (JobStore) loadHelper.loadClass(jsClass).newInstance();
        } catch (Exception e) {
            initException = new SchedulerException("JobStore class '" + jsClass
                    + "' could not be instantiated.", e);
            throw initException;
        }

        SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);

    //这里可以获取到spring.quartz.properties.org.quartz.jobStore.xxx
    //xxx是jobStore这个类的里属性,比如isClustered,clusterCheckinInterval等
    //在applicaiton.properties配置成spring.quartz.properties.org.quartz.jobStore.isClustered = true
        tProps = cfg.getPropertyGroup(PROP_JOB_STORE_PREFIX, true, new String[] {PROP_JOB_STORE_LOCK_HANDLER_PREFIX});
        try {
            setBeanProps(js, tProps);
        } catch (Exception e) {
            initException = new SchedulerException("JobStore class '" + jsClass
                    + "' props could not be configured.", e);
            throw initException;
        }

        
        
}

So we have to configure the cluster can be configured as follows in application.properties:

spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
spring.quartz.properties.org.quartz.jobStore.isClustered = true
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval = 10000
spring.quartz.properties.org.quartz.scheduler.instanceId = AUTO

Demo 1

The main example is a simple application of Quartz. Quartz api written in a very humane, just look at the method name will know what that means, almost no investigation documents.
Demand in the following figure needs to be able to customize different execution plan
Options perform periodically

code
 public static void main(String[] args) throws SchedulerException {
        // Grab the Scheduler instance from the Factory
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // and start it off
        scheduler.start();

        // define the job and tie it to our MyJob class
        JobDetail job = newJob(MyJob.class)
                .withIdentity("job1", "group1")
                .build();

        //DAILY on12:00 START 【now】- END  【2019-3-1】
        GregorianCalendar calendar=new GregorianCalendar();
        calendar.set(2019,3,1);
        Date endTime=calendar.getTime();
        Trigger triggerDaily = newTrigger()
                .withIdentity("triggerDaily", "group1")
                .startNow()
                .endAt(endTime)
                .withSchedule(dailyAtHourAndMinute(12,0)).build();
         //DAILY exclude Sunday on 12:00 START 【now】- END  【2019-3-1】        
         Trigger triggerDaily2 = newTrigger()
                .withIdentity("triggerDaily2", "group1")
                .startNow()
                .endAt(endTime)
                .withSchedule(atHourAndMinuteOnGivenDaysOfWeek(12,0,2,3,4,5,6,7)).build();
        //WEELKY monday 12:00 START 【now】- END  【2019-3-1】
        Trigger triggerWeekly = newTrigger()
                .withIdentity("triggerWeekly", "group1")
                .startNow()
                .endAt(endTime)
                .withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.MONDAY,12,0)).build();
        //MONTHLY 31th 12:00 START 【now】- END  【2019-3-1】
        Trigger triggerMonthly = newTrigger()
                .withIdentity("triggerMonthly", "group1")
                .startNow()
                .endAt(endTime)
                .withSchedule(monthlyOnDayAndHourAndMinute(31,12,0)).build();

        // Tell quartz to schedule the job using our trigger
        //one job can only have one trigger
        //scheduler.scheduleJob(job, triggerDaily);
        //scheduler.scheduleJob(job, triggerDaily1);
        //scheduler.scheduleJob(job, triggerWeekly);
        scheduler.scheduleJob(job, triggerMonthly);
    }

Demo 2

demand:
  • Dynamically add, delete, schedule tasks.
  • Clustered, only one machine in the cluster to perform.
  • When one machine is down, another machine to be able to continue.

Springmvc do this by example.

Sql execution

Find org.quartz-scheduler in maven lib in: quartz: 2.3.0 in the tables_mysql_innodb.sql

Code

application.properies

server.port=9999
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ssmdb?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=devuser
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver

spring.quartz.job-store-type=jdbc
#开启集群
spring.quartz.properties.org.quartz.jobStore.isClustered = true
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval = 10000
spring.quartz.properties.org.quartz.scheduler.instanceId = AUTO
@SpringBootApplication
public class SchedualBootstrap {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    public static void main(String[] args) {
        SpringApplicationBuilder builder =new SpringApplicationBuilder(SchedualBootstrap.class);
        builder.run(args);
    }

   @Autowired
   Scheduler scheduler;
   
   //加入监听
   @PostConstruct
   public void init(){
            scheduler.getListenerManager().addSchedulerListener(new SchedulerListenerSupport(){
                @Override
                public void jobDeleted(JobKey jobKey) {
                    logger.info(jobKey.getName()+" is deleted");
                }
                public void jobAdded(JobDetail jobDetail) {
                    logger.info(jobDetail.getKey().getName()+" is add");
                }
                public void jobScheduled(Trigger trigger) {
                    logger.info(trigger.getJobKey()+" is trigger");
                }
            });
    }
public class JobBean {
    private String jobName;
    private int intervalSecond;
    //...setter and getter
}

Increase in rest mode, reducing schedual task

@RestController
public class schedualController {
    @Autowired
    Scheduler scheduler;

    @PostMapping("/schedual/add")
    public void addSchedual(@RequestBody JobBean jobBean){
        try {
            JobDetail jobDetail = newJob(MyJob.class)
                    .withIdentity(jobBean.getJobName(), "group1")
                    .usingJobData("jobName", jobBean.getJobName())
                    .storeDurably()
                    .build();
            scheduler.addJob(jobDetail,true);
            // Trigger the job to run now, and then repeat every 2 seconds
            Trigger trigger = newTrigger()
                    .withIdentity("trigger-"+jobBean.getJobName(), "group1")
                    .forJob(jobDetail)
                    .startNow()
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(jobBean.getIntervalSecond())
                            .repeatForever())
                    .build();

            scheduler.scheduleJob(trigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    @PostMapping("/schedual/{key}/delete")
    public void deleteSchedual(@PathVariable String key){
        JobKey jobKey = new JobKey(key,"group1");
        try {
            if(scheduler.checkExists(jobKey)){
                scheduler.deleteJob(jobKey);
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

    }
}

job

@DisallowConcurrentExecution
public class MyJob implements org.quartz.Job{

    Logger logger = LoggerFactory.getLogger(this.getClass());

    private String JobName;

    public String getJobName() {
        return JobName;
    }

    public void setJobName(String jobName) {
        JobName = jobName;
    }

    public MyJob() {
    }

    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
        logger.info(key.getName());
        logger.info(getJobName()+" : [Hello World!  MyJob is executing.]");
    }
}
How to run
  1. Modify application.properties server.port = 9999 startup
  2. Modify application.properties server.port = 9998 startup
  3. 打开postman,http://127.0.0.1:9998/schedual/add
    body
{
	"jobName":"job1",
	"intervalSecond":1
}

Increase a job1, running every 1 second

  1. 打开postman,http://127.0.0.1:9999/schedual/add
    body
{
	"jobName":"job2",
	"intervalSecond":3
}

Increase a job2, not three seconds to run once

See console output, run job1,9999 run job2 9998

5. Close the 9999, you will find 9998 running job1 and job2

github code

Guess you like

Origin blog.csdn.net/guo_xl/article/details/85068719