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

Stella981
• 阅读 236

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Easter79 Easter79
2年前
SpringBoot项目使用多线程处理任务时无法通过@Autowired注入bean
  最近在做一个“温湿度控制”的项目,项目要求通过用户设定的温湿度数值和实时采集到的数值进行比对分析,因为数据的对比与分析是一个通过前端页面控制的定时任务,经理要求在用户开启定时任务时,单独开启一个线程进行数据的对比分析,并将采集到的温湿度数值存入数据库中的历史数据表,按照我们正常的逻辑应该是用户在请求开启定时任务时,前端页面通过调用后端接口,创建一个新的线
京东云开发者 京东云开发者
5个月前
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了