天天看點

SpringBoot項目使用多線程處理任務時無法通過Autowired注入bean

  最近在做一個“溫濕度控制”的項目,項目要求通過使用者設定的溫濕度數值和實時采集到的數值進行比對分析,因為資料的對比與分析是一個通過前端頁面控制的定時任務,經理要求在使用者開啟定時任務時,單獨開啟一個線程進行資料的對比分析,并将采集到的溫濕度數值存入資料庫中的曆史資料表,按照我們正常的邏輯應該是使用者在請求開啟定時任務時,前端頁面通過調用後端接口,建立一個新的線程來執行定時任務,然後線上程類中使用 @Autowired 注解注入儲存曆史資料的service層,線上程類中調用service層儲存曆史資料的方法實作溫濕度資料的儲存,這時就出現了一個很尴尬的問題,在新開啟的線程中使用 @Autowired 注解無法注入需要的bean(即:儲存曆史資料的service層),程式一直在報 NullPointerException 。

  這是controller層,方法 startExperiment 和 stopExperiment 分别是開始定時任務和停止定時任務的方法,getData方法不屬于本次讨論範圍,不用管

  如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進階交流:854630135,群裡有阿裡大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家。

  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: 2021/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;

  * @Package: com.backstage.service

  * @ClassName: MainPageService

  * @Description: 首頁面相關操作業務層接口

  * @CreateDate: 2021/8/29 9:51

  public interface MainPageService {

  JsonResponse startExperiment(HttpServletRequest request, Threshold threshold);

  JsonResponse stopExperiment();

  JSONObject getData();

  service 層實作類代碼,關于springboot項目使用多線程進行業務處理不屬于本章節的讨論範圍,如有需要,請留言,我會在看到留言後第一時間更新相關技術文章,由于這裡删除了一些與本章節無關的代碼,如果複制到

買二手手遊

開發工具内有報錯問題,麻煩大家提醒我一下,以便修改,非常感謝

  package com.backstage.servicepl;

  import com.backstage.entity.*;

  import com.backstage.monitor.TimingMonitoring;

  import com.backstage.service.*;

  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 java.text.SimpleDateFormat;

  import java.util.Date;

  import java.util.List;

  import java.util.concurrent.ScheduledFuture;

  * @Package: com.backstage.servicepl

  * @ClassName: MainPageServiceImpl

  * @Description: 首頁面相關操作業務層實作類

  @Service

  public class MainPageServiceImpl implements MainPageService {

  private ThreadPoolTaskScheduler threadPoolTaskScheduler;

  private ScheduledFuture future2;

  @Bean

  public ThreadPoolTaskScheduler threadPoolTaskScheduler() {

  return new ThreadPoolTaskScheduler();

  @Override

  TimingMonitoring timingMonitoring=new TimingMonitoring();

  timingMonitoring.setThreshold(threshold, list, experiment.getId(), experimentData.getId());

  future2=threadPoolTaskScheduler.schedule(new TimingMonitoring(), new Trigger() {

  public Date nextExecutionTime(TriggerContext triggerContext) {

  //設定定時任務的執行時間為3秒鐘執行一次

  return new CronTrigger("0/10 ?").nextExecutionTime(triggerContext);

  });

  return new JsonResponse(0,"開始實驗!");

  if (future2 !=null) {

  experimentService.upd(getTime());

  future2.cancel(true);

  return new JsonResponse(0,"結束實驗!");

  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.ValveValue;

  import com.backstage.service.DetailedDataService;

  * @Package: com.backstage.monitor

  * @ClassName: TimingMonitoring

  * @Description: 定時監測溫(濕)度 資料

  * @CreateDate: 2021/8/29 10:11

  public class TimingMonitoring implements Runnable{

  //曆史資料業務層接口

  public DetailedDataService detailedDataService;

  private Threshold threshold; //門檻值實體類

  private List settingData; //設定的溫濕度資料

  private Integer id; //實驗記錄id

  private Integer dataId; //曆史資料主表id

  public void setThreshold(Threshold threshold, List settingData, Integer id, Integer dataId) {

  this.threshold=threshold;

  this.settingData=settingData;

  this.id=id;

  this.dataId=dataId;

  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]);

  * 擷取目前時間,精确到毫秒

  擷取bean對象的工具類,既然程式無法通過注解拿到需要的bean,那就隻好自己寫個工具類來擷取喽,下面是工具類代碼

  package com.backstage.config;

  import org.springframework.beans.BeansException;

  import org.springframework.context.ApplicationContext;

  import org.springframework.context.ApplicationContextAware;

  import org.springframework.stereotypeponent;

  * @Package: com.backstage.config

  * @ClassName: ApplicationContextProvider

  * @Description: 擷取bean對象的工具類

  * @CreateDate: 2021/8/31 13:26

  * Author:ZhuShangJin

  * Date:2021/7/3

  @Component

  public class ApplicationContextProvider implements ApplicationContextAware {

  * 上下文對象執行個體

  private static ApplicationContext applicationContext;

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

  this.applicationContext=applicationContext;

  * 擷取applicationContext

  public static ApplicationContext getApplicationContext() {

  return applicationContext;

  * 通過name擷取 Bean.

  * @param name

  public static Object getBean(String name) {

  return getApplicationContext().getBean(name);

  * 通過class擷取Bean.

  * @param clazz

  * @param

  public static T getBean(Class clazz) {

  return getApplicationContext().getBean(clazz);

  * 通過name,以及Clazz傳回指定的Bean

  public static T getBean(String name, Class clazz) {

  return getApplicationContext().getBean(name, clazz);

  這樣呢,就可以線上程類中寫一個無參的構造方法,在構造方法中,通過調用工具類中的 getBean() 方法就可以拿到執行個體了,程式在調用這個線程類時,會自動調用其無參的構造方法,在構造方法中我們将需要的bean對象注入,然後就可以正常使用了,下邊是線程類修改後的代碼,由于别的地方沒有改動,是以這裡隻給大家改動的代碼,省得大家看到一大堆代碼頭疼。

  public TimingMonitoring() {

  //new的時候注入需要的bean

  this.detailedDataService=ApplicationContextProvider.getBean(DetailedDataService.class);

  好了,至此呢,問題就得到解決了,文章中如錯誤或不足,請指出,不勝感激