解决SpringBoot项目使用多线程处理任务时无法通过@Autowired注入bean问题

最近在做一个“温湿度控制”的项目,项目要求通过用户设定的温湿度数值和实时采集到的数值进行比对分析,因为数据的对比与分析是一个通过前端页面控制的定时任务,经理要求在用户开启定时任务时,单独开启一个线程进行数据的对比分析,并将采集到的温湿度数值存入数据库中的历史数据表,按照我们正常的逻辑应该是用户在请求开启定时任务时,前端页面通过调用后端接口,创建一个新的线程来执行定时任务,然后在线程类中使用 @Autowired 注解注入保存历史数据的service层,在线程类中调用service层保存历史数据的方法实现温湿度数据的保存,这时就出现了一个很尴尬的问题,在新开启的线程中使用 @Autowired 注解无法注入需要的bean(即:保存历史数据的service层),程序一直在报 NullPointerException

这是controller层,方法 startExperiment 和 stopExperiment 分别是开始定时任务和停止定时任务的方法,getData方法不属于本次讨论范围,不用管

package com.backstage.controller;
import com.alibaba.fastjson.JSONObject;
import com.backstage.entity.JsonResponse;
import com.backstage.entity.Threshold;
import com.backstage.service.MainPageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
 * @ProjectName:
 * @Package: com.backstage.controller
 * @ClassName: MainPageController
 * @Description: 主页面相关操作控制器
 * @Author: wangzhilong
 * @CreateDate: 2018/8/29 9:49
 * @Version: 1.0
 */
@RestController
@RequestMapping("/main")
public class MainPageController {
 @Autowired
 private MainPageService mainPageService;
 /**
  * 开始实验
  *
  * @param threshold
  */
 @RequestMapping("/startExperiment")
 public JsonResponse startExperiment(HttpServletRequest request, Threshold threshold) {
  return mainPageService.startExperiment(request, threshold);
 }
 /**
  * 停止实验
  */
 @RequestMapping("/stopExperiment")
 public JsonResponse stopExperiment() {
  return mainPageService.stopExperiment();
 }
 /**
  * 获取实时数据
  *
  * @return
  */
 @RequestMapping("/getData")
 public JSONObject getData() {
  return null;
 }
}

service 层接口代码,没什么好说的,直接上代码:

package com.backstage.service;
import com.alibaba.fastjson.JSONObject;
import com.backstage.entity.JsonResponse;
import com.backstage.entity.Threshold;
import javax.servlet.http.HttpServletRequest;
/**
 * @ProjectName:
 * @Package: com.backstage.service
 * @ClassName: MainPageService
 * @Description: 主页面相关操作业务层接口
 * @Author: wangzhilong
 * @CreateDate: 2018/8/29 9:51
 * @Version: 1.0
 */
public interface MainPageService {
 /**
  * 开始实验
  *
  * @param threshold
  */
 JsonResponse startExperiment(HttpServletRequest request, Threshold threshold);
 /**
  * 停止实验
  */
 JsonResponse stopExperiment();
 /**
  * 获取实时数据
  *
  * @return
  */
 JSONObject getData();
}

service 层实现类代码,关于springboot项目使用多线程进行业务处理不属于本章节的讨论范围,如有需要,请留言,我会在看到留言后第一时间更新相关技术文章,由于这里删除了一些与本章节无关的代码,如果复制到开发工具内有报错问题,麻烦大家提醒我一下,以便修改,非常感谢

package com.backstage.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.backstage.entity.*;
import com.backstage.monitor.TimingMonitoring;
import com.backstage.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
/**
 * @ProjectName:
 * @Package: com.backstage.service.impl
 * @ClassName: MainPageServiceImpl
 * @Description: 主页面相关操作业务层实现类
 * @Author: wangzhilong
 * @CreateDate: 2018/8/29 9:51
 * @Version: 1.0
 */
@Service
public class MainPageServiceImpl implements MainPageService {
 @Autowired
 private ThreadPoolTaskScheduler threadPoolTaskScheduler;
 private ScheduledFuture<?> future2;
 @Bean
 public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
  return new ThreadPoolTaskScheduler();
 }
 /**
  * 开始实验
  *
  * @param threshold
  */
 @Override
 public JsonResponse startExperiment(HttpServletRequest request, Threshold threshold) {
  TimingMonitoring timingMonitoring = new TimingMonitoring();
  timingMonitoring.setThreshold(threshold, list, experiment.getId(), experimentData.getId());
  future2 = threadPoolTaskScheduler.schedule(new TimingMonitoring(), new Trigger() {
   @Override
   public Date nextExecutionTime(TriggerContext triggerContext) {
    //设置定时任务的执行时间为3秒钟执行一次
    return new CronTrigger("0/10 * * * * ?").nextExecutionTime(triggerContext);
   }
  });
  return new JsonResponse(0,"开始实验!");
 }
 /**
  * 停止实验
  */
 @Override
 public JsonResponse stopExperiment() {
  if (future2 != null) {
   experimentService.upd(getTime());
   future2.cancel(true);
  }
  return new JsonResponse(0,"结束实验!");
 }
 /**
  * 获取实时数据
  *
  * @return
  */
 @Override
 public JSONObject getData() {
  return null;
 }
 protected String getTime() {
  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  return format.format(new Date());
 }
}

重点,线程类代码,大家注意看,我在代码最开始使用了spring的 @Autowired 注解注入需要的service,可在调用service中的add方法时,程序报空指针异常,一直认为是add方法或者sql语句有问题,找了一上午,也没发现任何问题,后来单独调用这个add方法是可以正常插入数据的,唯独在这个线程类中调用时报错,感觉和线程有莫大的关系,百度一搜,还真找到了,原来,在线程中为了线程安全,是防注入的,没办法,要用到这个类啊。只能从bean工厂里拿个实例了,继续往下看

package com.backstage.monitor;
import com.backstage.entity.DetailedData;
import com.backstage.entity.Threshold;
import com.backstage.entity.ValveValue;
import com.backstage.service.DetailedDataService;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
 * @ProjectName:
 * @Package: com.backstage.monitor
 * @ClassName: TimingMonitoring
 * @Description: 定时监测温(湿)度 数据
 * @Author: wangzhilong
 * @CreateDate: 2018/8/29 10:11
 * @Version: 1.0
 */
public class TimingMonitoring implements Runnable{
 //历史数据业务层接口
 @Autowired
 public DetailedDataService detailedDataService;
 private Threshold threshold;   //阈值实体类
 private List<ValveValue> settingData; //设定的温湿度数据
 private Integer id;      //实验记录id
 private Integer dataId;     //历史数据主表id

 public void setThreshold(Threshold threshold, List<ValveValue> settingData, Integer id, Integer dataId) {
  this.threshold = threshold;
  this.settingData = settingData;
  this.id = id;
  this.dataId = dataId;
 }
 @Override
 public void run() {
  //模拟从PLC获取到的数据
  String data = "001,50.5,002,37,003,45.6,004,40,005,55.2,006,58";
  if (data == null || data.trim() == "") {
   return; //若获取到的数据为空,则直接停止该方法的执行
  }
  double temperature = 0.0; //温度
  double humidity = 0.0;  //湿度
  Integer type = null;    //数据类型,1是温度,2是湿度
  //解析数据,并将数据保存到历史数据数据库
  String[] str = data.split(",");
  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS");
  for (int i = 0; i < str.length; i++) {
   if (i == 1 || i == 5 || i == 9) { //温度
    type = 1;
    temperature += Double.parseDouble(str[i]);
    //System.out.println("温度" + i + " -》 " + str[i-1] + ":" + str[i]);
    detailedDataService.add(new DetailedData(null, type, Double.parseDouble(str[i]), format.format(new Date()), str[i - 1], dataId));
   }
   if (i == 3 || i == 7 || i == 11) { //湿度
    type = 2;
    humidity += Double.parseDouble(str[i]);
    //System.out.println("湿度" + i + " -》 " + str[i-1] + ":" + str[i]);
    detailedDataService.add(new DetailedData(null, type, Double.parseDouble(str[i]), format.format(new Date()), str[i - 1], dataId));
   }
  }
 }
 /**
  * 获取当前时间,精确到毫秒
  * @return
  */
 protected String getTime() {
  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS");
  return format.format(new Date());
 }
}

获取bean对象的工具类,既然程序无法通过注解拿到需要的bean,那就只好自己写个工具类来获取喽,下面是工具类代码

package com.backstage.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @ProjectName:
 * @Package: com.backstage.config
 * @ClassName: ApplicationContextProvider
 * @Description: 获取bean对象的工具类
 * @Author: wangzhilong
 * @CreateDate: 2018/8/31 13:26
 * @Version: 1.0
 */
/**
 * Author:ZhuShangJin
 * Date:2018/7/3
 */
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
 /**
  * 上下文对象实例
  */
 private static ApplicationContext applicationContext;
 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  this.applicationContext = applicationContext;
 }
 /**
  * 获取applicationContext
  *
  * @return
  */
 public static ApplicationContext getApplicationContext() {
  return applicationContext;
 }
 /**
  * 通过name获取 Bean.
  *
  * @param name
  * @return
  */
 public static Object getBean(String name) {
  return getApplicationContext().getBean(name);
 }
 /**
  * 通过class获取Bean.
  *
  * @param clazz
  * @param <T>
  * @return
  */
 public static <T> T getBean(Class<T> clazz) {
  return getApplicationContext().getBean(clazz);
 }
 /**
  * 通过name,以及Clazz返回指定的Bean
  *
  * @param name
  * @param clazz
  * @param <T>
  * @return
  */
 public static <T> T getBean(String name, Class<T> clazz) {
  return getApplicationContext().getBean(name, clazz);
 }
}

这样呢,就可以在线程类中写一个无参的构造方法,在构造方法中,通过调用工具类中的 getBean() 方法就可以拿到实例了,程序在调用这个线程类时,会自动调用其无参的构造方法,在构造方法中我们将需要的bean对象注入,然后就可以正常使用了,下边是线程类修改后的代码,由于别的地方没有改动,所以这里只给大家改动的代码,省得大家看到一大堆代码头疼。

public TimingMonitoring() {
  //new的时候注入需要的bean
  this.detailedDataService = ApplicationContextProvider.getBean(DetailedDataService.class);
 }

总结

以上所述是小编给大家介绍的SpringBoot项目使用多线程处理任务时无法通过@Autowired注入bean 问题,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

(0)

相关推荐

  • Spring bean 加载执行顺序实例解析

    本文研究的主要是Spring bean 加载执行顺序的相关内容,具体如下. 问题来源: 有一个bean为A,一个bean为B.想要A在容器实例化的时候的一个属性name赋值为B的一个方法funB的返回值. 如果只是在A里单纯的写着: private B b; private String name = b.funb(); 会报错说nullpointException,因为这个时候b还没被set进来,所以为null. 解决办法为如下代码,同时学习下spring中 InitializingBean

  • 关于SpringBoot获取IOC容器中注入的Bean(推荐)

    一: 注入一个TestUtils类 package com.shop.sell.Utils; import com.shop.sell.dto.CartDTO; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TestUtils { @Bean(name="test

  • 谈谈我对Spring Bean 生命周期的理解

    前言 Spring的ioc容器功能非常强大,负责Spring的Bean的创建和管理等功能.而Spring 的bean是整个Spring应用中很重要的一部分,了解Spring Bean的生命周期对我们了解整个spring框架会有很大的帮助. BeanFactory和ApplicationContext是Spring两种很重要的容器,前者提供了最基本的依赖注入的支持,而后者在继承前者的基础进行了功能的拓展,例如增加了事件传播,资源访问和国际化的消息访问等功能.本文主要介绍了ApplicationCo

  • 详解Spring Bean的循环依赖解决方案

    如果使用构造函数注入,则可能会创建一个无法解析的循环依赖场景. 什么是循环依赖 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的相互依赖关系.循环调用其实就是一个死循环,除非有终结条件. Spring中循环依赖场景有: (1)构造器的循环依赖 (2)field属性的循环依赖. 怎么检测是否存在循环依赖 检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,

  • 详解Spring中bean的几种注入方式

    首先,要学习Spring中的Bean的注入方式,就要先了解什么是依赖注入.依赖注入是指:让调用类对某一接口的实现类的实现类的依赖关系由第三方注入,以此来消除调用类对某一接口实现类的依赖. Spring容器中支持的依赖注入方式主要有属性注入.构造函数注入.工厂方法注入.接下来将为大家详细介绍这三种依赖注入的方式以及它们的具体配置方法. 1.属性注入 属性注入即通过setXXX( )方法注入bean的属性值或依赖对象.由于属性注入方式具有可选择性和灵活性高的特点,因此它也是实际开发中最常用的注入方式

  • 关于Spring中Bean的创建进行更多方面的控制

    我们知道Spring Boot 中一个@Controller修饰的Bean是在什么时间被创建的,那么这个Bean创建时间能不能由我们管控?答案是肯定的 关于Spring中Bean的创建,除了配置装配属性外,我们还可以进行更多方面的控制. 1,首先,我们可以控制Bean是单例还是可以生成多个对象的. 在Spring中,Bean默认是单例的,如果想每次请求都生成一个新的Bean对象,可以在定义Bean时,在<bean>标签中配置scope属性为prototype,那么,就会允许该Bean可以被多次

  • 如何在Spring中使用编码方式动态配置Bean详解

    bean与spring容器的关系 Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载.实例化Bean,并建立Bean和Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用. 本文将给大家详细介绍关于在Spring中使用编码方式动态配置Bean的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1 DefaultListableBea

  • 详解Spring-bean的循环依赖以及解决方式

    本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可以应用在我们实际开发项目中. 1. 什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 注意,这里不是函数的循环调用,是对象的相互依赖关系.循环调用其实就是一个死循环,除非有终结条件. Spring中循环依赖场景有: (1)构造器的循环依赖 (2)field属性的循环依赖. 循环依赖的产生和解

  • Spring运行时动态注册bean的方法

    在spring运行时,动态的添加bean,dapeng框架在解析xml的字段时,使用到了动态注册,注册了一个实现了FactoryBean类! 定义一个没有被Spring管理的Controller public class UserController implements InitializingBean{ private UserService userService; public UserService getUserService() { return userService; } pu

  • Spring之动态注册bean的实现方法

    Spring之动态注册bean 什么场景下,需要主动向Spring容器注册bean呢? 如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运行了,而这个基础平台是一直都在运行的,所以在新来任务时,最直观需要注册的就是 DataSource 数据源这个bean了,那么可以怎么玩? I. 主动注册Bean支持 借助BeanDefinition来实现bean的定义,从最终的使用来看,代码比较少,几行而已 public <T> T regis

随机推荐