Spring Websocket+SockJS+STOMP 实现即时通信(三)—— MessageChannel

版权声明:本文为博主原创文章,可以转载不可复制。 https://blog.csdn.net/qq_32331073/article/details/83014829


两种MessageChannel实现

TemporaryReplyChannel

  • 用于接收单个回复消息的临时通道。在整个断点调试过程中没有追踪到,所以在这里不详细说明。

ExecutorSubscribableChannel

  • 正如字面上所表示的这样Executor(线程池)Subscribable(可订阅的)Channel(通道)——一个通过线程池将消息发送给每个订阅者的通道。这也是Spring-Messaging功能的核心,理解了这个实现类的构成,就很容易掌握《Spring Websocket+SockJS+STOMP 实现即时通信》

剖析ExecutorSubscribableChannel

在这里插入图片描述
ExecutorSubscribableChannel类,通过继承父类方法或直接声明,可以看作由 部分构成

  1. beanName:主要用作日志记录,用来区分ExecutorSubscribableChannel的不同实例;
  2. handlers:MessageHandler集合,作为MessageChannel的订阅者,用来处理Messages;
  3. SendTask :一个内部类,是MessageHandlingRunnable的子类,将一个Message与一个MessageHandler封装成线程任务,丢入线程池执行;
  4. executor: 用来执行SendTask任务的TaskPoolExecutor线程池;
  5. interceptors :普通ChannelInterceptor集合;
  6. executorInterceptors:ExecutorChannelInterceptor线程池拦截器集合;

ChannelInterceptor与ExecutorChannelInterceptor到底有什么区别,并如何工作?

ExecutorSubscribableChannel:

public class ExecutorSubscribableChannel extends AbstractSubscribableChannel {
    private String beanName;
    private final Set<MessageHandler> handlers = new CopyOnWriteArraySet<>();
	private final Executor executor;
	private final List<ChannelInterceptor> interceptors = new ArrayList<>(5);
	private final List<ExecutorChannelInterceptor> executorInterceptors = new ArrayList<>(4);
	/**
	 * Invoke a MessageHandler with ExecutorChannelInterceptors.
	 */
	private class SendTask implements MessageHandlingRunnable {
	}

}

三个ExecutorSubscribableChannel实例

在启用STOMP的时候——@EnableWebSocketMessageBroker,Spring框架会自动构造三个ExecutorSubscribableChannel实例:

  • “clientInboundChannel” — 用于传递从WebSocket客户端接收到的消息。
  • “clientOutboundChannel” — 用于向WebSocket客户端发送服务器消息。
  • “brokerChannel” — 用于从服务器端的应用程序代码中向message broker或 stomp broker relay发送消息。

工作方式如下图所示:

启用简单的消息代理:config.enableSimpleBroker
在这里插入图片描述
启用STOMP代理中继:config.enableStompBrokerRelay
在这里插入图片描述


自定义配置MessageChannel

  1. 启用STOMP,并配置MessageChannel。从方法的名字上我们可以确定分别是对brokerChannelclientInboundChannelclientOutboundChannel进行自定义配置。
WebSocketMessageBrokerConfigurer实现类:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfigurer implements WebSocketMessageBrokerConfigurer {
	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.configureBrokerChannel().taskExecutor();
	}
	@Override
	public void configureClientInboundChannel(ChannelRegistration registration) {
	}
	@Override
	public void configureClientOutboundChannel(ChannelRegistration registration) {		
	}
}

MessageChannel的默认配置

  1. 一直说通过@EnableWebSocketMessageBroker来启用STOMP,那么它是如何启用STOMP的呢?当然,它是通过导入相关配置来实现STOMP启用的。
EnableWebSocketMessageBroker:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebSocketMessageBrokerConfiguration.class)
public @interface EnableWebSocketMessageBroker {

}

在这里插入图片描述

  1. 配置clientInboundChannel。其中有一个相关成员变量四个相关方法
    • 一个成员变量:
      clientInboundChannelRegistration:用来登记“clientInboundChannel”的配置信息。
    • 一个钩子方法:
      configureClientInboundChannel(registration):留给它的子类,用来获取WebSocketMessageBrokerConfigurer提供的自定义配置信息。
    • 三个Bean方法:
      • clientInboundChannel():通过clientInboundChannelExecutor()获得Executor实例,通过getClientInboundChannelRegistration()获得通道的其他配置信息,用来构造一个ExecutorSubscribableChannel 实例做为“clientInboundChannel”;
      • clientInboundChannelExecutor():通过getClientInboundChannelRegistration()获得通道配置信息,再从通道配置信息中获得TaskExecutorRegistration线程池配置信息,最后从TaskExecutorRegistration获得ThreadPoolTaskExecutor 实例,作为“clientInboundChannel”的支撑;
      • getClientInboundChannelRegistration():如果成员变量clientInboundChannelRegistrationnull,那么将直接new一个ChannelRegistration实例,并赋值给成员变量clientInboundChannelRegistration,同时调用configureClientInboundChannel(registration)钩子方法,获取WebSocketMessageBrokerConfigurer提供的自定义配置信息,否则就说明不是第一次调用该方法,直接返回成员变量;
AbstractMessageBrokerConfiguration :

public abstract class AbstractMessageBrokerConfiguration implements ApplicationContextAware {
	@Nullable
	private ChannelRegistration clientInboundChannelRegistration;
	@Bean
	public AbstractSubscribableChannel clientInboundChannel() {
		ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientInboundChannelExecutor());
		ChannelRegistration reg = getClientInboundChannelRegistration();
		if (reg.hasInterceptors()) {
			channel.setInterceptors(reg.getInterceptors());
		}
		return channel;
	}
	@Bean
	public ThreadPoolTaskExecutor clientInboundChannelExecutor() {
		TaskExecutorRegistration reg = getClientInboundChannelRegistration().taskExecutor();
		ThreadPoolTaskExecutor executor = reg.getTaskExecutor();
		executor.setThreadNamePrefix("clientInboundChannel-");
		return executor;
	}
	protected final ChannelRegistration getClientInboundChannelRegistration() {
		if (this.clientInboundChannelRegistration == null) {
			ChannelRegistration registration = new ChannelRegistration();
			configureClientInboundChannel(registration);
			registration.interceptors(new ImmutableMessageChannelInterceptor());
			this.clientInboundChannelRegistration = registration;
		}
		return this.clientInboundChannelRegistration;
	}
	/**
	 * A hook for subclasses to customize the message channel for inbound messages
	 * from WebSocket clients.
	 */
	protected void configureClientInboundChannel(ChannelRegistration registration) {
	}
}
  1. 配置clientOutboundChannel。其中也有一个相关成员变量四个相关方法,在此不做详述,可以直接类比上面的“配置clientInboundChannel”。
AbstractMessageBrokerConfiguration :

public abstract class AbstractMessageBrokerConfiguration implements ApplicationContextAware {
	@Nullable
	private ChannelRegistration clientOutboundChannelRegistration;
	@Bean
	public AbstractSubscribableChannel clientOutboundChannel() {
		ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientOutboundChannelExecutor());
		ChannelRegistration reg = getClientOutboundChannelRegistration();
		if (reg.hasInterceptors()) {
			channel.setInterceptors(reg.getInterceptors());
		}
		return channel;
	}
	@Bean
	public ThreadPoolTaskExecutor clientOutboundChannelExecutor() {
		TaskExecutorRegistration reg = getClientOutboundChannelRegistration().taskExecutor();
		ThreadPoolTaskExecutor executor = reg.getTaskExecutor();
		executor.setThreadNamePrefix("clientOutboundChannel-");
		return executor;
	}
	protected final ChannelRegistration getClientOutboundChannelRegistration() {
		if (this.clientOutboundChannelRegistration == null) {
			ChannelRegistration registration = new ChannelRegistration();
			configureClientOutboundChannel(registration);
			registration.interceptors(new ImmutableMessageChannelInterceptor());
			this.clientOutboundChannelRegistration = registration;
		}
		return this.clientOutboundChannelRegistration;
	}
	/**
	 * A hook for subclasses to customize the message channel for messages from
	 * the application or message broker to WebSocket clients.
	 */
	protected void configureClientOutboundChannel(ChannelRegistration registration) {
	}
}
  1. 在看brokerChannel之前,有必要先要了解下ChannelRegistration——通道配置信息类。该类共持有两个实例:

    • TaskExecutorRegistration实例:
      我们知道ExecutorSubscribableChannel实际上是由ThreadPoolTaskExecutor线程池作为支撑,而TaskExecutorRegistration所持有的就是通道的ThreadPoolTaskExecutor线程池配置信息;
    • ChannelInterceptor集合:
      用来保存一系列的通道拦截器;

    另外我们需要理解taskExecutor(taskExecutor)方法:

    1. 如果taskExecutor()方法不是第一次被调用,那么TaskExecutorRegistration将不为null,说明线程池已经被配置,将直接返回配置信息;
    2. 如果TaskExecutorRegistrationnull,那么继续判断;
    3. 参数taskExecutor如果不为null,那么将把taskExecutor绑定到TaskExecutorRegistration——new TaskExecutorRegistration(taskExecutor)
    4. 参数taskExecutor如果为null,那么直接new TaskExecutorRegistration()
ChannelRegistration:

public class ChannelRegistration {
	@Nullable
	private TaskExecutorRegistration registration;
	private final List<ChannelInterceptor> interceptors = new ArrayList<>();
	/**
	 * Configure the thread pool backing this message channel.
	 */
	public TaskExecutorRegistration taskExecutor() {
		return taskExecutor(null);
	}
	/**
	 * Configure the thread pool backing this message channel using a custom
	 * ThreadPoolTaskExecutor.
	 * @param taskExecutor the executor to use (or {@code null} for a default executor)
	 */
	public TaskExecutorRegistration taskExecutor(@Nullable ThreadPoolTaskExecutor taskExecutor) {
		if (this.registration == null) {
			this.registration = (taskExecutor != null ? new TaskExecutorRegistration(taskExecutor) :
					new TaskExecutorRegistration());
		}
		return this.registration;
	}
	/**
	 * Configure the given interceptors for this message channel,
	 * adding them to the channel's current list of interceptors.
	 * @since 4.3.12
	 */
	public ChannelRegistration interceptors(ChannelInterceptor... interceptors) {
		this.interceptors.addAll(Arrays.asList(interceptors));
		return this;
	}
	/**
	 * Configure interceptors for the message channel.
	 * @deprecated as of 4.3.12, in favor of {@link #interceptors(ChannelInterceptor...)}
	 */
	@Deprecated
	public ChannelRegistration setInterceptors(@Nullable ChannelInterceptor... interceptors) {
		if (interceptors != null) {
			this.interceptors.addAll(Arrays.asList(interceptors));
		}
		return this;
	}
	protected boolean hasTaskExecutor() {
		return (this.registration != null);
	}
	protected boolean hasInterceptors() {
		return !this.interceptors.isEmpty();
	}
	protected List<ChannelInterceptor> getInterceptors() {
		return this.interceptors;
	}
}
  1. 接着再看TaskExecutorRegistration这个类,这个类持有一个ThreadPoolTaskExecutor实例。这个类有两个构造方法:
    • TaskExecutorRegistration(taskExecutor)
      将传入的taskExecutor绑定到成员变量上。
    • TaskExecutorRegistration()
      无参的构造方法,在该构造方法中,会直接new一个ThreadPoolTaskExecutor实例,其coreSize核心线程数为Runtime.getRuntime().availableProcessors() * 2 —— 两倍CPU
TaskExecutorRegistration:
public class TaskExecutorRegistration {
	private final ThreadPoolTaskExecutor taskExecutor;
	/**
	 * Create a new {@code TaskExecutorRegistration} for a default
	 * {@link ThreadPoolTaskExecutor}.
	 */
	public TaskExecutorRegistration() {
		this.taskExecutor = new ThreadPoolTaskExecutor();
		this.taskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
		this.taskExecutor.setAllowCoreThreadTimeOut(true);
	}
	/**
	 * Create a new {@code TaskExecutorRegistration} for a given
	 * {@link ThreadPoolTaskExecutor}.
	 * @param taskExecutor the executor to use
	 */
	public TaskExecutorRegistration(ThreadPoolTaskExecutor taskExecutor) {
		Assert.notNull(taskExecutor, "ThreadPoolTaskExecutor must not be null");
		this.taskExecutor = taskExecutor;
	}
	
	protected ThreadPoolTaskExecutor getTaskExecutor() {
		if (this.corePoolSize != null) {
			this.taskExecutor.setCorePoolSize(this.corePoolSize);
		}
		if (this.maxPoolSize != null) {
			this.taskExecutor.setMaxPoolSize(this.maxPoolSize);
		}
		if (this.keepAliveSeconds != null) {
			this.taskExecutor.setKeepAliveSeconds(this.keepAliveSeconds);
		}
		if (this.queueCapacity != null) {
			this.taskExecutor.setQueueCapacity(this.queueCapacity);
		}
		return this.taskExecutor;
	}
}
  1. 配置brokerChannel。与clientInboundChannelclientOutboundChannel不同的是,配置brokerChannelgetBrokerRegistry()代替了getClient**boundChannelRegistration()、用configureMessageBroker(registry)代替了configureClient**boundChannel(registration),主要是因为前两者只需要配置MessageChannel,而后者既需要配置MessageChannel同时需要配置MessageBroker,所以这里用MessageBrokerRegistry代替了ChannelRegistration,而MessageBrokerRegistry持有了ChannelRegistration实例,相当于多加了一层;
AbstractMessageBrokerConfiguration :

public abstract class AbstractMessageBrokerConfiguration implements ApplicationContextAware {
	@Nullable
	private MessageBrokerRegistry brokerRegistry;
	@Bean
	public AbstractSubscribableChannel brokerChannel() {
		ChannelRegistration reg = getBrokerRegistry().getBrokerChannelRegistration();
		ExecutorSubscribableChannel channel = (reg.hasTaskExecutor() ?
				new ExecutorSubscribableChannel(brokerChannelExecutor()) : new ExecutorSubscribableChannel());
		reg.interceptors(new ImmutableMessageChannelInterceptor());
		channel.setInterceptors(reg.getInterceptors());
		return channel;
	}
	@Bean
	public ThreadPoolTaskExecutor brokerChannelExecutor() {
		ChannelRegistration reg = getBrokerRegistry().getBrokerChannelRegistration();
		ThreadPoolTaskExecutor executor;
		if (reg.hasTaskExecutor()) {
			executor = reg.taskExecutor().getTaskExecutor();
		}
		else {
			// Should never be used
			executor = new ThreadPoolTaskExecutor();
			executor.setCorePoolSize(0);
			executor.setMaxPoolSize(1);
			executor.setQueueCapacity(0);
		}
		executor.setThreadNamePrefix("brokerChannel-");
		return executor;
	}
	/**
	 * An accessor for the {@link MessageBrokerRegistry} that ensures its one-time creation
	 * and initialization through {@link #configureMessageBroker(MessageBrokerRegistry)}.
	 */
	protected final MessageBrokerRegistry getBrokerRegistry() {
		if (this.brokerRegistry == null) {
			MessageBrokerRegistry registry = new MessageBrokerRegistry(clientInboundChannel(), clientOutboundChannel());
			configureMessageBroker(registry);
			this.brokerRegistry = registry;
		}
		return this.brokerRegistry;
	}
	/**
	 * A hook for subclasses to customize message broker configuration through the
	 * provided {@link MessageBrokerRegistry} instance.
	 */
	protected void configureMessageBroker(MessageBrokerRegistry registry) {
	}
}
  1. 值得注意的是,brokerChannel()方法在构造ExecutorSubscribableChannel实例时,绑定ThreadPoolTaskExecutor的逻辑与前两者有所不同,结合上面的分析不难理解下列内容;
    • clientInboundChannelclientOutboundChannel
      这两个通道的线程池一定会被设置,所以Messages总会由新的线程异步处理 —— 首先考虑自定义配置线程池,如果没有,那么将配置默认线程池 —— 其coreSize核心线程数为Runtime.getRuntime().availableProcessors() * 2 —— 两倍CPU
      ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(client**boundChannelExecutor());
      
    • brokerChannel
      如果没有自定义配置线程池,那么brokerChannel的线程池将被设为null,Messages将会被使用当前线程同步处理,所以在生产环境中一定要配置brokerChannel的线程池
       ExecutorSubscribableChannel channel = (reg.hasTaskExecutor() ?
         		new ExecutorSubscribableChannel(brokerChannelExecutor()) : new ExecutorSubscribableChannel());
      
ExecutorSubscribableChannel :

public class ExecutorSubscribableChannel extends AbstractSubscribableChannel {
	@Nullable
	private final Executor executor;
	/**
	 * Create a new {@link ExecutorSubscribableChannel} instance
	 * where messages will be sent in the callers thread.
	 */
	public ExecutorSubscribableChannel() {
		this(null);
	}
	/**
	 * Create a new {@link ExecutorSubscribableChannel} instance
	 * where messages will be sent via the specified executor.
	 * @param executor the executor used to send the message,
	 * or {@code null} to execute in the callers thread.
	 */
	public ExecutorSubscribableChannel(@Nullable Executor executor) {
		this.executor = executor;
	}
}

猜你喜欢

转载自blog.csdn.net/qq_32331073/article/details/83014829
今日推荐