[springBoot]震惊!多线程中使用@Autowired居然会发生这样的事!
近期,在项目中需要用到多线程,而且需要读取配置文件中RocketMq的相关参数,于是乎自自然而然的将配置文件映射到实体类中(据说Spring不建议这样干?),重复三遍,以后具体查一下:
为什么Spring不建议将配置映射到实体类?
为什么Spring不建议将配置映射到实体类?
为什么Spring不建议将配置映射到实体类?
然后,自然而然的用@Autowired在多线程中注入这个实体类!就这样奇迹发生了…
在多线程中注入的实体类一直 空指针异常,而在test中却完全没问题,说明我的映射是成功了的,那,为啥?
经过一番与度娘的激烈讨论,原来如此:
在线程中为了安全,是防注入的
那,这岂不是很坑,不过为了安全,我忍了!
复盘一下我们的应用场景
假设application.properties中是这样的(yml文件同理):
user.name=aran
user.sex=male
user.age=18
user.job=coder
通过实体类进行配置文件映射:
package com.aran.test.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import lombok.Data;
@Data
@Configuration
@ConfigurationProperties(prefix = "user")
public class PropConfig {
private String name;
private String sex;
private String age;
private String job;
@Override
public String toString() {
return "PropConfig [name=" + name + ", sex=" + sex + ", age=" + age + ", job=" + job + "]";
}
}
简单写一个线程类:
package threads;
import org.springframework.beans.factory.annotation.Autowired;
import com.aran.test.config.PropConfig;
public class TestThreads implements Runnable{
@Autowired
PropConfig propConfig;
@Override
public void run() {
System.out.println("进入线程" + Thread.currentThread().getName());
System.out.println("==================开始读取参数========================");
System.out.println(propConfig.getName());
System.out.println(propConfig.getAge());
System.out.println("=======================参数读取完毕===================");
}
}
为了方便,我就直接在主类中启动一个线程:
package com.aran.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import threads.TestThreads;
@SpringBootApplication
public class AutowiredInRunnableApplication {
public static void main(String[] args) {
SpringApplication.run(AutowiredInRunnableApplication.class, args);
new Thread(new TestThreads()).start();
}
}
启动,结果如下:

果然,空指针异常,我们在测试类中试一下看:
@Autowired
PropConfig propConfig;
@Test
void getConfig() {
System.out.println("进入线程" + Thread.currentThread().getName());
System.out.println("==================开始读取参数========================");
System.out.println(propConfig.getName());
System.out.println(propConfig.getAge());
System.out.println("=======================参数读取完毕===================");
}

在测试类中,我们 成功拿到了参数,而在线程中,确实不可以
那怎么解决,据说有这样三种方法2:
1. 内部类
定义一个含有内部类的类:
package com.aran.test.byInnerClass;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.aran.test.config.PropConfig;
@Component
public class AnInnerClazz {
@Autowired
PropConfig propConfig;
public void excute() {
new Thread(new Worker()).start();
}
private class Worker implements Runnable{
@Override
public void run() {
System.out.println("我是内部类执行的Run方法!");
System.out.println("==========开始读取配置参数==========");
System.out.println("name:" + propConfig.getName());
System.out.println("age:" + propConfig.getAge());
System.out.println("==========结束读取配置参数==========");
}
}
}
此时,我们就可以通过在外部类中注入AnInnerClazz,便可以在Worker线程中获取配置参数:
比如,在Controller中:
package com.aran.test.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.aran.test.byInnerClass.AnInnerClazz;
import com.aran.test.config.PropConfig;
@RestController
public class TestController {
@Autowired
AnInnerClazz inner;
@GetMapping("/user")
public void getUser(){
inner.excute();
}
}
在test中测试一下:
package com.aran.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.aran.test.byInnerClass.AnInnerClazz;
import com.aran.test.config.PropConfig;
@SpringBootTest
class AutowiredInRunnableApplicationTests {
@Test
void contextLoads() {
}
@Autowired
PropConfig propConfig;
@Autowired
AnInnerClazz inner;
@Test
void getConfig() {
// System.out.println("进入线程" + Thread.currentThread().getName());
// System.out.println("==================开始读取参数========================");
// System.out.println(propConfig.getName());
// System.out.println(propConfig.getAge());
// System.out.println("=======================参数读取完毕===================");
//
inner.excute();
}
}
运行结果:

可以成功读取到配置文件的内容。
2. 构造函数
在线程中,通过构造函数,将实体类引入:
package com.aran.test.byConstructor;
import org.springframework.stereotype.Component;
import com.aran.test.config.PropConfig;
@Component
public class AnConstructor implements Runnable{
private PropConfig propconfig;
public AnConstructor(PropConfig propconfig) {
this.propconfig = propconfig;
}
@Override
public void run() {
System.out.println("通过构造函数注入:");
System.out.println("============开始读取配置文件=============");
System.out.println(propconfig.getName());
System.out.println(propconfig.getAge());
System.out.println("================结束读取配置文件=========");
}
}
测试一下:
package com.aran.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.aran.test.byConstructor.AnConstructor;
import com.aran.test.byInnerClass.AnInnerClazz;
import com.aran.test.config.PropConfig;
@SpringBootTest
class AutowiredInRunnableApplicationTests {
@Test
void contextLoads() {
}
@Autowired
PropConfig propConfig;
@Autowired
AnInnerClazz inner;
@Autowired
AnConstructor constr;
@Test
void getConfig() {
// System.out.println("进入线程" + Thread.currentThread().getName());
// System.out.println("==================开始读取参数========================");
// System.out.println(propConfig.getName());
// System.out.println(propConfig.getAge());
// System.out.println("=======================参数读取完毕===================");
//
// inner.excute();
new Thread(new AnConstructor(propConfig)).start();
}
}
运行结果:

同样成功读取到配置文件!
3. 全局Bean工具类
通过继承ApplicationContextAware,实现Bean的获取,即注入:
我们在文章一开始报错的线程中改动如下:
package com.aran.test.threads;
import org.springframework.beans.factory.annotation.Autowired;
import com.aran.test.config.PropConfig;
import com.aran.test.utils.ApplicationContextProvider;
public class TestThreads implements Runnable{
PropConfig propConfig;
@Override
public void run() {
//注意这句
this.propConfig = ApplicationContextProvider.getBean(PropConfig.class);
System.out.println("进入线程" + Thread.currentThread().getName());
System.out.println("==================开始读取参数========================");
System.out.println(propConfig.getName());
System.out.println(propConfig.getAge());
System.out.println("=======================参数读取完毕===================");
}
}
启动入口类:
package com.aran.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.aran.test.byConstructor.AnConstructor;
import com.aran.test.byInnerClass.AnInnerClazz;
import com.aran.test.threads.TestThreads;
@SpringBootApplication
public class AutowiredInRunnableApplication {
public static void main(String[] args) {
SpringApplication.run(AutowiredInRunnableApplication.class, args);
//在线程中使用Autowired
new Thread(new TestThreads()).start();
// new AnInnerClazz().excute();
// new AutowiredInRunnableApplication().inner.excute();
// new Thread(new AnConstructor(propConfig)).start();
}
}
运行结果:

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)