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

原文地址:https://www.cnblogs.com/xiaolong1996/p/9571645.html

时间: 2024-08-29 21:48:57

SpringBoot项目使用多线程处理任务时无法通过@Autowired注入bean的相关文章

使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上

一个用于构建和推送Docker镜像的Maven插件. 使用Maven插件构建Docker镜像,将Docker镜像push到DockerHub上,或者私有仓库,上一篇文章是手写Dockerfile,这篇文章借助开源插件docker-maven-plugin 进行操作 以下操作.默认你已经阅读过我上一篇文章: Docker 部署 SpringBoot 项目整合 Redis 镜像做访问计数Demo http://www.ymq.io/2018/01/11/Docker-deploy-spring-bo

记一次springboot项目,maven引发的悲剧(Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletCont)

maven下载大的项目的时候,jar包下载出错是常见的, 但是这种情况经常能看到,如java.lang.ClassNotFoundException这样的提示, 所以一直以来也觉得maven下载jar错误也没什么,直到使用了springboot 因为springboot是内嵌tomcat的,所以当这些关于Tomcat的jar包出错时,他提示的是:Unable to start EmbeddedWebApplicationContextdue to missing EmbeddedServletC

在云服务器上部署自己的SpringBoot项目

在云服务器上部署自己的SpringBoot项目 因为正在开发一个个人的微信小程序项目,所以开始学习如何部署SpringBoot服务器,之前完全没有接触过部署服务器相关,只能从0开始摸索,网上各种文章杂乱得很,而且大多都语焉不详,完全跟不上作者的脑回路,所以摸索得非常痛苦! 所幸最后成功了,记录一下完整的步骤,造福后来的跟我一样的小白们. 部署步骤 云服务器的购买和配置 安装JDK 安装Tomcat 安装svn 安装Maven 打包SpringBoot项目 设置安全组 安装MySQL 服务器的代码

springboot入门一,使用myEclipse新建一个springboot项目

1.环境信息 MyEclipse2015,jdk1.8,tomcat8 2.新建maven项目 2.1 新建一个web项目 2.2 生成的项目结构如下 3.配置pom.xml文件 3.1 pom.xml完整信息 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http

Docker化你的SpringBoot项目

容器和微服务可谓是一对好朋(ji)友(you),因为微服务架构下的业务服务通常都基于SpringBoot进行开发,上线部署服务的时候通过容器来进行部署,能够简化部署的过程,然后使用一些容器管理工具管理容器,例如k8s.rancher等.这样才能方便我们进行扩展.重建以及销毁服务等操作,最重要的是使用Docker部署项目会比传统的部署方式更简单,基本就几条命令的事,所以学会使用Docker部署SpringBoot项目显得尤为重要. 本文默认读者已经掌握docker的相关操作,如果你对docker相

SpringBoot(二)CentOS部署SpringBoot项目从0到1

在之前的博文<详解intellij idea搭建SpringBoot>介绍了idea搭建SpringBoot的详细过程, 并在<CentOS安装Tomcat>中介绍了Tomcat的安装,前面几篇文章实际上已经充分准备好了部署Linux的必要条件.那么今天来看看如何在CentOS部署SpringBoot,让你的SpringBoot在服务器上跑起来. vLinux部署springboot 从0到1,5步走,在Linux Tomcat部署springboot: 1. <CentOS

springboot项目启动成功后执行一段代码的两种方式

springboot项目启动成功后执行一段代码的两种方式 实现ApplicationRunner接口 package com.lnjecit.lifecycle; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; import org.sp

springboot项目打jar包

首先用eclips打包未遂, 第一次,export-java-JAR file,好像是打的包太大了,把所有的Lib都打进去了,而且还报错,找不到主方法. 第二次,也是同样的方式,只是把除了src的内容,都去掉了,打成的包与目标大小相近,但是依然找不到主方法. 具体的形容词忘记了,差不多是这么个表述. 之后用IDEA打包, 别人告诉我要用插件,我发现没有那个插件,于是我打开pom看了一下 <!--解决springboot项目打jar包运行时找不到main方法方案--> <plugin>

idea内springboot项目设置热部署

一.需求分析: 编写idea中编写项目时,经常性改某几行代码就需要重新启动项目,比较浪费时间,借助idea的热部署可以实现代码的热部署 二.实现经过 这边可以借助spring-boot-devtools模块进行配置,devtools会检测代码,并进行重新发布.devtools的实现原理是通过使用两个 ClassLoader,一个用来加载一些第三方的代码(如引入的一些jar包).另一个ClassCLoud会加载一些会更改的代码,可以称 为restart ClassLoader.在有代码进行更改的时