我们在spring 或 springboot 的 websocket 里面使用 @Autowired 注入 service 或 bean 时,会报空指针异常,获取的service 为 null,并不是service 不能被注入。
本质原因:spring管理的都是单例(singleton),和 websocket (多对象)相冲突。
详细解释:项目启动时初始化,会初始化 websocket (非用户连接的),spring 同时会为其注入 service,该对象的 service 不是 null,被成功注入。但是,由于 spring 默认管理的是单例,所以只会注入一次 service。当新用户进入聊天时,系统又会创建一个新的 websocket 对象,这时矛盾出现了:spring 管理的都是单例,不会给第二个 websocket 对象注入 service,所以导致只要是用户连接创建的 websocket 对象,都不能再注入了。像 controller 里面有 service, service 里面有 dao。因为 controller,service ,dao 都有是单例,所以注入时不会报 null。但是 websocket 不是单例,所以使用spring注入一次后,后面的对象就不会再注入了,会报null。
下面我们可以通过三种方式来解决无法注入的问题
一、 使用applicationContext获取
@Component
@ServerEndpoint(value = "/loggings")
//注意这里要实现ApplicationContextAware的接口,才能使applicationContext生效,要不到时候获取不到
public class LoggingWebConfig implements ApplicationContextAware{
//此处是解决无法注入的关键
private static ApplicationContext applicationContext;
//引入自己的接口类
private MobileUserService mobileUserService;
//applicationContext的set方法
public void setApplicationContext(ApplicationContext applicationContext) {
LoggingWebConfig.applicationContext = applicationContext;
}
//然后通过applicationContext.getBean()的方法即可获取到对应的接口类了
applicationContext.getBean(MobileUserService.class)
}
二、使用@Autowired set方法注入,这种方法无需实现ApplicationContextAware接口
@Component
@ServerEndpoint(value = "/loggings")
public class LoggingWebConfig{
//引入自己的接口类,注意要加上static 静态修饰
private static MobileUserService mobileUserService;
//mobileUserService的set方法
@Autowired
public void setApplicationContext(MobileUserService mobileUserService) {
LoggingWebConfig.mobileUserService= mobileUserService;
}
//然后就可以直接用引入的mobileUserService接口,例如mobileUserService.list()即可获取到相应的数据
}
三、使用配置文件,通过BeanFactory.getBean()来获取
1.新建配置文件MyEndpointConfig
public class MyEndpointConfig extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
//此处是解决无法注入的关键
private static volatile BeanFactory context;
@Override
public <T> T getEndpointInstance(Class<T> clazz){
return context.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
MyEndpointConfig.context = applicationContext;
}
}
2.引入配置文件
@Component
//使用configurator = MyEndpointConfig.class的方式引入配置文件
@ServerEndpoint(value = "/loggings",configurator = MyEndpointConfig.class)
public class LoggingWebConfig{
//此方式可以用@Autowired将接口类直接注入进去
@Autowired
MobileUserService mobileUserService;
//然后就可以直接用引入的mobileUserService接口,例如mobileUserService.list()即可获取到相应的数据
}
四、新建WebSocketConfig配置文件
在WebSocketConfig配置文件中注入配置文件MyEndpointConfig的bean,要不MyEndpointConfig配置文件无法生效,不用MyEndpointConfig配置方式的解决问题的可以不用配置这个bean,只要有ServerEndpointExporter注解扫描就可以了
@Configuration
public class WebSocketConfig {
/**
* 用途:扫描并注册所有携带@ServerEndpoint注解的实例。 @ServerEndpoint("/loggings")
* 如果使用外部容器 则无需提供ServerEndpointExporter。
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 支持注入其他接口类
*/
@Bean
public MyEndpointConfig newMyEndpointConfigure (){
return new MyEndpointConfig ();
}
}