欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring整合Quartz动态定时任务步骤分析

程序员文章站 2022-07-26 20:53:57
1. quartz-demo1.导入依赖![在这里插入图片描述](https://img-blog.csdnimg.cn/20200716103507816.png)如果是maven项目直接导入下面即可。 org.quartz-scheduler quartz 2.3.2&l...

1. quartz-demo

1.导入依赖

Spring整合Quartz动态定时任务步骤分析

如果是maven项目直接导入下面即可。

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

2.log4.properties

### 设置###
log4j.rootLogger=DEBUG,stdout
log4j.logger.io.netty=DEBUG
log4j.logger.com.mchange=DEBUG
### 输出信息到控制抬 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p %d{HH:mm:ss} [%c:%L] %m%n

3.quartz.properties

默认会去classpath去找quartz.properties,如果没有,那么就使用默认quartz的jar包中的quartz.properties文件

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3    #开启三个线程
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

4.监听器

1. JobListener

public class MyJobListener extends JobListenerSupport {
    @Override
    public String getName() {
        return "MyJobListener007";
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println("-------jobWasExecuted-------");
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("-------jobToBeExecuted-------");

    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("-------jobExecutionVetoed-------");
    }
}

2. SchedulerListener

public class MySchedulerListener extends SchedulerListenerSupport {
    @Override
    public void jobAdded(JobDetail jobDetail) {
        System.out.println("---------jobAdded------------");
    }

    @Override
    public void jobScheduled(Trigger trigger) {
        System.out.println("---------jobScheduled------------");

    }

    @Override
    public void schedulerShutdown() {
        System.out.println("---------schedulerShutdown------------");

    }

    @Override
    public void schedulerShuttingdown() {
        System.out.println("---------schedulerShuttingdown------------");

    }

    @Override
    public void schedulerStarted() {
        System.out.println("---------schedulerStarted------------");

    }

    @Override
    public void schedulerStarting() {
        System.out.println("---------schedulerStarting------------");

    }
}

5.HelloJob继承job接口

/* 将job封装给jobDetail,由调度器scheudler根据触发器trggier条件触发相应的jobDetail,每次触发都会让jobDetail重新创建job对象,并且jobDetail会将数据传给job
              有两种方式: 1.jobDetail会根据自己usingJobData中的参数主动调用job对应的set方法,设置给job使用。
                        2.job可以从重写方法传过来的参数jobExecutionContext中获取jobDetail,
                                         然后从jobDetail中获取到jobDataMap。
                        
 HelloJob必须要有一个无参的构造方法
*/

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class HelloJob implements Job { 

    private Integer joe;

    private Integer mike;

    public HelloJob() {
        System.out.println("无参构造执行....");
    }

    public void setJoe(Integer joe) {
        this.joe = joe;
        System.out.println("执行setJoe方法....");
    }

    public void setMike(Integer mike) {
        this.mike = mike;
        System.out.println("执行setMike方法....");
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
                                                   // 每次打印的hash值都不一样
        System.out.println("Hello job...start: " + jobExecutionContext.hashCode()); 
                                                   // 从jobExecutionContext中获取到JobDetail
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        // 从jobDataMap中获取数据
        /*Integer joe = (Integer) jobDataMap.get("joe");
        Integer mike = (Integer) jobDataMap.get("mike");*/

        System.out.println("当前线程: " + Thread.currentThread());
        System.out.println("joe = "+(joe++) + " , mike = " + (mike++));
        // 将数据从jobDetail对象传给job,job执行完后,将数据放入jobDataMap
        // 使用@PersistJobDataAfterExecution,才能将以下修改过的数据同步到jobDetail中
        jobDataMap.put("joe",joe);
        jobDataMap.put("mike",mike);

        try {
            // 验证并发: 触发器每隔1秒触发一次job,而一个job执行的线程需要睡上5秒。
            // jobDetail使用储备的线程,尽管线程还有2个富余,但使用此注解后,将会禁用jobDetail的并发
            // 即->使用@DisallowConcurrentExecution: 同一个JobDetail对象,禁止创建多个job去执行)
            
            // 如果不使用@DisallowConcurrentExecution此注解,那么每一秒都会使用线程池中的线程执行job,
            // 总共3条线程满了,那么只能等线程释放了(第一个线程执行睡5秒后),才会把job给到空闲的线程。
            // 所以线程数量也是一个重要的参数,否则就会影响调度的时间
            
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println();
    }

}

4.QuartzTest.java[重点]

public class QuartzTest {

    public static void main(String[] args) {

        try {
            // 1. 创建任务调度器Scheduler,给任务调度器添加监听器SchedulerListener
            // Grab the Scheduler instance from the Factory
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 
            scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());

            // 2. 开启任务调度器
            // and start it off
            scheduler.start();

            // 3. 根据Job接口实现类,创建JobDetail对象
            // define the job and tie it to our HelloJob class
            JobDetail job1 = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job1", "group1")  // jobDetail的唯一标识  -  分组
                    .usingJobData("joe", 1)          // 将会封装到jobDataMap中
                    .usingJobData("mike", 2)         //	jobDetail会主动job对象对应的setMike方法
                    .build();                        //          将数据传给job使用

			// 4. 创建触发器Trigger, 指定触发规则
            // Trigger the job to run now, and then repeat every 40 seconds
            Trigger trigger1 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1")  // trigger的唯一标识 - 分组
                    .startNow()
                    .withSchedule(                       // 触发规则: 各种Schedule,常用是cronSchedule
                            SimpleScheduleBuilder
                                .simpleSchedule()
                                .withIntervalInSeconds(2)
                                .repeatForever()
            		).build();

            Trigger trigger2 = newTrigger()
                    .withIdentity("trigger2", "group2")
                    .startAt(new Date(new Date().getTime() + 2000))  // 开始时间         注意6表示7月
                	.endAt(new GregorianCalendar(2020, 6, 14, 23, 24, 00).getTime()) // 结束时间
                    .withSchedule(
                            CronScheduleBuilder          // 触发规则: cronSchedule
                            .cronSchedule("0/1 * * * * ?")
                            .withMisfireHandlingInstructionIgnoreMisfires()
            		 ).build();

		
           // 给任务调度器添加job监听器
           // MyJobListener myJobListener = new MyJobListener(); //
           // scheduler.getListenerManager().addJobListener(myJobListener
                            /*, KeyMatcher.keyEquals(new JobKey("job1","group1")));*/

            // 5. 使用任务调度器Scheduler 配置任务jobDetail 和 触发器Trigger
            // Tell quartz to schedule the job using our trigger
            scheduler.scheduleJob(job1, trigger2);

            // 60s过后主线程关闭 scheduler
            Thread.sleep(60000);
            scheduler.shutdown();

        } catch (SchedulerException se) {
            se.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.spring整合quartz

1.导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zzhua</groupId>
    <artifactId>quartz-demo03</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>war</packaging>

    <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jsp.version>2.1</jsp.version>
        <servlet.version>3.1.0</servlet.version>
        <spring.version>5.0.2.RELEASE</spring.version>

    </properties>

    <dependencies>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>${jsp.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

    </dependencies>

    <build>

        <plugins>
            <!-- 配置tomcat的运行插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>

            <!-- 配置jdk的编译版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <!-- 指定source和target的版本 -->
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

2.log4j.properties

log4j.rootLogger=info, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n

#log4j.appender.R=org.apache.log4j.RollingFileAppender
#log4j.appender.R.File=firestorm.log

log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%m%n

3.spring-quartz.xml

2中配置job的方法

第一种:实现 job接口,

​ 将job接口的实现类交给 jobDetail,

​ 然后让调度器Scheduler将任务 jobDetail和触发器Trigger执行调度

第二种:不用实现 job接口,

​ 将任务对象oceanStatusJob和要执行的方法名交给MethodInvokingJobDetailFactoryBean对象,

​ 将MethodInvokingJobDetailFactoryBean对象给到CronTriggerFactoryBean,并且加上cron表达式

​ 将CronTriggerFactoryBean交给调度器SchedulerFactoryBean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.zzhua.job.MyJob"></property>
        <property name="applicationContextJobDataKey" value="springFactory"></property>
        <property name="group" value="jobGroup"></property>
        <property name="name" value="jobName"></property>
    </bean>

    <bean id="cronTrigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="group" value="triggerGroup1"></property>
        <property name="name" value="triggerName1"></property>
        <property name="cronExpression" value="0/1 * * * * ?"></property>
        <property name="jobDetail" ref="jobDetail"></property>
    </bean>

    <bean id="cronTrigger2" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="group" value="triggerGroup2"></property>
        <property name="name" value="triggerName2"></property>
        <property name="cronExpression" value="0/2 * * * * ?"></property>
        <property name="jobDetail" ref="jobDetail"></property>
    </bean>

    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <array>
                <!--此处可选择配置固定的系统任务-->
                <!--<ref bean="cronTrigger1"/>-->
                <!--<ref bean="cronTrigger2"/>-->
            </array>
        </property>
    </bean>

</beans>

4.MyJob系统任务

public class MyJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("---------系统任务开始----------:
                           " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        ApplicationContext applicationContext 
            = (ApplicationContext) context.getJobDetail().getJobDataMap().get("springFactory");
        System.out.println("bean工厂->" + applicationContext);
        JobDetail jobDetail = context.getJobDetail();
        System.out.println("jobDetail" + jobDetail);
        Trigger trigger = context.getTrigger();
        System.out.println("trigger" + trigger);
        System.out.println();
        System.out.println("---------系统任务结束---------- ");
        System.out.println();
    }
}

5.HelloJob动态任务

@Slf4j
public class HelloJob extends QuartzJobBean {


    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

        log.info("--------------------开始执行任务---------------------{}",
                 new SimpleDateFormat("HH:mm:ss").format(new Date()));
        log.info("jobDetail: {} , {}", 
                 context.getJobDetail().getKey().getName(), 
                 context.getJobDetail().getKey().getGroup());
        log.info("trigger:{} , {}", 
                 context.getTrigger().getKey().getName(), 
                 context.getTrigger().getKey().getGroup());
        System.out.println();

    }
}

6.动态任务的增删改操作

@RestController
@RequestMapping("job")
public class JobController {

    @Autowired
    Scheduler scheduler;

    // 添加job
    @RequestMapping(value = "addJob",produces = "application/json;charset=UTF-8")
    public String addJob(SysJob sysJob) throws ClassNotFoundException, SchedulerException {

        JobDetail jobDetail = JobBuilder
                .newJob((Class<? extends Job>) Class.forName(sysJob.getJobclassname()))
                .withIdentity(sysJob.getJobname(), sysJob.getJobgroup())
                .build();

        Trigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity(sysJob.getTriggername(), sysJob.getTriggergroup())
                .withSchedule(CronScheduleBuilder.cronSchedule(sysJob.getCronExpression()))
                .build();

        scheduler.scheduleJob(jobDetail, trigger);

        return "添加job成功";

    }

    // 停止job
    @RequestMapping(value = "pauseJob",produces = "application/json;charset=UTF-8")
    public String pause(SysJob sysJob) throws SchedulerException {
		// 根据jobkey停止, 也可以根据triggerkey停止
        scheduler.pauseJob(JobKey.jobKey(sysJob.getJobname(), sysJob.getJobgroup()));
        return "停止job成功";
    }

    // 重启job
    @RequestMapping(value = "resumeJob",produces = "application/json;charset=UTF-8")
    public String resumeJob(SysJob sysJob) throws SchedulerException {
        // 根据jobkey重启,也可以根据triggerkey重启
        scheduler.resumeJob(JobKey.jobKey(sysJob.getJobname(), sysJob.getJobgroup()));
        return "重启job成功";
    }

    // 修改job
    @RequestMapping(value = "updateJob",produces = "application/json;charset=UTF-8")
    public String updateJob(SysJob sysJob) throws SchedulerException {

        scheduler.rescheduleJob(
                TriggerKey.triggerKey(sysJob.getTriggername(), sysJob.getTriggergroup()),
                TriggerBuilder
                        .newTrigger()
                        .withIdentity(sysJob.getTriggername(), sysJob.getTriggergroup())
                 		.withSchedule(
                        		CronScheduleBuilder.cronSchedule(sysJob.getCronExpression())
                          )
                        .startNow() // 修改之后,立刻启动
                        .build()
        );

        return "修改job成功";


    }

    // 删除job
    @RequestMapping(value = "deleteJob",produces = "application/json;charset=UTF-8")
    public String deleteJob(SysJob sysJob) throws SchedulerException {

        scheduler.pauseTrigger(
            TriggerKey.triggerKey(sysJob.getTriggername(), sysJob.getTriggergroup())
        );
        
        scheduler.unscheduleJob(
            TriggerKey.triggerKey(sysJob.getTriggername(), sysJob.getTriggergroup())
        );
        
        scheduler.deleteJob(JobKey.jobKey(sysJob.getJobname(), sysJob.getJobgroup()));
        
        return "删除job成功";
    }

    // 检查scheduler中是否存在jobkey或triggerkey
    @RequestMapping(value = "checkJob",produces = "application/json;charset=UTF-8")
    public String checkJob(SysJob sysJob) throws SchedulerException {

        boolean b1 = scheduler.checkExists(
            JobKey.jobKey(sysJob.getJobname(), sysJob.getJobgroup())
        ); 
        System.out.println("jobKey: " + b1);

        boolean b2 = scheduler.checkExists(
            TriggerKey.triggerKey(sysJob.getTriggername(), sysJob.getTriggergroup())
        );
        
        System.out.println("triggerKey: " + b2);

        HashMap map = new HashMap();
        map.put("jobKey: ", b1);
        map.put("triggerKey: ", b2);
        return JSON.toJSONString(map);
    }

    // 检查job的状态
    @RequestMapping(value = "checkState",produces = "application/json;charset=UTF-8")
    public String checkState(SysJob sysJob) throws SchedulerException {

        TriggerKey triggerKey = TriggerKey.triggerKey(
                                                    sysJob.getTriggername(),
                                                    sysJob.getTriggergroup()
        										);
        
        Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
        
        System.out.println(triggerState.name());
        return JSON.toJSONString(triggerState);

    }

}

7.总结

Scheduler通过jobKey 和 TriggerKey唯一标识来识别JobDetail对象和Trigger对象,

一个Trigger对象只能绑定一个JobDetail对象

一个JobDetail对象能够被多个Trigger对象绑定

一个jobDetail对象封装了一个job接口的实现类

一个trigger封装了一个schedule

Job接口实现类上的@DisAllowConcurrentExecution不允许同一个jobDetail对象,在一个job没完成时,再次创建job对象。

Job接口实现类上的@PersistJobDataAfterExecution使得job实现类中对jobdataMap修改的数据同步到jobDetail对象当中.

疑问一: job实现类如何拿到spring容器


具体分析如下: 
public class JobDetailFactoryBean implements 
    FactoryBean<JobDetail>, 
    BeanNameAware,
    ApplicationContextAware, 
    InitializingBean 
{...}

可以看出JobDetailFactoryBean实现了一系列的接口,
FactoryBean接口: 该接口是指向spring容器中注入实现该接口的bean时,会对该bean作一些特殊处理,
				特殊处理是指:当根据该bean的名字去拿bean对象的时候,返回的并不是本身的bean,
				而是拿这个本身的bean调用getObject返回来的bean(还重写的一个方法,告诉spring是否为单例)
				
InitializingBean接口: 该接口在本身的bean初始化完成,完成属性注入后,重写该接口的afterPropertiesSet方法被调用
    

ApplicationContextAware接口: 该接口在bean对象初始化完成,完成属性注入后,内置的aware接口方法回调[BeanNameAware,BeanClassLoaderAware,BeanFactoryAware]后,执行applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)方法,这个方法会获取到所有的后置处理器[也就是扩展],这里注入了一个ApplicationContextAwareProcessor后置处理器,它会检查bean是否实现了ApplicationContextAware接口,如果实现了,那么就会回调实现的setApplicationContext(applicationContext),从而获取到spring包装过后的spring容器。然后再执行invokeInitMethods(beanName, wrappedBean, mbd);调用实现了InitializingBean接口的bean重写的afterPropertiesSet方法。

那么就有一个问题:ApplicationContextAwareProcessor这个后置处理器是什么时候怎么注入进去的。
我们来到AbstractBeanFactory中的addBeanPostProcessor方法打上断点,可以知道是在refresh()方法中的prepareBeanFactory(beanFactory);这一句中向容器中注入的该后置处理器的。 


现在回到原来的问题: JobDetailFactoryBean在实现Initializing接口重写的afterPropertiesSet方法中getJobDataMap().put(this.applicationContextJobDataKey, this.applicationContext);把spring容器给到了自己的applicationContextJobDataKey属性上面。而这个属性,之前在初始化bean完成属性注入时,可以设置值,那么就可以拿到spring容器了。


@Override
public void afterPropertiesSet() {
    Assert.notNull(this.jobClass, "Property 'jobClass' is required");

    if (this.name == null) {
        this.name = this.beanName;
    }
    if (this.group == null) {
        this.group = Scheduler.DEFAULT_GROUP;
    }
    if (this.applicationContextJobDataKey != null) {
        if (this.applicationContext == null) {
            throw new IllegalStateException(
                "JobDetailBean needs to be set up in an ApplicationContext " +
                "to be able to handle an 'applicationContextJobDataKey'");
        }
        // 拿到spring容器
        getJobDataMap().put(this.applicationContextJobDataKey, this.applicationContext);
    }

    JobDetailImpl jdi = new JobDetailImpl();
    jdi.setName(this.name != null ? this.name : toString());
    jdi.setGroup(this.group);
    jdi.setJobClass(this.jobClass);
    jdi.setJobDataMap(this.jobDataMap); // 把factoryBean的jobDataMap拿到了,也就拿到了spring工厂
    jdi.setDurability(this.durability);
    jdi.setRequestsRecovery(this.requestsRecovery);
    jdi.setDescription(this.description);
    this.jobDetail = jdi;
}

@Override
@Nullable
public JobDetail getObject() {
    return this.jobDetail;  // 返回jobDetail
}
@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory); // 此处spring自己注册了ApplicationContextAwareProcessor

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

疑问二:scheduler为什么不用调用start()方法了

// 这是通过DefaultLifecycleProcessor的这个处理器扩展实现的,

// 我们来到SchedulerFactoryBean中,
public class SchedulerFactoryBean extends SchedulerAccessor implements 
    FactoryBean<Scheduler>,
    BeanNameAware, 
    ApplicationContextAware, 
    InitializingBean, 
    DisposableBean,
    SmartLifecycle {/*...*/}
// 它实现了SmartLifecycle接口,间接实现了Lifecycle接口,重写了start()方法。
public interface SmartLifecycle extends Lifecycle, Phased {
	boolean isAutoStartup();
	void stop(Runnable callback);
}

// 看一下SchedulerFactoryBean重写的start()方法做了哪些事
@Override
public void start() throws SchedulingException {
    if (this.scheduler != null) {
    try {                                        		// [问题又来了: this.scheduler是怎么初始化的]
    	startScheduler(this.scheduler, this.startupDelay); // 调用下面这个方法
    }catch (SchedulerException ex) {
    	throw new SchedulingException("Could not start Quartz Scheduler", ex);
    }
}

// 被上面这个方法调用
/**
* Start the Quartz Scheduler, respecting the "startupDelay" setting.
* @param scheduler the Scheduler to start
* @param startupDelay the number of seconds to wait before starting
* the Scheduler asynchronously
*/
protected void startScheduler(final Scheduler scheduler, final int startupDelay) {
    if (startupDelay <= 0) {
        scheduler.start(); //  开启scheduler
    }
    else {
        // Not using the Quartz startDelayed method since we explicitly want a daemon
        // thread here, not keeping the JVM alive in case of all other threads ending.
        Thread schedulerThread = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(startupDelay * 1000);
                }
                catch (InterruptedException ex) {
                    // simply proceed
                }
                scheduler.start();  //  开启scheduler,
            }
        };
        schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]");
        schedulerThread.setDaemon(true);
        schedulerThread.start();
    }
}
//至此我们就看到了,scheduler的start方法 被调用了,那么lifecycle接口重写的start()方法,又是怎么被调用的呢?


//我们继续在start()方法上打上断点
// 通过debug追踪,可以看到是DefaultLifecleProcessor的doStart(...)在调用schedulerFactoryBean的start()方法

/**
* Start the specified bean as part of the given set of Lifecycle beans,
* making sure that any beans that it depends on are started first.
* @param lifecycleBeans Map with bean name as key and Lifecycle instance as value
* @param beanName the name of the bean to start
*/
private void doStart(
    Map<String, ? extends Lifecycle> lifecycleBeans,  // 是一个LinkedHashMap
    String beanName,                                  
    boolean autoStartupOnly
) 
{
    Lifecycle bean = lifecycleBeans.remove(beanName); // 从传过来LinkedHashMap中取出bean对象
    if (bean != null && !this.equals(bean)) {
        // 获取到bean工厂,这是因为DefaultLifeCycleProcessor还实现了BeanFactoryAware接口,
        // 把当前bean所需要依赖的其它bean先给启动了再说
        String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName);
        for (String dependency : dependenciesForBean) {
            doStart(lifecycleBeans, dependency, autoStartupOnly);
        }
        
        if (
            	!bean.isRunning() && // bean没有在运行,并且
            (
                !autoStartupOnly ||     // 传过来的,目前是true,所以这里是false                   
             	!(bean instanceof SmartLifecycle) ||  // bean没有实现smartlecycle接口
             	((SmartLifecycle) bean).isAutoStartup() // 调用bean重写的isAutoStartup()方法
            )                                           // SchdulerFactoryBean这里返回的,默认是true
            // 以上返回true
           ) {
            try {
                bean.start(); // 这里在调用
            }
            catch (Throwable ex) {
                throw new ApplicationContextException(
                    "Failed to start bean '" + beanName + "'", ex);
            }
        }
    }
}   

// 那么DefaultLifecycle的doStart()方法又是怎么被调用的呢?
// 是被DefaultLifecycle的内部类LifecycleGroup中的start()方法调用的
private class LifecycleGroup {
    
    private final List<LifecycleGroupMember> members = new ArrayList<>();
    
    private final Map<String, ? extends Lifecycle> lifecycleBeans;
    
    private final boolean autoStartupOnly;

    private volatile int smartMemberCount;

    public LifecycleGroup(int phase, 
                          long timeout, 
                          Map<String, ? extends Lifecycle> lifecycleBeans, // 给lifecyleBeans赋值
                          boolean autoStartupOnly) {
        this.phase = phase;
        this.timeout = timeout;
        this.lifecycleBeans = lifecycleBeans;
        this.autoStartupOnly = autoStartupOnly;
    }
    
    public void add(String name, Lifecycle bean) {
        if (bean instanceof SmartLifecycle) {
            this.smartMemberCount++;
        }
        this.members.add(new LifecycleGroupMember(name, bean)); // 把bean封装成LifecycleGroupMember
    }                                                           // 再放到members里面去
    
    public void start() {
        if (this.members.isEmpty()) {
            return;
        }
        if (logger.isInfoEnabled()) {
            logger.info("Starting beans in phase " + this.phase);
        }
        Collections.sort(this.members);
        for (LifecycleGroupMember member : this.members) {
            // 获取到所有的members,遍历members,
            // member的名字然后看是否包含在了lifecycleBeans里面
            if (this.lifecycleBeans.containsKey(member.name)) {
                doStart(this.lifecycleBeans, member.name, this.autoStartupOnly);
            }
        }
    } 
}
 
// 内部类的start()方法是被DefaultLfecycleProcessor的startBeans(boolean autoStartupOnly)方法调用
private void startBeans(boolean autoStartupOnly) { // 传过来的是true
    
    // 关键的一步: 之前拿到spring容器,所以这里拿到容器中所有实现了lifecycle接口的所有bean对象
    Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
    Map<Integer, LifecycleGroup> phases = new HashMap<>();
    
    lifecycleBeans.forEach((beanName, bean) -> {
        if (!autoStartupOnly || 
            (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())
           ) {                                                    // 如果没有实现就,返回0
            int phase = getPhase(bean); // 判断bean是否实现了Phased接口,如果实现了,返回就调用并返回int值
            
            LifecycleGroup group = phases.get(phase); // 从map中判断判断这个bean的phase值,
                                                      // 是否有对应的LifecycleGroup
            // 如果没有对应的LifecycleGroup对象
            if (group == null) {
                // 在这里创建的LifecycleGroup内部类对象
                group = new LifecycleGroup(phase,    // 上面获取到的bean的phase值
                                           this.timeoutPerShutdownPhase,  // 默认30000
                                           lifecycleBeans, // spring容器中所有的lifecycle的bean对象
                                           autoStartupOnly); 
                phases.put(phase, group);
            }
                                       // 把这个bean放进lifecycleGroup对象中,
            group.add(beanName, bean); // 所以上面内部类的add方法调用了
        }
    });
    if (!phases.isEmpty()) {
        List<Integer> keys = new ArrayList<>(phases.keySet());
        Collections.sort(keys);
        for (Integer key : keys) {
            phases.get(key).start(); // 挨个调用每个在phases这个map中的每个bean的start()方法
        }
    }
}    

// startBeans方法被DefaultLifecycleProcessor的onRefresh方法调用了,
// 		OnRefresh方法是重写的LifecycleProcessor接口的方法,而LifecycleProcessor接口继承了Lifecycle接口  
@Override
public void onRefresh() {
    startBeans(true);
    this.running = true;
}
    
// onRefresh方法被AbstractApplicationContext的finishRefresh方法调用了    
protected void finishRefresh() {  // finishRefresh是refresh方法的最后一步,容器刷新完毕后,发布相应的事件
    // Clear context-level resource caches (such as ASM metadata from scanning).
    clearResourceCaches();

    // 在这里注入进去的DefaultLifecyleProcessor
    // Initialize lifecycle processor for this context.
    initLifecycleProcessor();

    // Propagate refresh to lifecycle processor first.
    // 在这里被调用
    // 获取到LifecycleProcessor属性值,然后调用他的onRefresh方法
    // 那么LifecycleProcessor是怎样注入进去的呢?
    getLifecycleProcessor().onRefresh(); 

    // Publish the final event.
    publishEvent(new ContextRefreshedEvent(this));

    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}

// 在AbstractApplicationContext的LifecycleProcessor属性上打上断点继续追踪
// 我们来到了AbstractApplicationContext的initLifecycleProcessor方法
    /**
	 * Initialize the LifecycleProcessor.
	 * Uses DefaultLifecycleProcessor if none defined in the context.
	 * @see org.springframework.context.support.DefaultLifecycleProcessor
	 */
    protected void initLifecycleProcessor() {
        // 获取到bean工厂(bean工厂被组合到applicationContext中了,设计模式的味道)
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 判断bean工厂的本地bean中是否包含指定bean   // 定值:"lifecycleProcessor"
        if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
            this.lifecycleProcessor = beanFactory.getBean(
                                            LIFECYCLE_PROCESSOR_BEAN_NAME, // 指定的名字
                                            LifecycleProcessor.class       // 指定的接口
            );
        }
        else {
            // 没有的话,那么就创建一个默认的DefaultLifecycleProcessor对象,于是这个对象就创建好了
            DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();
            defaultProcessor.setBeanFactory(beanFactory);
            this.lifecycleProcessor = defaultProcessor; // 至此: 完成了lifecycleProcessor的属性赋值
            beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor);
        }
    }


    
    
    
// 应用: 我们定义一个bean实现SmartLifecycle接口,交给spring管理
    @Component
    public class MyLifecycle implements SmartLifecycle {
    @Override
    public void start() {
        System.out.println("myLifecycle starting...");
    }

    @Override
    public void stop() {
        System.out.println("myLifecycle stopped...");
    }

    @Override
    public boolean isRunning() {
        return false;
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable callback) {

    }

    @Override
    public int getPhase() {
        return 1;
    }
}
    
// 可以看到打印信息
INFO - Starting beans in phase 1
myLifecycle starting...

疑问三: SchedulerFactoryBean中的this.scheduler是怎么初始化的

// 从上面的分析,在代码里看到了SchedulerFactoryBean种的scheduler是怎么调用的start()方法的。那scheduler这个属性对象是怎么完成赋值的呢?
// 注意到: SchedulerFactoryBean 实现了initializing接口,这个接口在对象初始化完成,并且完成属性注入后,被调用。
// 看看这个方法
@Override
public void afterPropertiesSet() throws Exception {
    if (this.dataSource == null && this.nonTransactionalDataSource != null) {
        this.dataSource = this.nonTransactionalDataSource;
    }

    if (this.applicationContext != null && this.resourceLoader == null) {
        this.resourceLoader = this.applicationContext;
    }

    // Create SchedulerFactory instance...
    SchedulerFactory schedulerFactory 
        = BeanUtils.instantiateClass(this.schedulerFactoryClass);
    // Load and/or apply Quartz properties to the given SchedulerFactory.
    initSchedulerFactory(schedulerFactory);

    if (this.resourceLoader != null) {
        // Make given ResourceLoader available for SchedulerFactory configuration.
        configTimeResourceLoaderHolder.set(this.resourceLoader);
    }
    if (this.taskExecutor != null) {
        // Make given TaskExecutor available for SchedulerFactory configuration.
        configTimeTaskExecutorHolder.set(this.taskExecutor);
    }
    if (this.dataSource != null) {
        // Make given DataSource available for SchedulerFactory configuration.
        configTimeDataSourceHolder.set(this.dataSource);
    }
    if (this.nonTransactionalDataSource != null) {
        // Make given non-transactional DataSource available 
        // for SchedulerFactory configuration.
        configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource);
    }

    // 在对schedulerFactory一顿设置之后,从这个工厂中开始获取scheduler,
    // 并且注入给了schedulerFactoryBean的 scheduler属性
    // Get Scheduler instance from SchedulerFactory.
    try {

        this.scheduler = createScheduler(schedulerFactory, this.schedulerName);

        populateSchedulerContext();
        if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) {
            // Use AdaptableJobFactory as default for a local Scheduler, unless when
            // explicitly given a null value through the "jobFactory" bean property.
            this.jobFactory = new AdaptableJobFactory();
        }
        if (this.jobFactory != null) {
            if (this.jobFactory instanceof SchedulerContextAware) {					                              ((SchedulerContextAware)this.jobFactory) .setSchedulerContext(
                this.scheduler.getContext());
                                                                  }
            this.scheduler.setJobFactory(this.jobFactory); // 给scheduler注入jobFactory
        }
    }

    finally {
        if (this.resourceLoader != null) {
            configTimeResourceLoaderHolder.remove();
        }
        if (this.taskExecutor != null) {
            configTimeTaskExecutorHolder.remove();
        }
        if (this.dataSource != null) {
            configTimeDataSourceHolder.remove();
        }
        if (this.nonTransactionalDataSource != null) {
            configTimeNonTransactionalDataSourceHolder.remove();
        }
    }

    registerListeners();
    registerJobsAndTriggers();
}

// 以上就创建了scheduler,那么这个scheduler又是怎么被spring管理注入的呢?
// 注意到: SchedulerFactoryBean 实现了FactoryBean接口,看看重写的方法
@Override
@Nullable
public Scheduler getObject() {
    return this.scheduler; // 就是直接返回的scheduler
}

3.WebSPI机制配置启动tomcat

导入了spring-web、spring-webmvc的依赖,

在spring-web中的META-INF/services下有特定名称文件javax.servlet.ServletContainerInitializer,
该文件中写入了启动类的全类名: org.springframework.web.SpringServletContainerInitializer,实现了ServletContainerInitializer这个接口。

tomcat在启动的时候,会去类路径下找META-INF/services路径下ServletContainerInitializer的全类名的文件,然后读取这个特定名称文件的内容,加载这个类,即SpringServletContainerInitializer这个类。

SpringServletContainerInitializer这个类中传入了感兴趣的类@HandlesTypes({WebApplicationInitializer.class}),tomcat又会去容器中找任何实现了WebApplicationInitializer这个接口的非抽象类,就是下面写的这个类。然后一个个钓鱼它们的initializer.onStartup(servletContext)方法,并且把ServletContext传给了这个方法。

经过上面这一步,我们自定义的类MyWebAppInitializer就能够在tomcat启动的过程中,拿到servletContext,
从而注册三大组件。

MyWebAppInitializer这个类大部分的功能都已经让父类给做了,比如给tomcat注册监听器ContextLoaderListener,同时加载spring的配置类。给tomcat注册前端控制器DispatcherServlet,同时加载springmvc的配置类。我们只需要重写其中给出配置类位置的方法即可。拓展:我们还可以重写父类的其它方法,在tomcat启动的过程中注册三大组件。

注意: tomcat的这种SPI的加载机制和tomcat加载web.xml是可以并存的

1.MyWebAppInitializer

package com.zzhua;

import com.zzhua.config.AppConfig;
import com.zzhua.config.RootConfig;
import org.springframework.web.servlet.support
    .AbstractAnnotationConfigDispatcherServletInitializer;
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

   //获取根容器的配置类;(Spring的配置文件)   父容器;
   @Override
   protected Class<?>[] getRootConfigClasses() {
      // TODO Auto-generated method stub
      return new Class<?>[]{RootConfig.class};
   }

   //获取web容器的配置类(SpringMVC配置文件)  子容器;
   @Override
   protected Class<?>[] getServletConfigClasses() {
      // TODO Auto-generated method stub
      return new Class<?>[]{AppConfig.class};
   }

   //获取DispatcherServlet的映射信息
   //  /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
   //  /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
   @Override
   protected String[] getServletMappings() {
      // TODO Auto-generated method stub
      return new String[]{"/"};
   }
    
    /**
    // 比如说在此处通过重写onStatup方法,注册一个servlet。
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
              ServletRegistration.Dynamic test2Servlet 
                                    = servletContext.addServlet("test2", new TestServlet());
              test2Servlet.addMapping("/test2");
              super.onStartup(servletContext);

        }
   */

}

2.RootConfig

相当于spring的父容器,spring主配置类

@ComponentScan("com.zzhua.job")
@ImportResource(locations = "classpath:spring-quartz.xml")
public class RootConfig {

}

3.AppConfig

相当于springmvc的子容器,springmvc的配置类

package com.zzhua.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.*;

//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan("com.zzhua.controller")
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter  {


    //静态资源访问
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        // TODO Auto-generated method stub
        configurer.enable();
    }

    //拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // TODO Auto-generated method stub
        //super.addInterceptors(registry);
//        registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
    }

}

4.Springboot整合quartz

1. 引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

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

    <groupId>com.zzhua</groupId>
    <artifactId>springboot-quartz-demo02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-quartz-demo02</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <!--druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. 建表

/*
Navicat MySQL Data Transfer

Source Server         : localhost_3306
Source Server Version : 50717
Source Host           : 127.0.0.1:3306
Source Database       : quartz-demo

Target Server Type    : MYSQL
Target Server Version : 50717
File Encoding         : 65001

Date: 2020-07-14 16:37:31
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for qrtz_blob_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_blob_triggers`;
CREATE TABLE `qrtz_blob_triggers` (
  `sched_name` varchar(120) NOT NULL,
  `trigger_name` varchar(200) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  `blob_data` blob,
  PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
  CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_calendars
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_calendars`;
CREATE TABLE `qrtz_calendars` (
  `sched_name` varchar(120) NOT NULL,
  `calendar_name` varchar(200) NOT NULL,
  `calendar` blob NOT NULL,
  PRIMARY KEY (`sched_name`,`calendar_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_cron_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_cron_triggers`;
CREATE TABLE `qrtz_cron_triggers` (
  `sched_name` varchar(120) NOT NULL,
  `trigger_name` varchar(200) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  `cron_expression` varchar(120) NOT NULL,
  `time_zone_id` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
  CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_fired_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_fired_triggers`;
CREATE TABLE `qrtz_fired_triggers` (
  `sched_name` varchar(120) NOT NULL,
  `entry_id` varchar(95) NOT NULL,
  `trigger_name` varchar(200) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  `instance_name` varchar(200) NOT NULL,
  `fired_time` bigint(20) NOT NULL,
  `sched_time` bigint(20) NOT NULL,
  `priority` int(11) NOT NULL,
  `state` varchar(16) NOT NULL,
  `job_name` varchar(200) DEFAULT NULL,
  `job_group` varchar(200) DEFAULT NULL,
  `is_nonconcurrent` varchar(5) DEFAULT NULL,
  `requests_recovery` varchar(5) DEFAULT NULL,
  PRIMARY KEY (`sched_name`,`entry_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_job_details
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_job_details`;
CREATE TABLE `qrtz_job_details` (
  `sched_name` varchar(120) NOT NULL,
  `job_name` varchar(200) NOT NULL,
  `job_group` varchar(200) NOT NULL,
  `description` varchar(250) DEFAULT NULL,
  `job_class_name` varchar(250) NOT NULL,
  `is_durable` varchar(5) NOT NULL,
  `is_nonconcurrent` varchar(5) NOT NULL,
  `is_update_data` varchar(5) NOT NULL,
  `requests_recovery` varchar(5) NOT NULL,
  `job_data` blob,
  PRIMARY KEY (`sched_name`,`job_name`,`job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_locks
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_locks`;
CREATE TABLE `qrtz_locks` (
  `sched_name` varchar(120) NOT NULL,
  `lock_name` varchar(40) NOT NULL,
  PRIMARY KEY (`sched_name`,`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_paused_trigger_grps
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_paused_trigger_grps`;
CREATE TABLE `qrtz_paused_trigger_grps` (
  `sched_name` varchar(120) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  PRIMARY KEY (`sched_name`,`trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_scheduler_state
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_scheduler_state`;
CREATE TABLE `qrtz_scheduler_state` (
  `sched_name` varchar(120) NOT NULL,
  `instance_name` varchar(200) NOT NULL,
  `last_checkin_time` bigint(20) NOT NULL,
  `checkin_interval` bigint(20) NOT NULL,
  PRIMARY KEY (`sched_name`,`instance_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_simple_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_simple_triggers`;
CREATE TABLE `qrtz_simple_triggers` (
  `sched_name` varchar(120) NOT NULL,
  `trigger_name` varchar(200) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  `repeat_count` bigint(20) NOT NULL,
  `repeat_interval` bigint(20) NOT NULL,
  `times_triggered` bigint(20) NOT NULL,
  PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
  CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_simprop_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_simprop_triggers`;
CREATE TABLE `qrtz_simprop_triggers` (
  `sched_name` varchar(120) NOT NULL,
  `trigger_name` varchar(200) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  `str_prop_1` varchar(512) DEFAULT NULL,
  `str_prop_2` varchar(512) DEFAULT NULL,
  `str_prop_3` varchar(512) DEFAULT NULL,
  `int_prop_1` int(11) DEFAULT NULL,
  `int_prop_2` int(11) DEFAULT NULL,
  `long_prop_1` bigint(20) DEFAULT NULL,
  `long_prop_2` bigint(20) DEFAULT NULL,
  `dec_prop_1` decimal(13,4) DEFAULT NULL,
  `dec_prop_2` decimal(13,4) DEFAULT NULL,
  `bool_prop_1` varchar(5) DEFAULT NULL,
  `bool_prop_2` varchar(5) DEFAULT NULL,
  PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
  CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for qrtz_triggers
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_triggers`;
CREATE TABLE `qrtz_triggers` (
  `sched_name` varchar(120) NOT NULL,
  `trigger_name` varchar(200) NOT NULL,
  `trigger_group` varchar(200) NOT NULL,
  `job_name` varchar(200) NOT NULL,
  `job_group` varchar(200) NOT NULL,
  `description` varchar(250) DEFAULT NULL,
  `next_fire_time` bigint(20) DEFAULT NULL,
  `prev_fire_time` bigint(20) DEFAULT NULL,
  `priority` int(11) DEFAULT NULL,
  `trigger_state` varchar(16) NOT NULL,
  `trigger_type` varchar(8) NOT NULL,
  `start_time` bigint(20) NOT NULL,
  `end_time` bigint(20) DEFAULT NULL,
  `calendar_name` varchar(200) DEFAULT NULL,
  `misfire_instr` smallint(6) DEFAULT NULL,
  `job_data` blob,
  PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
  KEY `sched_name` (`sched_name`,`job_name`,`job_group`),
  CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `qrtz_job_details` (`sched_name`, `job_name`, `job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3. yml配置

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/quartz-demo?serverTimezone=GMT&characterEncoding=utf8
                                &zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initialSize: 2
      minIdle: 2
      maxActive: 30
      #StatViewServlet:
      #loginUsername: admin
      #loginPassword: admin
  quartz:
    #相关属性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: DefaultQuartzScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            isClustered: false
            clusterCheckinInterval: 10000
            useProperties: true
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
          dataSource:
            default:
              URL: jdbc:mysql://127.0.0.1:3306/quartz-demo?characterEncoding=utf-8
              user: root
              password: root
              driver: com.mysql.jdbc.Driver

    #数据库方式
    job-store-type: jdbc
    #启动时开启下面2项配置,quartz将会自动创建需要的表,创建表后。
    #记得关掉下面的配置,否则将会清理掉原来的数据,重新建表
#    jdbc:
#      initialize-schema: always

4.动态定时任务的增删改查

JobDetail -> JobKey (name,group) -> Job

Trigger -> TriggerKey (name,group) -> Schedule

@Controller
public class IndexController {
    
    @Autowired
    private Scheduler scheduler;

    @RequestMapping(value = "hasExecuted",method = RequestMethod.GET)
    @ResponseBody
    public String hasExecuted(String jobName,String jobGroup){

        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        JobDetail oldJobDetail = null;
        try {
            oldJobDetail = scheduler.getJobDetail(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

        if (oldJobDetail == null) {
            return "未开启";
        } else {
            return "已开启";
        }
    }

    @ResponseBody
    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index() throws SchedulerException {

        TriggerKey triggerKey = TriggerKey.triggerKey("zzhua-MyTrigger", "MyTrigger");
        CronTrigger triggerOld = null;

        try {
            // 从Scheduler中根据TriggerKey
            triggerOld = (CronTrigger) scheduler.getTrigger(triggerKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

        if (triggerOld == null) {

            //cron表达式
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/6 * * * * ?");

            //将job加入到jobDetail中
            JobDetail jobDetail = JobBuilder.newJob(UploadTask.class).withIdentity("zzhua-MyJob", "MyJob").build();
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("zzhua-MyTrigger","MyTrigger").withSchedule(cronScheduleBuilder).build();

            //执行任务
            scheduler.scheduleJob(jobDetail, trigger);
            return "开始执行任务!";
        } else {
            System.out.println("当前job已存在--------------------------------------------");
            return "该任务已存在";
        }
    }

    /**
     * 删除job
     *
     * @param triggerName  触发器名称
     * @param triggerGroup 触发器分组
     * @param jobName      任务名称
     * @param jobGroup     任务分组
     * @throws SchedulerException
     */
    public void deleteJob(String triggerName, 
                          String triggerGroup, 
                          String jobName, 
                          String jobGroup) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroup);
        scheduler.pauseTrigger(triggerKey);
        scheduler.unscheduleJob(triggerKey);
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        scheduler.deleteJob(jobKey);
    }

    /**
     * 修改定时任务
     *
     * @param oldTriggerKey 需要修改的TriggerKey 也就是唯一标识
     * @param cron          新的cron表达式
     */
    public void updateJob(TriggerKey oldTriggerKey, String cron) {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(oldTriggerKey).withSchedule(scheduleBuilder).build();
        try {
            scheduler.rescheduleJob(oldTriggerKey, cronTrigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 新增job任务
     *
     * @param jobName          job名称
     * @param jobGroupName     job分组名称
     * @param triggerName      触发器名称
     * @param triggerGroupName 触发器分组名称
     * @param jobClass         需要执行的job.class
     * @param cron             cron 表达式
     * @throws SchedulerException
     */
    public void addJob(String jobName, String jobGroupName,
                       String triggerName, String triggerGroupName, 
                       String jobClassName, String cron) 
                                  throws SchedulerException, ClassNotFoundException {
        
        // 根据jobClassName获取到job类
        Class<Job> jobClass = (Class<Job>) Class.forName(jobClassName);
        
        JobDetail jobDetail = JobBuilder.newJob(jobClass)
                                        .withIdentity(jobName, jobGroupName).build();

        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        Trigger trigger = TriggerBuilder.newTrigger()
                                        .withIdentity(triggerName, triggerGroupName)
                                        .withSchedule(cronScheduleBuilder).build();
        
        scheduler.scheduleJob(jobDetail, trigger);
    }


}

本文地址:https://blog.csdn.net/qq_16992475/article/details/107377693