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

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

  最近在做一个“温湿度控制”的项目,项目要求通过用户设定的温湿度数值和实时采集到的数值进行比对分析,因为数据的对比与分析是一个通过前端页面控制的定时任务,经理要求在用户开启定时任务时,单独开启一个线程进行数据的对比分析,并将采集到的温湿度数值存入数据库中的历史数据表,按照我们正常的逻辑应该是用户在请求开启定时任务时,前端页面通过调用后端接口,创建一个新的线程来执行定时任务,然后在线程类中使用 @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;

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

  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.servicepl

  * @ClassName: MainPageServiceImpl

  * @Description: 主页面相关操作业务层实现类

  * @Author: wangzhilong

  * @CreateDate: 2021/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());

  }

  }

  如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

  重点,线程类代码,大家注意看,我在代码最开始使用了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: 2021/8/29 10:11

  * @Version: 1.0

  */

  public class TimingMonitoring implements Runnable{

  //历史数据业务层接口

  @Autowired

  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;

  }

  @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.stereotypeponent;

  /**

  * @ProjectName:

  * @Package: com.backstage.config

  * @ClassName: ApplicationContextProvider

  * @Description: 获取bean对象的工具类

  * @Author: wangzhilong

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

  * @Version: 1.0

  */

  /**

  * Author:ZhuShangJin

  * Date:2021/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

  * @return

  */

  public static T getBean(Class clazz) {

  return getApplicationContext().getBean(clazz);

  }

  /**

  * 通过name,以及Clazz返回指定的Bean

  *

  * @param name

  * @param clazz

  * @param

  * @return

  */

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

  }

  如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

  好了,至此呢,问题就得到解决了,文章中如错误或不足,请指出,不胜感激

目录
相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
246 0
|
1月前
|
存储 Java 数据库
如何处理线程池关闭时未完成的任务?
总之,处理线程池关闭时未完成的任务需要综合考虑多种因素,并根据实际情况选择合适的处理方式。通过合理的处理,可以最大程度地减少任务丢失和数据不一致等问题,确保系统的稳定运行和业务的顺利开展。
124 64
|
1月前
|
消息中间件 监控 Java
线程池关闭时未完成的任务如何保证数据的一致性?
保证线程池关闭时未完成任务的数据一致性需要综合运用多种方法和机制。通过备份与恢复、事务管理、任务状态记录与恢复、数据同步与协调、错误处理与补偿、监控与预警等手段的结合,以及结合具体业务场景进行分析和制定策略,能够最大程度地确保数据的一致性,保障系统的稳定运行和业务的顺利开展。同时,不断地优化和改进这些方法和机制,也是提高系统性能和可靠性的重要途径。
122 62
|
30天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
55 12
|
1月前
|
Java
SpringBoot构建Bean(RedisConfig + RestTemplateConfig)
SpringBoot构建Bean(RedisConfig + RestTemplateConfig)
47 2
|
1月前
|
前端开发 Java 数据格式
SpringBoot中定义Bean的几种方式
本文介绍了Spring Boot中定义Bean的多种方式,包括使用@Component、@Bean、@Configuration、@Import等注解及Java配置类。每种方式适用于不同的场景,帮助开发者高效管理和组织应用组件。
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
2月前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
87 5
|
2月前
|
Java Spring 容器
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
这篇文章讨论了在Spring Boot 3.2.1版本中,同名同类型的bean和@Service注解类之间冲突的问题得到了解决,之前版本中同名bean会相互覆盖,但不会在启动时报错,而在配置文件中设置`spring.main.allow-bean-definition-overriding=true`可以解决这个问题。
114 0
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
|
2月前
|
Java Shell C++
Springboot加载注入bean的方式
本文详细介绍了Spring Boot中Bean的装配方法。首先讲解了使用@Component、@Service、@Controller、@Repository等注解声明Bean的方式,并解释了这些注解之间的关系及各自适用的层次。接着介绍了通过@Configuration和@Bean注解定义Bean的方法,展示了其灵活性和定制能力。最后讨论了@Component与@Bean的区别,并提供了在Spring Boot应用中装配依赖包中Bean的三种方法:使用@ComponentScan注解扫描指定包、使用@Import注解导入特定Bean以及在spring.factories文件中配置Bean。