【转载】spring springboot websocket 不能注入( @Autowired ) service bean 报 null 错误

SpringBoot WebSocket与Service注入
探讨SpringBoot项目中,WebSocket如何正确注入Service层避免null问题,解析多对象特性与单例管理冲突,提供两种有效解决方案。

最近项目要实现一个需求,springboot使用websocket定时给前端返回数据,我使用@Autiwired注解注入service服务类,但是运行起来service类为null,在网上查了一下,发现在@ServerEndPoint中是无法使用@Autowired注入Bean的。原理大致如下:

其实在项目启动的时候,这个MyWebSocket已经被注入了这个MyService的Bean,但是WebSocket的特点就是每建立一个连接,会生成一个新的MyWebSocket对象,网上有人分析过源码,也可以理解成new了一个WebSocket,这样当然是不能获得自动注入的对象了。
后来在网上看到有网友按照如下的代码,解决了此问题,

大概就是获取到Spring容器,然后去进行手动注入。

因为新生成的MyWebSocket对象根本不存在与Spring容器中,所以传统的ContextLoader.getCurrentWebApplicationContext()自然也就无法使用。

我用到了ApplicationContextAware来在项目加载时就获得项目的Spring容器。代码如下

@Component
@Lazy(false)
public class MyApplicationContextAware implements ApplicationContextAware {
 
    private static ApplicationContext APPLICATION_CONTEXT;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        APPLICATION_CONTEXT = applicationContext;
    }
 
    public static ApplicationContext getApplicationContext() {
        return APPLICATION_CONTEXT;
    }
}
这样我们就可以在任何类中去拿到这个类的ApplicationContext

通过这个ApplicationContext我们就可以在新生成一个WebSocket对象时来手动注入Service层了

@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {
 
    private static ConcurrentHashMap<String, MyWebSocket> map = new ConcurrentHashMap<>();
 
    private Session session;
 
    private MyService myServiceImpl = (MyService) MyApplicationContextAware.getApplicationContext().getBean("myServiceImpl");
但是我按照此代码,springboot启动报错了,后来又看到一个网友有个更简单的方式能解决此问题,代码如下:

解决方法:将要注入的 service 改成 static,就不会为null了。
参考代码:

@Controller
@ServerEndpoint(value="/chatSocket")
public class ChatSocket {
    //  这里使用静态,让 service 属于类
    private static ChatService chatService;

    // 注入的时候,给类的 service 注入
    @Autowired
    public void setChatService(ChatService chatService) {
        ChatSocket.chatService = chatService;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
本质原因: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。

参考:http://www.fengyunxiao.cn
参考:https://www.fengyunxiao.cn/blogs
--------------------- 
作者:亦寒2017 
来源:优快云 
原文:https://blog.youkuaiyun.com/m0_37202351/article/details/86255132 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

 

<think>首先,用户的问题是关于在Spring Boot应用中整合WebSocket时,如何正确注入相关的Service,解决自动注入失败的问题。具体来说,他们遇到了WebSocket自动注入Service失败的问题。 我需要参考提供的引用,这些引用描述了类似的问题和解决方案: - 引用[1]:解释了问题原因:Spring默认为单例,而WebSocket是多对象的,每次连接会产生不同的对象。Spring只在服务器启动时注入一次,但客户端连接时创建的新WebSocket对象没有注入Service,导致空指针。 - 引用[2]:给出了一个解决方案:使用static关键字对要注入的对象进行静态化处理,并通过一个方法注入。示例代码中,使用@Autowired注解一个setter方法,将Service设置为静态变量。 - 引用[3]:提到在@ServerEndpoint下添加一个静态的ApplicationContext,然后使用它来获取Service- 引用[4]:类似地,解释原因并提供了实验代码。 - 引用[5]:提到了WebSocketConfig配置类中的configureClientInboundChannel方法,但内容不完整。 我的回答需要: 1. 使用中文回答。 2. 结构清晰,帮助用户逐步解决问题。 3. 保证回答真实可靠,基于提供的引用。 4. 在行内数学表达式使用$...$格式(但这个问题可能不涉及数学表达式,所以我可以忽略或确保没有数学内容)。 5. 独立公式使用$$...$$(同样,可能不适用)。 6. 生成相关问题(在回答末尾)。 7. 在引用的段落末尾自然地添加引用标识,如[^1]。 回答结构: - 先解释问题原因。 - 然后提供解决方案。 - 最后生成相关问题。 步骤: 1. **解释问题原因**:基于引用[1]和[4],说明Spring的默认行为(单例)与WebSocket的多对象特性冲突,导致注入失败。 2. **提供解决方案**:基于引用[2]和[3],给出具体方法: - 方法1:使用静态变量和setter方法注入(如引用[2])。 - 方法2:使用ApplicationContext手动获取Bean(如引用[3])。 3. **确保可靠性**:引用来源,如[^1]、[^2]等。 4. **生成相关问题**:在末尾,添加相关的问题列表。 最终回答框架: - 开头:确认问题并简述原因。 - 主体:详细解释原因和解决方案。 - 结尾:总结并生成相关问题。</think>### Spring Boot WebSocket 自动注入 Service 失败解决方案 在 Spring Boot 应用中整合 WebSocket 时,自动注入 Service 失败是一个常见问题。这通常是由于 Spring 的默认行为与 WebSocket 的多对象特性冲突导致的。下面我将逐步解释原因,并提供可靠的解决方案,帮助您彻底解决此问题。 #### 问题原因 Spring 框架默认采用单例模式管理 Bean,即在整个应用生命周期中只创建一个实例。然而,WebSocket 是多对象的:每次客户端连接时,Spring 会创建一个新的 WebSocket 端点对象(例如,标注了 `@ServerEndpoint` 的类)。Spring 只在服务器启动时为初始 Bean 注入依赖,但新创建的 WebSocket 对象不会自动注入 Service,导致 `@Autowired` 或 `@Resource` 注解的 Service 字段为 `null`,从而引发空指针异常(NullPointerException)[^1]。引用中的实验代码也验证了这一点:连接时创建的新对象未注入 Service[^4]。 #### 解决方案 为了解决自动注入失败的问题,您需要手动管理 Service注入。以下是两种经过验证的可靠方法,基于引用中的最佳实践。确保您的项目中已添加 WebSocket 依赖(如 `spring-boot-starter-websocket`)。 ##### 方法1:使用静态变量和 setter 方法注入(推荐) 这种方法通过静态变量将 Service 绑定到类级别,然后使用 setter 方法实现注入Spring 会在启动时调用 setter 方法,确保所有 WebSocket 实例共享同一个 Service 实例。 1. **修改 WebSocket 端点类**:在您的 `@ServerEndpoint` 类中添加静态变量和注入方法。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.websocket.server.ServerEndpoint; @Component @ServerEndpoint("/ws/{sid}") // 根据您的路径调整 public class WebSocketServer { // 声明静态变量,使 Service 属于类而非实例 private static YourService yourService; // 使用 @Autowired 注入 Service 到静态变量 @Autowired public void setYourService(YourService yourService) { WebSocketServer.yourService = yourService; // 将注入Service 赋值给静态变量 } // WebSocket 方法示例(如 @OnOpen, @OnMessage) @OnOpen public void onOpen(Session session, @PathParam("sid") String sid) { // 使用 yourService 调用方法,例如:yourService.doSomething(); // 由于 yourService 是静态的,所有实例共享,不会空指针 } } ``` - **优点**:简单高效,无需额外配置,Spring 自动处理注入[^2]。 - **注意事项**:确保 `YourService` 是 Spring 管理的 Bean(例如,使用 `@Service` 注解)。 ##### 方法2:使用 ApplicationContext 手动获取 Bean 如果方法1不适用,您可以通过 Spring 的 `ApplicationContext` 在运行时手动获取 Service。这需要在 WebSocket 类中保存一个静态的 ApplicationContext 引用。 1. **添加 ApplicationContext 工具类**:创建一个辅助类来存储 ApplicationContext。 ```java import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextProvider implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public static ApplicationContext getContext() { return context; } } ``` 2. **修改 WebSocket 端点类**:在 WebSocket 类中使用 ApplicationContext 获取 Service。 ```java import org.springframework.context.ApplicationContext; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/ws/{sid}") public class WebSocketServer { private YourService yourService; // 在连接方法中手动获取 Service @OnOpen public void onOpen(Session session, @PathParam("sid") String sid) { ApplicationContext context = ApplicationContextProvider.getContext(); yourService = context.getBean(YourService.class); // 手动注入 // 使用 yourService 调用方法 } } ``` - **优点**:灵活,适用于复杂场景[^3]。 - **注意事项**:确保 `ApplicationContextProvider` 被 Spring 扫描到(放在主包或添加 `@ComponentScan`)。 #### 验证和最佳实践 - **测试验证**:启动应用后,模拟客户端连接(如使用 WebSocket 测试工具),检查 Service 方法是否正常调用,无空指针异常。 - **配置检查**:确保 WebSocket 配置正确。例如,在 `WebSocketConfig` 类中启用端点扫描: ```java import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // 注册端点,例如:registry.addHandler(yourHandler, "/your-path"); } } ``` - **性能考虑**:方法1的静态变量不会增加内存开销,因为 Service 本身是单例。避免在 WebSocket 中保存状态变量,以防并发问题[^5]。 #### 总结 通过上述方法,您可以解决 Spring Boot WebSocket 自动注入 Service 失败的问题。核心思路是绕过 Spring 的单例限制,确保每个 WebSocket 实例能访问已注入Service。推荐优先使用方法1,它更简洁且符合 Spring 的依赖注入原则[^2]。如果问题仍存在,检查依赖版本(如 Spring Boot 2.x 以上)或提供更多代码细节以便进一步诊断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值