Skip to content

STOMP 消息发送:让服务端主动与客户端对话 🚀

什么是 STOMP 消息发送?

在传统的 HTTP 请求-响应模式中,只有客户端可以主动发起请求,服务端只能被动响应。但在实时应用中,我们经常需要服务端主动向客户端推送消息,比如:

  • 📢 系统通知推送
  • 💬 聊天消息广播
  • 📊 实时数据更新
  • ⚠️ 告警信息推送

STOMP(Simple Text Oriented Messaging Protocol)消息发送就是解决这个问题的关键技术!

IMPORTANT

STOMP 消息发送的核心价值:让服务端能够在任何时候、从应用的任何地方主动向已连接的客户端发送消息,实现真正的双向实时通信。

核心组件:SimpMessagingTemplate

SimpMessagingTemplate 是 Spring WebSocket 提供的消息发送模板,它就像一个"消息快递员",负责将我们的消息准确投递到指定的目的地。

设计哲学

Spring 框架的设计者们遵循了一个重要原则:简化复杂操作。他们将复杂的 WebSocket 消息发送逻辑封装在 SimpMessagingTemplate 中,让开发者只需要关注业务逻辑,而不用担心底层的消息传输细节。

实战应用:Kotlin + SpringBoot 示例

基础消息发送

kotlin
@Controller
class GreetingController {
    
    // 需要手动处理 WebSocket 连接和消息格式
    @Autowired
    private lateinit var webSocketHandler: WebSocketHandler
    
    @PostMapping("/greetings")
    fun greet(@RequestBody greeting: String) {
        // 复杂的消息构建和发送逻辑
        val sessions = webSocketHandler.getSessions()
        val message = createWebSocketMessage(greeting)
        sessions.forEach { session ->
            try {
                session.sendMessage(message) 
            } catch (e: Exception) {
                // 需要手动处理异常
            }
        }
    }
}
kotlin
@Controller
class GreetingController(
    private val messagingTemplate: SimpMessagingTemplate
) {
    
    @PostMapping("/greetings")
    fun greet(@RequestBody greeting: String) {
        val timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME)
        val message = "[$timestamp]: $greeting"
        
        // 一行代码搞定消息发送!
        messagingTemplate.convertAndSend("/topic/greetings", message) 
    }
}

TIP

使用 SimpMessagingTemplate 后,复杂的 WebSocket 消息发送变成了一行代码!这就是 Spring 框架"约定优于配置"哲学的体现。

高级应用场景

1. 用户特定消息推送

kotlin
@Service
class NotificationService(
    private val messagingTemplate: SimpMessagingTemplate
) {
    
    /**
     * 向特定用户发送私人消息
     * @param userId 用户ID
     * @param notification 通知内容
     */
    fun sendPersonalNotification(userId: String, notification: String) {
        // 发送到用户专属队列
        messagingTemplate.convertAndSendToUser(
            userId, 
            "/queue/notifications", 
            notification
        )
    }
    
    /**
     * 广播系统公告
     * @param announcement 公告内容
     */
    fun broadcastAnnouncement(announcement: String) {
        val message = mapOf(
            "type" to "SYSTEM_ANNOUNCEMENT",
            "content" to announcement,
            "timestamp" to System.currentTimeMillis()
        )
        
        // 广播给所有订阅者
        messagingTemplate.convertAndSend("/topic/announcements", message)
    }
}

2. 实时数据监控推送

kotlin
@Component
class SystemMonitorService(
    private val messagingTemplate: SimpMessagingTemplate
) {
    
    /**
     * 定时推送系统状态
     */
    @Scheduled(fixedRate = 5000) // 每5秒执行一次
    fun pushSystemStatus() {
        val systemStatus = SystemStatus(
            cpuUsage = getCpuUsage(),
            memoryUsage = getMemoryUsage(),
            activeConnections = getActiveConnections(),
            timestamp = LocalDateTime.now()
        )
        
        // 推送系统状态到监控面板
        messagingTemplate.convertAndSend("/topic/system-status", systemStatus)
    }
    
    /**
     * 异常告警推送
     */
    fun sendAlert(alertLevel: AlertLevel, message: String) {
        val alert = Alert(
            level = alertLevel,
            message = message,
            timestamp = LocalDateTime.now()
        )
        
        when (alertLevel) {
            AlertLevel.CRITICAL -> {
                // 紧急告警发送给管理员
                messagingTemplate.convertAndSend("/topic/alerts/critical", alert)
            }
            AlertLevel.WARNING -> {
                // 警告信息发送给运维团队
                messagingTemplate.convertAndSend("/topic/alerts/warning", alert)
            }
            else -> {
                // 一般信息广播
                messagingTemplate.convertAndSend("/topic/alerts/info", alert)
            }
        }
    }
}

data class SystemStatus(
    val cpuUsage: Double,
    val memoryUsage: Double,
    val activeConnections: Int,
    val timestamp: LocalDateTime
)

data class Alert(
    val level: AlertLevel,
    val message: String,
    val timestamp: LocalDateTime
)

enum class AlertLevel {
    INFO, WARNING, CRITICAL
}

依赖注入的两种方式

方式一:按类型注入(推荐)

kotlin
@Controller
class MessageController(
    private val messagingTemplate: SimpMessagingTemplate
) {
    // Spring 会自动注入 SimpMessagingTemplate 实例
}

方式二:按名称注入

kotlin
@Controller
class MessageController {
    
    @Autowired
    @Qualifier("brokerMessagingTemplate") 
    private lateinit var messagingTemplate: SimpMessagingTemplate
    
    // 当存在多个相同类型的 Bean 时使用
}

NOTE

当应用中只有一个 SimpMessagingTemplate Bean 时,推荐使用构造函数注入(方式一)。当存在多个同类型 Bean 时,使用 @Qualifier 注解指定具体的 Bean 名称。

实际业务场景应用

聊天应用示例

完整的聊天室实现示例
kotlin
@RestController
@RequestMapping("/api/chat")
class ChatController(
    private val messagingTemplate: SimpMessagingTemplate,
    private val chatService: ChatService
) {
    
    /**
     * 发送聊天消息
     */
    @PostMapping("/send")
    fun sendMessage(@RequestBody chatMessage: ChatMessageRequest): ResponseEntity<String> {
        try {
            // 保存消息到数据库
            val savedMessage = chatService.saveMessage(chatMessage)
            
            // 构建广播消息
            val broadcastMessage = ChatMessage(
                id = savedMessage.id,
                username = chatMessage.username,
                content = chatMessage.content,
                timestamp = savedMessage.createdAt,
                type = MessageType.CHAT
            )
            
            // 广播给所有在线用户
            messagingTemplate.convertAndSend("/topic/chat/room/${chatMessage.roomId}", broadcastMessage)
            
            return ResponseEntity.ok("消息发送成功")
        } catch (e: Exception) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("消息发送失败: ${e.message}")
        }
    }
    
    /**
     * 用户加入聊天室
     */
    @PostMapping("/join")
    fun joinRoom(@RequestBody joinRequest: JoinRoomRequest): ResponseEntity<String> {
        // 广播用户加入消息
        val joinMessage = ChatMessage(
            username = "系统",
            content = "${joinRequest.username} 加入了聊天室",
            timestamp = LocalDateTime.now(),
            type = MessageType.SYSTEM
        )
        
        messagingTemplate.convertAndSend("/topic/chat/room/${joinRequest.roomId}", joinMessage) 
        
        return ResponseEntity.ok("加入聊天室成功")
    }
}

data class ChatMessage(
    val id: Long? = null,
    val username: String,
    val content: String,
    val timestamp: LocalDateTime,
    val type: MessageType
)

data class ChatMessageRequest(
    val roomId: String,
    val username: String,
    val content: String
)

data class JoinRoomRequest(
    val roomId: String,
    val username: String
)

enum class MessageType {
    CHAT, SYSTEM, NOTIFICATION
}

关键优势总结

🎯 简化开发

  • 一行代码发送消息convertAndSend() 方法封装了所有复杂逻辑
  • 自动类型转换:支持对象自动序列化为 JSON
  • 异常处理:内置错误处理机制

🔄 灵活的消息路由

  • 主题广播/topic/* 一对多消息推送
  • 队列私发/queue/* 一对一消息推送
  • 用户专属convertAndSendToUser() 用户特定消息

⚡ 高性能

  • 异步处理:消息发送不阻塞主线程
  • 连接复用:复用已建立的 WebSocket 连接
  • 批量发送:支持批量消息处理

IMPORTANT

SimpMessagingTemplate 让服务端消息推送变得像发送 HTTP 请求一样简单,这正是 Spring 框架"让复杂的事情变简单"设计理念的完美体现!

最佳实践建议

开发建议

  1. 消息格式统一:定义标准的消息格式,包含类型、内容、时间戳等字段
  2. 异常处理:在消息发送周围添加适当的异常处理逻辑
  3. 性能监控:监控消息发送频率,避免消息风暴
  4. 安全考虑:验证消息发送者权限,防止恶意消息推送

通过 SimpMessagingTemplate,我们可以轻松实现服务端主动消息推送,让应用具备真正的实时交互能力! ✨