分散式定時器 Quartz 作業中注入 Spring 依賴項

东北大亨發表於2024-09-14

一、問題描述

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 + "}<----");
    }
}

第三種

 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 + "}<----");
    }

相關文章