GraphQL SPQR搭配Spring Reactive实现订阅时WebSocket连接报错如何解决
问题排查与解决思路
1. 移除不兼容的WebSocket依赖
你当前使用Webflux+Netty的响应式技术栈,引入的spring-boot-starter-websocket是Servlet容器专用的WebSocket依赖,和Netty栈完全不兼容,直接从build.gradle中删除该依赖即可,Webflux本身已经内置WebSocket支持,无需额外引入独立starter。
2. 手动配置Webflux下的GraphQL订阅端点
你使用的graphql-spqr-spring-boot-starter 0.0.6版本默认仅自动配置Servlet栈的订阅端点,Webflux场景下需要手动注册WebSocket路由,新增如下配置类即可:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; import graphql.GraphQL; import io.leangen.graphql.spqr.spring.web.reactive.GraphQLWebSocketHandler; import io.leangen.graphql.spqr.spring.web.ws.GraphQLWsProtocolFactory; import java.util.Map; @Configuration public class GraphQLWsConfig { private final GraphQL graphQL; public GraphQLWsConfig(GraphQL graphQL) { this.graphQL = graphQL; } @Bean public HandlerMapping graphQLWsHandlerMapping() { GraphQLWebSocketHandler wsHandler = new GraphQLWebSocketHandler( graphQL, new GraphQLWsProtocolFactory().create() ); SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setUrlMap(Map.of("/subscriptions", wsHandler)); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE); return mapping; } @Bean public WebSocketHandlerAdapter webSocketHandlerAdapter() { return new WebSocketHandlerAdapter(); } }
3. 修正Playground订阅端点配置
graphql-kickstart playground默认不会自动识别Webflux下的订阅端点,需要在配置文件中手动指定:
# application.yml graphql: playground: enabled: true endpoint: /graphql subscription-endpoint: /subscriptions
如果你的项目配置了server.webflux.base-path前缀,需要把前缀同步加到subscription-endpoint的路径中。
4. 补充订阅触发逻辑
你当前仅定义了订阅的Publisher,还需要在数据变更的Mutation逻辑中,主动向匹配的订阅者推送数据,示例如下:
@GraphQLMutation public Mono<ProductDto> saveProduct(ProductDto productDto) { return productRepository.save(AppUtils.ProductDtoToEntity(productDto)) .map(AppUtils::ProductEntityToDto) .doOnSuccess(savedProduct -> { // 向价格匹配的所有订阅者推送新增/修改的商品数据 subscribers.get(savedProduct.getPrice()) .forEach(sink -> sink.next(savedProduct)); }); }
额外校验点
- 确认启动端口无占用,启动后可直接测试WebSocket地址连通性,排查网络或路径配置问题
- 你当前使用的Spring Boot 2.5.4和SPQR starter 0.0.6版本兼容,无需调整版本号
内容的提问来源于stack exchange,提问作者Tali
相关产品推荐
相关产品推荐

