解决Springboot多线程场景下无法使用@Autowired/@Resource注入的问题
以前总觉得离多线程还很远,自己也基本没接触过多线程和高并发,只是一个初中级程序员而已。但是在这次包装第三方SDK的时候却遇到了多线程下无法@Autowired注入的问题。好记性不如烂笔头。 事情是这样的,工作需要使用websocket+stomp包装RFID扫描器的SDK给前端使用,往前端发消息时需要用到**SimpMessagingTemplate**,这个(org.springframework.messaging.simp.SimpMessagingTemplate;)类使用@Autowired注入为NULL。nmmp~~
花开两头,各表一枝。且听我将遇到的问题一一道来。
问题①:(STOMP中)@MessageMapping和@SendTo必须同时使用,一个意为前端请求,一个为前端接收(订阅)。不懂STOMP的可以百度一下,就知道。**@SendTo**不可单独使用,这也是我为什么要注入**SimpMessagingTemplate**的原因。
```java
/**
* 连接扫描器
*/
@MessageMapping("/connect")
@SendTo("/scanner/connect")
public boolean connect(Connect connect){
if (reader != null){
return true;
}
CallBackR2000 cb = new CallBackR2000();
cb.SetContext(this);
reader = service.connect(connect.getName(), connect.getCode(), cb);
System.out.println("扫描器连接成功!");
return true;
}
```
问题②:Springboot多线程使用@Autowired/@Resource注入为NULL,即:
```java
@resource//@Autowired 也一样
private SimpMessagingTemplate messagingTemplate; //注入
/**
* 读卡回调
*/
public void rfid(String s) throws Exception {
Thread.sleep(200);
System.out.println(s);
messagingTemplate.convertAndSend("/scanner/getId",s); //使用此处messagingTemplate对象为NULL
}
```
百度后解决方法:https://blog.csdn.net/sunjinjuan/article/details/87255551
百度给的解决方法大致为:通过一个获取上下文的工具类,获取所有上下文的bean,然后在使用时通过工具类的get方法,使用class获取到对应的bean对象。
**工具类:**
```java
/**
* @Description: 获取bean对象的工具类
*
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
if (applicationContext == null){
throw new RuntimeException("applicationContext注入失败");
}
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
if (applicationContext == null){
throw new RuntimeException("applicationContext注入失败");
}
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
if (applicationContext == null){
throw new RuntimeException("applicationContext注入失败");
}
return getApplicationContext().getBean(name, clazz);
}
}
```
**使用方法:**
```java
@Resource
private SimpMessagingTemplate messagingTemplate;
public StompSocket(){
//通过工具类赋值
this.messagingTemplate = SpringContextUtil.getBean(SimpMessagingTemplate.class);
}
```
使用这种方法可能会报错,找不到SpringContextUtil.getBean方法,需要使用@DependsOn注解,这也是遇到错误通过百度解决的。
```java
@RestController
@DependsOn("springContextUtil") //加载上下文 bean
public class StompSocket {
private static final ReaderR2000ServiceImpl service = new ReaderR2000ServiceImpl();
//扫描器对象
private ReaderR2000 reader;
@Resource
private SimpMessagingTemplate messagingTemplate;
public StompSocket(){
this.messagingTemplate = SpringContextUtil.getBean(SimpMessagingTemplate.class);
}
```
以上,即是通过百度得到的解决方法,通过测试可以解决Springboot多线程场景下无法使用@Autowired/@Resource注入的问题。
但是。。。。开发组长看了代码后,嫌弃代码在拉杂,命名两句代码的事儿,还要搞工具类,搞这搞那。。。。我是真的菜!
接下来,了解什么叫**闭包和变量提升**,抱歉是我当粗没认真学习的下场.....
controller层,类名(**StompSocket**)
```java
@RestController
//@DependsOn("springContextUtil") //加载上下文 bean
public class StompSocket {
private static final ReaderR2000ServiceImpl service = new ReaderR2000ServiceImpl();
//扫描器对象
private ReaderR2000 reader;
@Resource
private SimpMessagingTemplate messagingTemplate; //重点关注这个
/*
public StompSocket(){
this.messagingTemplate = SpringContextUtil.getBean(SimpMessagingTemplate.class);
}
*/
/**
* 连接扫描器
*/
@MessageMapping("/connect")
@SendTo("/scanner/connect")
public boolean connect(Connect connect){
if (reader != null){
return true;
}
//方法初始化时,会将当前类对象传入到CallbackR2000,当前类初始化时SimpMessagingTemplate是存在的
//文末解释为什么存在,为什么不存在
CallBackR2000 cb = new CallBackR2000();
cb.SetContext(this);
reader = service.connect(connect.getName(), connect.getCode(), cb);//cb是需要这个类为参数
System.out.println("扫描器连接成功!");
return true;
}
/**
* 读卡回调
*/
public void rfid(String s) throws Exception {
Thread.sleep(200);
System.out.println(s);
messagingTemplate.convertAndSend("/scanner/getId",s);
}
```
**CallbackR2000**
```java
public class CallBackR2000 implements CallBack.R2000 {
StompSocket stompSocket = null;
public void SetContext(StompSocket ss){
stompSocket = ss;
}
@SneakyThrows
@Override
public void readData(String data, String rssi, String antennaNo, String deviceNo, String direction,String communicationMode) {
//这是之前手动创建的StompSocket对象,调用读卡回调
//StompSocket stompSocket = new StompSocket();
//stompSocket.rfid(data);
stompSocket.rfid(data); //调用controller层的读卡回调方法
}
```
这就是一个变量提升和闭包的应用场景。。。我也没搞懂是啥意思,但是看代码多少还是能够理解其中意思的。手动new的StompSocket和传进来的StompSocket对象是不一样的,@Autowired只在spring框架初始化时(也就是项目启动时)才能扫描到,那个时候SimpMessagingTemplate时有值的,所以我传进来是可行的。但是我在CallbackR2000类中手动New时,是不会注入SimpMessagingTemplate对象的。大概就是这样。
这样的写法也是学到了,遇到异常一定要弄清楚异常出现的原理,懂了原理就可以更简单的解决问题。这种方法明显代码量减少,看上去也比较高大上。
**解释为什么SimpMessagingTemplate对象@Autowired不进来**
@Autowired注解是springboot项目在启动时会扫描到,然后根据类型注入进来。此时上下文中是存在SimpMessagingTemplate对象的,但是当我在CallbackR2000类中想要调用stompSocket.rfid()时,通过newStompSocket()产生的是一个新的对象,新对象并没有通过@autowired注入SimpMessagingTemplate对象,所以SimpMessagingTemplate为NULL。
关于概念的理解还不是特别通透,如有错误还请指正。
本帖最后由 lvbuqing 于 2020-10-27 11:09 编辑
在一个没有被spring管理的类中 使用spring的bean是不可以的。
还有,你这跟多线程有啥子关系·············· 使用@Component注解可以把类交给spring管理,也可以使用@Bean注解转交方法,看场景是要把sdk注入进去吗?用一些工具类也不是不行 说的简单点是多线程下是不可以直接注入spring的。如果在多线程下需要注入spring组件。你可以先制作一个静态方法工具类之后获取spring组件。还有个建议是楼主的贴的代码看的不是很清楚,符号转义看的很费劲啊,感觉可以贴图 还是没看懂和多线程有什么关系
页:
[1]