解决Spring boot集成quartz时service注入失败为null的问题

解决Spring boot集成quartz时service注入失败为null的问题

一、报错信息

java 复制代码
java.lang.NullPointerException: null
at farbun.server.scheduledTask.ScheduledTasks.execute(ScheduledTasks.java:41) ~[classes/:na]

二、代码

任务类源代码

java 复制代码
package farbun.server.scheduledTask;

import farbun.server.service.CustomerSerice;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author: 小新
 * @Date: 2024/12/15 10:18
 */
@Slf4j
@Component
public class ScheduledTasks implements Job {

    @Autowired
    private CustomerSerice customerSerice; // 这里customerSerice为null

    /**
     * 触发任务
     * 查询
     * 添加
     * @param context
     * @throws JobExecutionException
     */
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 获取当前日期时间
        LocalDateTime currentDateTime = LocalDateTime.now();
        // 计算前一天的日期时间
        LocalDateTime previousDateTime = currentDateTime.minusDays(1);
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 格式化前一天的日期时间为字符串
        String previousDateTimeStr = previousDateTime.format(formatter);
        log.info(previousDateTimeStr);
        
        // 调用方法
        log.info("customerSearcher方法开始执行了。。。。");
        customerSerice.customerSearcher(previousDateTimeStr);
        log.info("customerSearcher方法执行完了===================");
        log.info("selectAndAdd方法开始执行了。。。。");
        customerSerice.selectAndAdd();
        log.info("selectAndAdd方法执行完了========================");
    }
}

配置类原代码

java 复制代码
package farbun.server.config;

import farbun.server.scheduledTask.ScheduledTasks;
import farbun.server.service.CustomerSerice;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * @Author: 小新
 * @Date: 2023/8/15 10:28
 */
@Configuration
public class QuartzConfig {
    /**
     * 创建定时任务
     * @return
     */
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean factory = new JobDetailFactoryBean();
        factory.setJobClass(ScheduledTasks.class); // 指定作业类
        factory.setDurability(true);
        return factory;
    }

    /**
     * 创建触发器
     * @param  jobDetail
     * @return
     */
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetail jobDetail) {
        CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
        factory.setJobDetail(jobDetail);
        factory.setCronExpression("0 0 0 * * ?"); // 每天凌晨0点触发
        return factory;
    }

    /**
     * 注册调度器
     * @param trigger
     * @return
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(Trigger trigger) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setTriggers(trigger);
        return factory;
    }
}

三、注入失败原因

  • 出现这个问题的根本原因,还得从spring的IOC和AOP的基本原理开始讲起
  • 在IOC中,是spring先把需要放置到IOC容器的对象放入,然后在通过IOC机制去请求获得该对象的时候,然后调用出来IOC容器中准备好的对象。
  • 具体在springboot中的表现,如果你在一个类中增加了Component的注解,或者在一个Configure中增加了Bean的注解,IOC就会明白你想要把该对象放入到容器,然后在需要容器帮你实例化的地方加入Autoware,容器就会把该对象给注入。
  • 需要注意的地方是,容器只能对容器注入的对象内部的属性注入,如果你通过自己的代码new了一个对象,这对象里面的Autoware的属性是不起作用的。
  • 很好理解,因为你的对象不在容器的管理范围,容器就无法去注入。
  • 而在quartz的job对象,是通过直接传入job类的class,由quartz框架去实例化的,而非通过spring框架去实例化的,自然就无法完成注解
java 复制代码
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(ScheduledTasks.class); // 把ScheduledTasks.class传入了JobDetailFactoryBean
factory.setDurability(true);

四、解决的思路1

  • 在job中通过Autoware注解去实现,是不太可能了。
  • 而JobDetail 可以通过jobDataMap的属性来传递对象,我们可以在需要spring注入的地方,把我们要注入的对象放到jobDataMap中去,然后在job中取出来使用,来绕道完成注解。

1、任务类修改

java 复制代码
public void execute(JobExecutionContext context) throws JobExecutionException {
    ...

    CustomerSerice customerSerice = (CustomerSerice) context.getJobDetail().getJobDataMap().get("customerSerice");

    // 调用方法
    log.info("customerSearcher方法开始执行了。。。。");
    customerSerice.customerSearcher(previousDateTimeStr);
    log.info("customerSearcher方法执行完了===================");
    log.info("selectAndAdd方法开始执行了。。。。");
    customerSerice.selectAndAdd();
    log.info("selectAndAdd方法执行完了========================");
}

2、配置类修改

java 复制代码
/**
 * 创建触发器
 * @param  jobDetail
 * @return
 */
@Autowired
private CustomerSerice customerSerice;
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetail jobDetail) {
    // 调度器
    CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
    //在调用调度器的地方去实现注入
    jobDetail.getJobDataMap().put("customerSerice",customerSerice);
    //
    factory.setJobDetail(jobDetail);
    factory.setCronExpression("0 0 0 * * ?"); // 每天凌晨0点触发
    return factory;
}
  • 经过测试,我们已经能解决了在job中无法注入的问题。但是也有一些缺点,比如我们要再数据库中保存很多的任务,而每个任务所调用service都不一样。
  • 我们就无法在我们的使用调度器的地方去实现找到需要注入的对象,然后放到jobDataMap中去。

五、 解决的思路2

  • 我们把spring的容器的context注入,然后job中需要什么注入对象,就直接从context中去获得 ,这样就实现了通用性。
java 复制代码
@Autowired
private ServletContext servletContext;
private void scheduleSumJob(Scheduler scheduler) throws SchedulerException
    {
        JobDetail jobDetail = JobBuilder.newJob(SumJob.class)
                .withIdentity("sumJob","group1")
                .build();
        jobDetail.getJobDataMap().put("context",servletContext);
 
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/2 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1","group1")
                .withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }
java 复制代码
public class SumJob implements Job {
 
 
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException
    {
        ServletContext context = (ServletContext)jobExecutionContext.getJobDetail()
                .getJobDataMap().get("context");
        WebApplicationContext cxt = (WebApplicationContext) context.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        EmployeeService employeeService = cxt.getBean(EmployeeService.class);
        employeeService.freshAreaEmployeeNum();
    }
}
相关推荐
魔道不误砍柴功几秒前
2025年Java无服务器架构实战:AWS Lambda与Spring Cloud Function深度整合
java·架构·serverless
豌豆花下猫2 分钟前
Python 潮流周刊#97:CUDA 终于原生支持 Python 了!(摘要)
后端·python·ai
smileNicky13 分钟前
SpringBoot系列之集成Redisson实现布隆过滤器
java·spring boot·redis·布隆过滤器
隔壁小查15 分钟前
【后端开发】初识Spring IoC与SpringDI、图书管理系统
java·spring·okhttp
程序员沉梦听雨32 分钟前
外观模式详解
java·设计模式·外观模式
yumuing34 分钟前
融合动态权重与抗刷机制的网文评分系统——基于优书网、IMDB与Reddit的混合算法实践
后端·算法·架构
bingbingyihao43 分钟前
接口请求控制工具
java·nginx·负载均衡
cyz1410011 小时前
树莓派4B配置wifi热点,可访问http协议
linux·网络·windows·后端·网络协议·http·树莓派
橘子青衫1 小时前
并发编程难题:死锁、活锁、饥饿深度剖析
java·后端
想不明白的过度思考者1 小时前
初识数据结构——深入理解LinkedList与链表:吃透LinkedList与链表的终极指南
java·数据结构·链表