近期,在项目中需要用到多线程,而且需要读取配置文件中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();
	}

}

运行结果:
在这里插入图片描述

Logo

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

更多推荐