ChannelInterceptor的作用及用法

213 阅读4分钟

ChannelInterceptor的作用及用法

ChannelInterceptor 是 Spring Messaging 框架中的一个接口,定义在 org.springframework.messaging.support 包下。它的主要作用是在消息通道(MessageChannel)处理消息的过程中插入自定义逻辑,允许开发者在消息发送、接收、异常处理等关键节点进行拦截和干预。以下详细介绍其作用和用法:

作用

  • 消息预处理:在消息发送到通道之前对消息进行修改、验证或添加额外的信息。例如,在消息中添加自定义的头部信息,用于后续的处理。
  • 消息后处理:在消息成功发送或接收后执行一些操作,如日志记录、性能监控等。
  • 异常处理:捕获并处理消息处理过程中抛出的异常,避免异常直接影响系统的正常运行。
  • 控制消息传递:根据特定条件决定是否允许消息继续在通道中传递,实现消息的过滤和限流。

接口方法

ChannelInterceptor 接口定义了多个方法,以下是主要方法及其作用:

  • preSend(Message<?> message, MessageChannel channel) :在消息发送到通道之前调用,可以对消息进行修改或决定是否允许消息继续传递。如果返回 null,则表示消息不会被发送。
  • postSend(Message<?> message, MessageChannel channel, boolean sent) :在消息发送操作完成后调用,无论消息是否成功发送。可以根据 sent 参数判断消息是否成功发送。
  • afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) :在消息发送完成且所有后处理操作完成后调用,可用于释放资源或进行最终的日志记录。如果发送过程中出现异常,ex 参数将包含该异常信息。
  • preReceive(MessageChannel channel) :在从通道接收消息之前调用,可以决定是否允许接收消息。如果返回 false,则不会尝试从通道接收消息。
  • postReceive(Message<?> message, MessageChannel channel) :在成功从通道接收到消息后调用,可以对消息进行后处理。
  • afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) :在消息接收完成且所有后处理操作完成后调用,可用于释放资源或进行最终的日志记录。如果接收过程中出现异常,ex 参数将包含该异常信息。

用法示例

1. 创建自定义拦截器

创建一个类实现 ChannelInterceptor 接口,并实现所需的方法。以下是一个简单的示例,用于记录消息发送和接收的日志:

import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomChannelInterceptor implements ChannelInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(CustomChannelInterceptor.class);

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        logger.info("Pre-sending message: {}", message);
        return message;
    }

    @Override
    public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
        logger.info("Post-sending message. Sent: {}", sent);
    }

    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
        if (ex != null) {
            logger.error("Error sending message", ex);
        }
        logger.info("Message sending completed");
    }

    @Override
    public boolean preReceive(MessageChannel channel) {
        logger.info("Pre-receiving message from channel: {}", channel);
        return true;
    }

    @Override
    public Message<?> postReceive(Message<?> message, MessageChannel channel) {
        logger.info("Post-receiving message: {}", message);
        return message;
    }

    @Override
    public void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) {
        if (ex != null) {
            logger.error("Error receiving message", ex);
        }
        logger.info("Message receiving completed");
    }
}

2. 配置拦截器

在 Spring 配置中,将自定义拦截器添加到消息通道中。以下是一个使用 Java 配置的示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.messaging.support.ChannelInterceptor;

@Configuration
public class MessagingConfig {

    @Bean
    public ExecutorSubscribableChannel messageChannel() {
        ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel();
        channel.addInterceptor(new CustomChannelInterceptor());
        return channel;
    }
}

3. 使用消息通道

在代码中使用配置好的消息通道发送和接收消息,拦截器将自动生效:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.stereotype.Service;

@Service
public class MessageService {

    @Autowired
    private ExecutorSubscribableChannel messageChannel;

    public void sendMessage(String payload) {
        Message<String> message = new GenericMessage<>(payload);
        messageChannel.send(message);
    }
}

总结

ChannelInterceptor 为开发者提供了在消息通道处理消息的各个阶段插入自定义逻辑的能力,通过实现该接口并配置拦截器,可以实现消息的预处理、后处理、异常处理等功能,增强消息处理的灵活性和可维护性。

如下我的消息中心在客户端连接消息服务前做的相关校验:

@Component
public class SocketChannelInterceptor implements ChannelInterceptor {

    private static final Logger log = LoggerFactory.getLogger(SocketChannelInterceptor.class);

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    RedisOperationUtils redisOperationUtils;

    public SocketChannelInterceptor() {
    }

    public Message<?> preSend(Message<?> message, MessageChannel channel) {

        //从消息中获取 StompHeaderAccessor,用于访问和修改 STOMP 消息的头部信息
        StompHeaderAccessor accessor = (StompHeaderAccessor) MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

        //只有当 STOMP 命令为 CONNECT 时,才会进行后续的参数验证和用户信息设置。
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            try {
                String incopatSessionId = accessor.getFirstNativeHeader("sessionId");
                log.info("com.incomessage.interceptor.SocketChannelInterceptor.preSend   -->   incopatSessionId:", incopatSessionId);
                String identifier = "";
                List<String> nativeHeaderList = accessor.getNativeHeader("identifier");
                if (nativeHeaderList != null && nativeHeaderList.size() > 0) {
                    identifier = (String)nativeHeaderList.get(0);
                }

                String tokenValue = "";
                List<String> tokenValueList = accessor.getNativeHeader("tokenValue");
                if (tokenValueList != null && tokenValueList.size() > 0) {
                    tokenValue = (String)tokenValueList.get(0);
                }

                if (!StringUtils.isNotBlank(identifier) || !StringUtils.isNotBlank(incopatSessionId) || !StringUtils.isNotBlank(tokenValue)) {
                    throw new IllegalArgumentException("invalid parameter");
                }

                accessor.setUser(new CustomUser(identifier, incopatSessionId, tokenValue));
            } catch (Exception exception) {
                Exception ex = exception;
                log.error("connect error:error message{}", ex.getMessage());
                try {
                    throw ex;
                } catch (Exception exc) {
                    throw new RuntimeException(exc);
                }
            }
        }
        return message;
    }
}