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

来源:https://www.cnblogs.com/xiaolong1996/archive/2018/09/01/9571645.html
-Advertisement-
Play Games

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

 

 

好了,至此呢,問題就得到解決了,文章中如錯誤或不足,請指出,不勝感激,本人小白一枚,如有不足,請多多包含,也請各位大佬能不吝賜教,抱拳

參考地址:https://blog.csdn.net/zsj777/article/details/80965081


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目:數字三角形 題目介紹:如圖所示的數字三角形,要求從最上方頂點開始一步一步下到最底層,每一步必須下一層,求出所經過的數字的最大和。 輸入:第一行值n,代表n行數值;後面的n行數據代表每一行的數字。 輸出:經過數字的最大和。 例: 輸入: 4 1 3 2 4 10 1 4 3 2 20 輸出: 2 ...
  • Java面向對象中的多線程 多線程 在 面向對象中的多線程中,要理解多線程的知識點,首先要掌握什麼是進程,什麼是線程?為什麼有多線程呢?多線程存在的意義有什麼什麼呢?線程的創建方式又有哪些?以及要理解多線程的特點等。 多線程和線程的理解 多線程?線程,多個線程,如何理解什麼是進程呢? 如圖: 任務管 ...
  • 最近在學習數據分析線性回歸演算法時,產生了很多疑問。作為初學者,我認為應該先從基本概念上進行一些深度理解。下麵將我的一些思考總結如下: 線性回歸模型為: (1) 其中ε是剩餘誤差,假設它服從的是高斯分佈,然後因此就將線性回歸模型和高斯模型聯合起來,獲取公式如下: 到這裡我是完全沒看懂! 對於線性回歸我 ...
  • 引入: 每次玩回合制游戲的時候,反反覆復的日常任務讓人不勝其煩 玩問道的時候,我們希望能夠自動刷道,玩夢幻希望能自動做師門、捉鬼等等 說明: 該外掛只能模擬滑鼠鍵盤操作,並不能修改游戲數據 我這裡使用的python2.7 開發工具是PyCharm 前期知識準備: 首先下載autopy包,我這裡PyC ...
  • 一個進程里只有一個線程,我們稱之為單線程爬蟲。單線程爬蟲每次只訪問一個頁面,不能充分利用電腦的網路帶寬。一個頁面最多也就幾百KB,所以爬蟲在爬取一個頁面的時候,多出來的網速就浪費掉了。 而如果我們可以讓爬蟲同時訪問10個頁面,就相當於我們的爬取速度提高了10倍。這個時候就需要使用多線程技術了。 這裡 ...
  • 1、Properties集合 2、序列化流與反序列化流 3、列印流 4、commons-IO ...
  • 用java中socket實現socket聊天 1, 什麼是socket Socket 是指網路套接字,什麼是套接字呢? 這是網路上一種端對端的網路協議,埠就是進程號,socket在網路中讓這兩個端通信形成埠直接通信,所以socket的參數可想而知就是兩端ip和埠號了; 再說在網路中,總要有人付 ...
  • 實例19:判斷正整數n的d進位表示形式是否是迴文數(順著看和倒著看相同的數)。 主要思路: 一種方法:將正整數n數轉換成d進位的數,逐個比較首尾對應數字,判斷是否為迴文數。 另一種方法:將正整數n數轉換成d進位的數,將低位數當做高位數,轉換成正整數判斷與原來的數是否相等。 書中採用的是第二種方法,下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...