解决SpringBoot项目使用多线程处理任务时无法通过@Autowired注入bean问题
作者:Bug开发工程师 发布时间:2022-06-25 06:48:17
最近在做一个“温湿度控制”的项目,项目要求通过用户设定的温湿度数值和实时采集到的数值进行比对分析,因为数据的对比与分析是一个通过前端页面控制的定时任务,经理要求在用户开启定时任务时,单独开启一个线程进行数据的对比分析,并将采集到的温湿度数值存入数据库中的历史数据表,按照我们正常的逻辑应该是用户在请求开启定时任务时,前端页面通过调用后端接口,创建一个新的线程来执行定时任务,然后在线程类中使用 @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 问题,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!
来源:https://www.cnblogs.com/xiaolong1996/archive/2018/09/01/9571645.html


猜你喜欢
- 前言:函数式编程是一种编程范式,其中程序是通过应用和组合函数来构造的。它是一种声明式编程范式,其中函数定义是表达式树,每个表达式树返回一个值
- 本文实例为大家分享了unity使用socket实现聊天室的具体代码,供大家参考,具体内容如下unity聊天室服务端实现using Syste
- 本文实例为大家分享了Spring AOP实现记录操作日志的具体代码,供大家参考,具体内容如下1 添加maven依赖<dependenc
- Android开发中,当一个页面存放的控件超出屏幕时,通常需要使用ScrollView来包裹布局。这样用户可以通过手指的滑动来查看超出屏幕的
- 本文实例讲述了C#使用Dispose模式实现手动对资源的释放。分享给大家供大家参考。具体实现方法如下://单一类的实现class MyCla
- 什么是tcpTcp通信有两个特点分别是面向连接,具有可靠性.面向连接:指的是客户端与服务端之间的连接,在通信之前会有三次握手的机制来确保连接
- 本文实例讲述了Java编程实现汉字按字母顺序排序的方法。分享给大家供大家参考,具体如下:String[] str0 = new String
- java 中clone()的使用方法前言:clone就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象
- 方法一:Handler+Threadpackage com.xunfang.handerDemo; import android.app.A
- 关于Retrofit的学习,我算是比较晚的了,而现在Retrofit已经是Android非常流行的网络请求框架了。之前,我没有学过Retro
- OkHttp 提供了对用户认证的支持。当 HTTP 响应的状态代码是 401 时,OkHttp 会从设置的 Authenticator 对象
- 本文实例讲述了Android编程中HTTP服务用法。分享给大家供大家参考,具体如下:在Android中,除了使用java.net包下的API
- Knife4j就相当于是swagger的升级版,对于我来说,它比swagger要好用得多1、在pom.xml引入依赖包<!-- Swa
- 有哪些“纪律”是Java程序员所要遵守的?1. 为代码添加注释(Add comments to your code). – 每个人都知道这一
- DataSet 对象是支持 ADO.NET的断开式、分布式数据方案的核心对象 ,用途非常广泛.我们很多时候需要使用其中的数据,比如取得一个D
- 在实际应用中,我们往往有需要比较两个自定义对象大小的地方。而这些自定义对象的比较,就不像简单的整型数据那么简单,它们往往包含有许多的属性,我
- 本示例采用基姆拉尔森计算公式来根据日期计算未来日子是星期几:首先看下百度百科的基姆拉尔森计算公式定义:基姆拉尔森计算公式W= (d+2*m+
- 在进行持久层数据维护(新增或修改)的时候,我们通常需要记录一些非业务字段,比如:create_time、update_time、update
- 前言我们在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理。session的处理有很多种方法,详情见转载的上篇
- android 点击EditText始终不弹出软件键盘场景描述:正常情况下,当点击EditText时,软键盘会弹出来。现在的要求