分布式定时器 Quartz 作业中注入 Spring 依赖项

一、问题描述

    SpringBoot 整合 分布式定时器 Quartz后,job中注入的自定义service,使用@Autowired 从spring容器中获取的对象为null,报空指针异常。

二、问题分析

    sping容器可以管理Bean,但是Quartz的job是分布式定时器自己管理,所有通过@Autowired 从容器中获得对象无法别识别到。及时在自定义job上添加 @service/@Component注解依然无效。原因是job对象在spring容器加载的时候能够注入bean,但是调度时,job对象会重新创建,此时导致已经注入的对象丢失,因此报空指针异常。

三、以下通过三种办法进行解决

第一种:创建容器工具类 SpringUtil 实现 ApplicationContextAware, 此工具会在容器启动后自动加载,使用的时候通过getBean方法直接从容器中获得bean即可。代码和使用方法如下

   1.1 SpringUtil 工具类代码

   

package com.northeasttycoon.shopping.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Component;

/**
 * @author :jack.zhao
 * @description :SpringUtil 容器工具类
 * @version: V1.0.0
 * @create :2022/06/18 10:16
 */
@Component
@Slf4j
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        SpringUtil.context = context;
    }

    /**
     * 获取 Spring Bean
     * @param clazz 类
     * @param <T>   泛型
     * @return 对象
     */
    public static <T> T getBean(Class<T> clazz) {
        if (clazz == null) {
            return null;
        }
        return context.getBean(clazz);
    }

    /**
     * 获取 Spring Bean
     * @param bean 名称
     * @param <T>  泛型
     * @return 对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String bean) {
        if (bean == null) {
            return null;
        }
        return (T) context.getBean(bean);
    }

    /**
     * 获取 Spring Bean
     * @param beanName 名称
     * @param clazz    类
     * @param <T>      泛型
     * @return 对象
     */
    public static <T> T getBean(String beanName, Class<T> clazz) {
        if (null == beanName || "".equals(beanName.trim())) {
            return null;
        }
        if (clazz == null) {
            return null;
        }
        return (T) context.getBean(beanName, clazz);
    }

    /**
     * 获取上下文
     * @return 上下文
     */
    public static ApplicationContext getContext() {
        if (context == null) {
            throw new RuntimeException("There has no Spring ApplicationContext!");
        }
        return context;
    }

    /**
     * 发布事件
     * @param event 事件
     */
    public static void publishEvent(ApplicationEvent event) {
        if (context == null) {
            return;
        }
        try {
            context.publishEvent(event);
        } catch (Exception ex) {
           log.error(ex.getMessage());
        }
    }
}

  1.2 job中使用

 

package com.northeasttycoon.shopping.quartz.job;

import com..northeasttycoon.shopping.common.SpringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author :jack.zhao
 * @description :自定义定时器
 * @version: V1.0.0
 * @create :2022/06/18 10:28
 */
// 持久化
@PersistJobDataAfterExecution
// 禁止并发执行
@DisallowConcurrentExecution
@Slf4j
public class CustomerQuartzJob extends QuartzJobBean {


    @SneakyThrows
    @Override
    protected void executeInternal(JobExecutionContext context) {

        // 业务服务类
        CustomerServer customerServer = SpringUtil.getBean(CustomerServer .class);

        System.out.println("ceshi:"+str);
        String taskName = context.getJobDetail().getJobDataMap().getString("name");
        log.info("数据 job, time:{" + new Date() + "} ,name:{" + taskName + "}<----");
    }
}

 

 第二种方法:

查看QuartzJobFactory源码发现Quartz在初始化Bean未使用Spring的ApplicationContext,所以需要将Quartz的bean初始化注入到Spring中,代码如下

2.1 自定义定时工厂

package com.northeasttycoon.shopping.config;


import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * @author :jack.zhao
 * @description :解决Quartz的bean无法被spring管理
 * @version: V1.0.0
 * @create :2022/06/18 15:18
 */
@Component
public class QuartzJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        //进行注入(这一步解决不能spring注入bean的问题)
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

 2.2 应用

package com.northeasttycoon.shopping.quartz.job;

import com..northeasttycoon.shopping.common.SpringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author :jack.zhao
 * @description :自定义定时器
 * @version: V1.0.0
 * @create :2022/06/18 10:28
 */
// 持久化
@PersistJobDataAfterExecution
// 禁止并发执行
@DisallowConcurrentExecution
@Slf4j
public class CustomerQuartzJob extends QuartzJobBean {


     @Autowired
     CustomerServer  customerServer;

    @SneakyThrows
    @Override
    protected void executeInternal(JobExecutionContext context) {

        // 业务服务类
        customerServer = SpringUtil.getBean(CustomerServer .class);

        System.out.println("ceshi:"+str);
        String taskName = context.getJobDetail().getJobDataMap().getString("name");
        log.info("数据 job, time:{" + new Date() + "} ,name:{" + taskName + "}<----");
    }
}

2.3 自定义配置QuartzJobConfig

@Configuration
public class QuartzJobConfig {

    @Autowired
    private QuartzJobFactory customerJobFactory;  //自定义的factory

    //获取调度器的工厂bean
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        try {
            schedulerFactoryBean.setAutoStartup(false);
            //修改成自己的工厂
            schedulerFactoryBean.setJobFactory(customerJobFactory);
            return schedulerFactoryBean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    //创建schedule调度器
    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }
}

 

第三种

 private CustomerServer customerServer;

    @SneakyThrows
    @Override
    protected void executeInternal(JobExecutionContext context) {
        final String s = tet.UploadStaticInfo();

        ApplicationContext applicationContext = (ApplicationContext)
                context.getScheduler().getContext().get("applicationContext");

        customerServer= applicationContext.getBean(CustomerServer.class);

        System.out.println("ceshi:"+str);
        String taskName = context.getJobDetail().getJobDataMap().getString("name");
        log.info("静态数据 job, time:{" + new Date() + "} ,name:{" + taskName + "}<----");
    }

 

posted @ 2024-09-14 20:08  东北大亨  阅读(32)  评论(0编辑  收藏  举报