分布式定时器 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 + "}<----"); }