a525 发表于 2020-10-27 10:53

解决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:08

本帖最后由 lvbuqing 于 2020-10-27 11:09 编辑

在一个没有被spring管理的类中 使用spring的bean是不可以的。

还有,你这跟多线程有啥子关系··············

张海洋 发表于 2020-10-27 11:12

使用@Component注解可以把类交给spring管理,也可以使用@Bean注解转交方法,看场景是要把sdk注入进去吗?用一些工具类也不是不行

smdzj 发表于 2020-10-27 11:30

说的简单点是多线程下是不可以直接注入spring的。如果在多线程下需要注入spring组件。你可以先制作一个静态方法工具类之后获取spring组件。还有个建议是楼主的贴的代码看的不是很清楚,符号转义看的很费劲啊,感觉可以贴图

猎祖猎宗 发表于 2020-10-27 12:26

还是没看懂和多线程有什么关系
页: [1]
查看完整版本: 解决Springboot多线程场景下无法使用@Autowired/@Resource注入的问题