一、問題描述
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 + "}<----"); }