Fork me on GitHub

springboot 整合quartz实现定时任务

  为了系统每天能够对用户的考勤数据进行检查,防止用户上午或者下午忘记打卡而影响了接下来的打卡操作,因此我们必须要在中午12点和晚上24:00设置一个定时任务对考勤数据进行检查。

  在项目中,能够完成定时调度的方案有很多,例如在Java中,能够进行定时调度的方案有:ScheduledExecutorService和quartz两种,但是这两种本质上都是通过native中的wait方法来实现的。
  ScheduledExecutorService的Scheduled方法,其根据delay周期性的执行任务。其核心执行类和方法:其主要流程就是根据initTime和period时间计算出第一次执行的时间差,然后调用ReentrantLock.newCondition().awaitNanos(long nanosTimeout)方法,到指定的时间进行唤醒,分配线程进行执行。对于后续的周期性执行的await时间为period. 而quartz定时任务的调度是通过Object.wait方式(native方法)来实现的,其本质是通过操作系统的时钟来实现。原理在这里细讲,具体的调度流程可以参考百度。这里主要记录如何使用quartz实现定时任务的调度过程。

使用quartz实现定时任务的调度
  quartz 的设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要CronTrigger。

在我们日常的开发过程中,很多时候,定时任务都不是写死的,而是写到数据库中,实现定时任务的动态配置。

  • 1.使用quartz时需要的maven依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.11.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
    </dependency>
    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.3.0</version>
    </dependency>
  • 2.在项目中添加quartz.properties(这样就不会走自带的properties文件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    # 固定前缀org.quartz
    # 主要分为scheduler、threadPool、jobStore、plugin等部分
    #
    #
    org.quartz.scheduler.instanceName = DefaultQuartzScheduler
    org.quartz.scheduler.rmi.export = false
    org.quartz.scheduler.rmi.proxy = false
    org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

    # 实例化ThreadPool时,使用的线程类为SimpleThreadPool
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

    # threadCount和threadPriority将以setter的形式注入ThreadPool实例
    # 并发个数
    org.quartz.threadPool.threadCount = 5
    # 优先级
    org.quartz.threadPool.threadPriority = 5
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

    org.quartz.jobStore.misfireThreshold = 5000

    org.quartz.scheduler.skipUpdateCheck=true

    # Job Store 方案2选1
    ## 1. 默认存储在内存中
    #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

    ## 2. 持久化到数据库
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.tablePrefix = QRTZ_
    org.quartz.jobStore.dataSource = qzDS
    org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
    org.quartz.dataSource.qzDS.URL = jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=UTF-8
    org.quartz.dataSource.qzDS.user = root
    org.quartz.dataSource.qzDS.password = Passw0rd
    org.quartz.dataSource.qzDS.maxConnections = 10
  • 3.在mysql数据库中创建quartz相关的表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    #
    # Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
    #
    # PLEASE consider using mysql with innodb tables to avoid locking issues
    #
    # In your Quartz properties file, you'll need to set
    # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    #

    DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
    DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
    DROP TABLE IF EXISTS QRTZ_LOCKS;
    DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
    DROP TABLE IF EXISTS QRTZ_CALENDARS;


    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) NULL,
    JOB_CLASS_NAME VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
    );

    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) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
    REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
    );

    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(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );

    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(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );

    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) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );

    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 NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );

    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)
    );

    CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
    (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
    );

    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(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
    );

    CREATE TABLE QRTZ_SCHEDULER_STATE
    (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
    );

    CREATE TABLE QRTZ_LOCKS
    (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
    );


    commit;
  • 4.项目中quartz结构示意图

  • 5.实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Entity
    @Table
    public class ScheduleJob {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String jobName;
    private String cronExpression;
    private String springId;
    private String methodName;
    private String jobStatus;

    //省略getter和setter方法
  • 6.任务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    @EnableScheduling
    public class TaskConfiguration {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(){
    return new SchedulerFactoryBean();
    }
    }
  • 7.任务实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    @Service
    @Transactional
    public class TaskServiceImpl implements ITaskService {

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private ScheduleJobRepository scheduleJobRepository;

    @Override
    public void timingTask() {
    //查询数据库是否存在需要定时的任务
    List<ScheduleJob> scheduleJobs = scheduleJobRepository.findAllByJobStatus("1");
    if (scheduleJobs != null) {
    scheduleJobs.forEach(this::execute);
    }
    }

    //添加任务
    private void execute(ScheduleJob scheduleJob){
    try {
    //声明调度器
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    //添加触发调度名称
    TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName());
    //设置触发时间
    CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());
    //触发建立
    Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
    //添加作业名称
    JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName());
    //建立作业
    JobDetail jobDetail = JobBuilder.newJob(QuartzFactory.class).withIdentity(jobKey).build();
    //传入调度的数据,在QuartzFactory中需要使用
    jobDetail.getJobDataMap().put("scheduleJob",scheduleJob);
    //调度作业
    scheduler.scheduleJob(jobDetail,trigger);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
    }

  通过上述代码查询数据库中存在的定时任务,因为项目中使用了jpa技术,查询语句直接使用了jpa自带的接口。查询到定时任务之后,将任务添加到项目中。

  • 7.自定义需要定时启动的job
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Service("taskInfo")
    @Transactional
    public class TaskInfoServiceImpl implements ITaskInfoService {

    @Autowired
    private AttendService attendService;

    @Override
    public void execute() {
    System.out.println("任务执行开始===============任务执行开始");
    attendService.checkAttend();
    System.out.println("任务执行结束===============任务执行结束");
    }
    }

  上述代码中的checkAttend()接口即需要定时启动的job.

  • 8.检查用户考勤数据的接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    @Override
    public void checkAttend() {
    /**
    * 第一种情况,上下午都没有打卡,调用自动执行任务,将打卡记录补上,
    * 记为缺勤一整天,状态为异常
    */
    List<String> userIdList = attendMapper.getUserIdAbsence();
    if (CollectionUtils.isNotEmpty(userIdList)){
    List<Attend> list = new ArrayList<>();
    for (String userId : userIdList){
    Attend attend = new Attend();
    attend.setUserId(userId);
    attend.setAttendId(UUID.getUUID());
    attend.setUserAttendId(UUID.getUUID());
    attend.setAttendDate(DateUtils.getCurrentDay());
    attend.setAttendWeek(DateUtils.getCurrentWeek());
    //拼装备注信息
    //根据用户Id查询用户名称
    String userName=userMapper.getUserByUserId(userId).getUserName();
    String currentDay=DateUtils.getCurrentDay();
    attend.setRemark(userName+"-"+currentDay+"打卡记录");
    attend.setAbsence(Constants.AttendConstants.ABSENCE_DAY);
    attend.setAttendStatus(Constants.AttendConstants.ATTEND_STATUS_ABNORMAL);
    list.add(attend);
    }
    //打卡记录表批量插入
    attendMapper.batchInsert(list);
    //关联记录表批量插入
    userAttendMapper.batchInsert(list);
    }
    /**
    * 第二种情况:上午完成了打卡,但是晚上忘记了打卡
    * 同样将缺勤时间记为240分钟,状态记为异常
    */
    List<Attend> attendList=attendMapper.getTodayEveningAbsence();
    //将状态置为0,缺勤240分钟
    if (CollectionUtils.isNotEmpty(attendList)){
    for (Attend attend:attendList){
    attend.setAttendStatus(Constants.AttendConstants.ATTEND_STATUS_ABNORMAL);
    attend.setAbsence(attend.getAbsence()+Constants.AttendConstants.ABSENCE_DAY/2);
    attendMapper.updateByPrimaryKeySelective(attend);
    }
    }
    }
  • 9.准备存放定时任务的数据库表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;

    -- ----------------------------
    -- Table structure for schedule_job
    -- ----------------------------
    DROP TABLE IF EXISTS `schedule_job`;
    CREATE TABLE `schedule_job` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    `job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    `job_status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    `method_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    `spring_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

    SET FOREIGN_KEY_CHECKS = 1;

在数据库中如下图所示配置:

说明:cron_expression的参数解释:
从左到右分别是:秒,分,时,月的某天,月,星期的某天,年;其中年不是必须的,也就是说任何一个表达式最少需要六项。

先看示列:”0 0/30 8-10 5,20 ?” 表示“每个月的5日和20日的8:00,8:30,9:00,9:30,10:00,10:30”
字符解释:
,:与,表式”,”两边的值都是需要执行的时间,如上例”5,20”,每个月的5日与20日。
-:表示值的范围,如上例”8-10”,从8点开始到10结束,包括8点与10点。
:表式任意可合法的值,如上例”*”是处于月份的字段,所以代表1-12中的任意值,所以上例是指“每个月”。
/:增量,如上例是指从0分开始,每过30分钟取一次值。如果换成”5/8”就是从第5钟开始每过8分钟取一次值:8:05,8:13,8:21,8:29等等
?:不指定值,就是“我也不知道”的意思,只能出现在“月的某天,星期的某天”项中。在什么情况下用呢?如上例如果指定值为星期一,那么可能会出现如4月5日不是星期一,这里就是不对应,有冲突,所以指定为”?”,也就是说我也不知道是星期几,只要是5日与20日就行了,至于是星期几我才不管呢!
L:最后的,last的意思,只能出现在“月的某天,星期的某天”项中。表示当前月或当前星期的最后一天,注意的是星期的最后一天为星期六。
W:月中最接近指定日期的普通日(星期一到星期五),只能出现在“月的某天”,如”15W”就是说当前月最接近15日的普通日,如果当月的15是星期三就是星期三,如果当月的15是星期六那么就是昨天也就是星期五,如果当月的15是星期天则为第二天也就是星期一。
注意:当前月的第N个星期X日,只能出现在“星期的某天”项中。如”6#3”就是说当前月的第三个星期五,注意”1-7”,1=星期天,2=星期一 等等。

(部分内容来自互联网)

  • 10.启动项目,即可看到任务开始执行

项目运行过程中遇到的问题:

  • 1.在shiro 中自带了quartz,不过版本是1.6.1。这就导致了quartz目前的版本2.3.0与shiro存在兼容性的问题。这个问题的解决方案参考.
  • 2.在项目中整合进quartz之后,报错
    1
    2
    3
    严重 : Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener

    org.springframework.beans.factory.BeanCreationException : Error creating bean with name 'cronTriggerPunch' defined in ServletContext resource [/WEB-INF/applicationContext-attend.xml]: Cannot create inner bean 'org.springframework.scheduling.quartz.JobDetailBean#15e92d7' of type [org.springframework.scheduling.quartz.JobDetailBean] while setting bean property 'jobDetail'; nested exception is org.springframework.beans.factory.BeanCreationException : Error creating bean with name 'org.springframework.scheduling.quartz.JobDetailBean#15e92d7' defined in ServletContext resource [/WEB-INF/applicationContext-attend.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException : Could not instantiate bean class [org.springframework.scheduling.quartz.JobDetailBean]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.apache.commons.collections.SetUtils.orderedSet(Ljava/util/Set;)Ljava/util/Set;

网上百度之后成功解决,检查代码完全正确,没有什么问题,思考着那么就可能是jar包出现问题,一般情况下应该是commons-collections的版本太低,升级之后还是报错。后来去掉了WEB-INF/lib/ 下的cglib-2.1.3.jar包,问题成功解决。

-------------本文结束感谢您的阅读-------------
你的支持是我最大的动力