解決SpringBoot項(xiàng)目使用多線程處理任務(wù)時(shí)無(wú)法通過(guò)@Autowired注入bean問(wèn)題
最近在做一個(gè)“溫濕度控制”的項(xiàng)目,項(xiàng)目要求通過(guò)用戶設(shè)定的溫濕度數(shù)值和實(shí)時(shí)采集到的數(shù)值進(jìn)行比對(duì)分析,因?yàn)閿?shù)據(jù)的對(duì)比與分析是一個(gè)通過(guò)前端頁(yè)面控制的定時(shí)任務(wù),經(jīng)理要求在用戶開(kāi)啟定時(shí)任務(wù)時(shí),單獨(dú)開(kāi)啟一個(gè)線程進(jìn)行數(shù)據(jù)的對(duì)比分析,并將采集到的溫濕度數(shù)值存入數(shù)據(jù)庫(kù)中的歷史數(shù)據(jù)表,按照我們正常的邏輯應(yīng)該是用戶在請(qǐng)求開(kāi)啟定時(shí)任務(wù)時(shí),前端頁(yè)面通過(guò)調(diào)用后端接口,創(chuàng)建一個(gè)新的線程來(lái)執(zhí)行定時(shí)任務(wù),然后在線程類中使用 @Autowired 注解注入保存歷史數(shù)據(jù)的service層,在線程類中調(diào)用service層保存歷史數(shù)據(jù)的方法實(shí)現(xiàn)溫濕度數(shù)據(jù)的保存,這時(shí)就出現(xiàn)了一個(gè)很尷尬的問(wèn)題,在新開(kāi)啟的線程中使用 @Autowired 注解無(wú)法注入需要的bean(即:保存歷史數(shù)據(jù)的service層),程序一直在報(bào) NullPointerException 。
這是controller層,方法 startExperiment 和 stopExperiment 分別是開(kāi)始定時(shí)任務(wù)和停止定時(shí)任務(wù)的方法,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: 主頁(yè)面相關(guān)操作控制器
* @Author: wangzhilong
* @CreateDate: 2018/8/29 9:49
* @Version: 1.0
*/
@RestController
@RequestMapping("/main")
public class MainPageController {
@Autowired
private MainPageService mainPageService;
/**
* 開(kāi)始實(shí)驗(yàn)
*
* @param threshold
*/
@RequestMapping("/startExperiment")
public JsonResponse startExperiment(HttpServletRequest request, Threshold threshold) {
return mainPageService.startExperiment(request, threshold);
}
/**
* 停止實(shí)驗(yàn)
*/
@RequestMapping("/stopExperiment")
public JsonResponse stopExperiment() {
return mainPageService.stopExperiment();
}
/**
* 獲取實(shí)時(shí)數(shù)據(jù)
*
* @return
*/
@RequestMapping("/getData")
public JSONObject getData() {
return null;
}
}
service 層接口代碼,沒(méi)什么好說(shuō)的,直接上代碼:
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: 主頁(yè)面相關(guān)操作業(yè)務(wù)層接口
* @Author: wangzhilong
* @CreateDate: 2018/8/29 9:51
* @Version: 1.0
*/
public interface MainPageService {
/**
* 開(kāi)始實(shí)驗(yàn)
*
* @param threshold
*/
JsonResponse startExperiment(HttpServletRequest request, Threshold threshold);
/**
* 停止實(shí)驗(yàn)
*/
JsonResponse stopExperiment();
/**
* 獲取實(shí)時(shí)數(shù)據(jù)
*
* @return
*/
JSONObject getData();
}
service 層實(shí)現(xiàn)類代碼,關(guān)于springboot項(xiàng)目使用多線程進(jìn)行業(yè)務(wù)處理不屬于本章節(jié)的討論范圍,如有需要,請(qǐng)留言,我會(huì)在看到留言后第一時(shí)間更新相關(guān)技術(shù)文章,由于這里刪除了一些與本章節(jié)無(wú)關(guān)的代碼,如果復(fù)制到開(kāi)發(fā)工具內(nèi)有報(bào)錯(cuò)問(wèn)題,麻煩大家提醒我一下,以便修改,非常感謝
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: 主頁(yè)面相關(guān)操作業(yè)務(wù)層實(shí)現(xiàn)類
* @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();
}
/**
* 開(kāi)始實(shí)驗(yàn)
*
* @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) {
//設(shè)置定時(shí)任務(wù)的執(zhí)行時(shí)間為3秒鐘執(zhí)行一次
return new CronTrigger("0/10 * * * * ?").nextExecutionTime(triggerContext);
}
});
return new JsonResponse(0,"開(kāi)始實(shí)驗(yàn)!");
}
/**
* 停止實(shí)驗(yàn)
*/
@Override
public JsonResponse stopExperiment() {
if (future2 != null) {
experimentService.upd(getTime());
future2.cancel(true);
}
return new JsonResponse(0,"結(jié)束實(shí)驗(yàn)!");
}
/**
* 獲取實(shí)時(shí)數(shù)據(jù)
*
* @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());
}
}
重點(diǎn),線程類代碼,大家注意看,我在代碼最開(kāi)始使用了spring的 @Autowired 注解注入需要的service,可在調(diào)用service中的add方法時(shí),程序報(bào)空指針異常,一直認(rèn)為是add方法或者sql語(yǔ)句有問(wèn)題,找了一上午,也沒(méi)發(fā)現(xiàn)任何問(wèn)題,后來(lái)單獨(dú)調(diào)用這個(gè)add方法是可以正常插入數(shù)據(jù)的,唯獨(dú)在這個(gè)線程類中調(diào)用時(shí)報(bào)錯(cuò),感覺(jué)和線程有莫大的關(guān)系,百度一搜,還真找到了,原來(lái),在線程中為了線程安全,是防注入的,沒(méi)辦法,要用到這個(gè)類啊。只能從bean工廠里拿個(gè)實(shí)例了,繼續(xù)往下看
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: 定時(shí)監(jiān)測(cè)溫(濕)度 數(shù)據(jù)
* @Author: wangzhilong
* @CreateDate: 2018/8/29 10:11
* @Version: 1.0
*/
public class TimingMonitoring implements Runnable{
//歷史數(shù)據(jù)業(yè)務(wù)層接口
@Autowired
public DetailedDataService detailedDataService;
private Threshold threshold; //閾值實(shí)體類
private List<ValveValue> settingData; //設(shè)定的溫濕度數(shù)據(jù)
private Integer id; //實(shí)驗(yàn)記錄id
private Integer dataId; //歷史數(shù)據(jù)主表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獲取到的數(shù)據(jù)
String data = "001,50.5,002,37,003,45.6,004,40,005,55.2,006,58";
if (data == null || data.trim() == "") {
return; //若獲取到的數(shù)據(jù)為空,則直接停止該方法的執(zhí)行
}
double temperature = 0.0; //溫度
double humidity = 0.0; //濕度
Integer type = null; //數(shù)據(jù)類型,1是溫度,2是濕度
//解析數(shù)據(jù),并將數(shù)據(jù)保存到歷史數(shù)據(jù)數(shù)據(jù)庫(kù)
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));
}
}
}
/**
* 獲取當(dāng)前時(shí)間,精確到毫秒
* @return
*/
protected String getTime() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS");
return format.format(new Date());
}
}
獲取bean對(duì)象的工具類,既然程序無(wú)法通過(guò)注解拿到需要的bean,那就只好自己寫個(gè)工具類來(lái)獲取嘍,下面是工具類代碼
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對(duì)象的工具類
* @Author: wangzhilong
* @CreateDate: 2018/8/31 13:26
* @Version: 1.0
*/
/**
* Author:ZhuShangJin
* Date:2018/7/3
*/
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
/**
* 上下文對(duì)象實(shí)例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 獲取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通過(guò)name獲取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通過(guò)class獲取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通過(guò)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);
}
}
這樣呢,就可以在線程類中寫一個(gè)無(wú)參的構(gòu)造方法,在構(gòu)造方法中,通過(guò)調(diào)用工具類中的 getBean() 方法就可以拿到實(shí)例了,程序在調(diào)用這個(gè)線程類時(shí),會(huì)自動(dòng)調(diào)用其無(wú)參的構(gòu)造方法,在構(gòu)造方法中我們將需要的bean對(duì)象注入,然后就可以正常使用了,下邊是線程類修改后的代碼,由于別的地方?jīng)]有改動(dòng),所以這里只給大家改動(dòng)的代碼,省得大家看到一大堆代碼頭疼。
public TimingMonitoring() {
//new的時(shí)候注入需要的bean
this.detailedDataService = ApplicationContextProvider.getBean(DetailedDataService.class);
}
總結(jié)
以上所述是小編給大家介紹的SpringBoot項(xiàng)目使用多線程處理任務(wù)時(shí)無(wú)法通過(guò)@Autowired注入bean 問(wèn)題,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!
相關(guān)文章
SpringBoot 使用WebSocket功能(實(shí)現(xiàn)步驟)
本文通過(guò)詳細(xì)步驟介紹了SpringBoot 使用WebSocket功能,首先需要導(dǎo)入WebSocket坐標(biāo),編寫WebSocket配置類,用于注冊(cè)WebSocket的Bean,結(jié)合示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-02-02
Springboot如何讀取resources下的json配置文件
這篇文章主要介紹了Springboot如何讀取resources下的json配置文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Springboot設(shè)置統(tǒng)一的返回格式的方法步驟
在我們應(yīng)用中我們通常與前端交互使用json格式,設(shè)置統(tǒng)一的返回json 格式是非常必要的,本文主要介紹了Springboot設(shè)置統(tǒng)一的返回格式的方法步驟,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Java獲取視頻時(shí)長(zhǎng)及截取幀截圖詳解
這篇文章主要介紹了Java獲取視頻時(shí)長(zhǎng)及截取幀截圖詳解,以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。,需要的朋友可以參考下2019-06-06
ssm框架Springmvc文件上傳實(shí)現(xiàn)代碼詳解
這篇文章主要介紹了ssm框架Springmvc文件上傳實(shí)現(xiàn)代碼詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
springboot?使用clickhouse實(shí)時(shí)大數(shù)據(jù)分析引擎(使用方式)
這篇文章主要介紹了springboot?使用clickhouse實(shí)時(shí)大數(shù)據(jù)分析引擎的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-02-02
搭建Springboot框架并添加JPA和Gradle組件的方法
這篇文章主要介紹了搭建Springboot框架并添加JPA和Gradle組件的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07

