@StreamListener註解的工作原理

一直被@StreamListener註解帶來的恐懼所支配。今天來揭開它的面紗。

MAVEN引入相關jar包(版本2.0.1)

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-stream-kafka</artifactId>
		</dependency>

@StreamListener註解使用

	@StreamListener
	public void test(){
	
	}

相關源碼分析

先點開@StreamListener註解源碼:

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MessageMapping
@Documented
public @interface StreamListener {

	/**
	 * The name of the binding target (e.g. channel) that the method subscribes to.
	 * @return the name of the binding target.
	 */
	@AliasFor("target")
	String value() default "";

	/**
	 * The name of the binding target (e.g. channel) that the method subscribes to.
	 * @return the name of the binding target.
	 */
	@AliasFor("value")
	String target() default "";

	/**
	 * A condition that must be met by all items that are dispatched to this method.
	 * @return a SpEL expression that must evaluate to a {@code boolean} value.
	 */
	String condition() default "";

	/**
	 * When "true" (default), and a {@code @SendTo} annotation is present, copy the
	 * inbound headers to the outbound message (if the header is absent on the outbound
	 * message). Can be an expression ({@code #{...}}) or property placeholder. Must
	 * resolve to a boolean or a string that is parsed by {@code Boolean.parseBoolean()}.
	 * An expression that resolves to {@code null} is interpreted to mean {@code false}.
	 *
	 * The expression is evaluated during application initialization, and not for each
	 * individual message.
	 *
	 * Prior to version 1.3.0, the default value used to be "false" and headers were
	 * not propagated by default.
	 *
	 * Starting with version 1.3.0, the default value is "true".
	 *
	 * @since 1.2.3
	 */
	String copyHeaders() default "true";

}

這是它所在的包結構:
在這裏插入圖片描述
選中@StreamListener註解value()方法看誰調用了它:
在這裏插入圖片描述
可以看到下面的方法又調用了上面的方法,所以只需要展開下面的方法即可:
在這裏插入圖片描述
直接點擊run()方法,就到了處理@StreamListener註解的代碼入口(部分代碼):

public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, SmartInitializingSingleton {
    @Override
	public final Object postProcessAfterInitialization(Object bean, final String beanName) throws BeansException {
		Class<?> targetClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean) : bean.getClass(); //找到bean對象的真實類型
		Method[] uniqueDeclaredMethods = ReflectionUtils.getUniqueDeclaredMethods(targetClass); //找到bean對象的方法
		//逐個遍歷並處理聲明瞭@StreamListener註解的方法
		for (Method method : uniqueDeclaredMethods) {
		    //找聲明瞭@StreamListener註解的方法
			StreamListener streamListener = AnnotatedElementUtils.findMergedAnnotation(method, StreamListener.class);
			if (streamListener != null && !method.isBridge()) {
				streamListenerCallbacks.add(() -> {
					Assert.isTrue(method.getAnnotation(Input.class) == null, StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER); //被@StreamListener註解聲明的方法上不允許再聲明@Input註解
					this.doPostProcess(streamListener, method, bean);
				});
			}
		}
		return bean;
	}
}

我們看看doPostProcess()方法中做了啥

	private void doPostProcess(StreamListener streamListener, Method method, Object bean) {
		streamListener = postProcessAnnotation(streamListener, method);
		Optional<StreamListenerSetupMethodOrchestrator> streamListenerSetupMethodOrchestratorAvailable =
				streamListenerSetupMethodOrchestrators.stream()
						.filter(t -> t.supports(method))
						.findFirst();
		Assert.isTrue(streamListenerSetupMethodOrchestratorAvailable.isPresent(),
				"A matching StreamListenerSetupMethodOrchestrator must be present");
		StreamListenerSetupMethodOrchestrator streamListenerSetupMethodOrchestrator = streamListenerSetupMethodOrchestratorAvailable.get();
		streamListenerSetupMethodOrchestrator.orchestrateStreamListenerSetupMethod(streamListener, method, bean);
	}
    
    //啥也沒幹的一個方法,其實是一個擴展點
    protected StreamListener postProcessAnnotation(StreamListener originalAnnotation, Method annotatedMethod) {
		return originalAnnotation;
	}

StreamListenerSetupMethodOrchestrator類是個接口,它的實現在StreamListenerAnnotationBeanPostProcessor類的內部類DefaultStreamListenerSetupMethodOrchestrator中:

private class DefaultStreamListenerSetupMethodOrchestrator implements StreamListenerSetupMethodOrchestrator
		@Override
		public void orchestrateStreamListenerSetupMethod(StreamListener streamListener, Method method, Object bean) {
			String methodAnnotatedInboundName = streamListener.value();  //處理@StreamListener註解的value值

			String methodAnnotatedOutboundName = StreamListenerMethodUtils.getOutboundBindingTargetName(method); //解析輸出通道
			int inputAnnotationCount = StreamListenerMethodUtils.inputAnnotationCount(method);
			int outputAnnotationCount = StreamListenerMethodUtils.outputAnnotationCount(method);
			boolean isDeclarative = checkDeclarativeMethod(method, methodAnnotatedInboundName, methodAnnotatedOutboundName);
			StreamListenerMethodUtils.validateStreamListenerMethod(method,
					inputAnnotationCount, outputAnnotationCount,
					methodAnnotatedInboundName, methodAnnotatedOutboundName,
					isDeclarative, streamListener.condition());
			if (isDeclarative) {
				StreamListenerParameterAdapter[] toSlpaArray = new StreamListenerParameterAdapter[this.streamListenerParameterAdapters.size()];
				Object[] adaptedInboundArguments = adaptAndRetrieveInboundArguments(method, methodAnnotatedInboundName,
						this.applicationContext,
						this.streamListenerParameterAdapters.toArray(toSlpaArray));
				invokeStreamListenerResultAdapter(method, bean, methodAnnotatedOutboundName, adaptedInboundArguments);
			} else {
				registerHandlerMethodOnListenedChannel(method, streamListener, bean);
			}
		}
}

可以看到,這段代碼中完成了對@StreamListener註解的解析,並在最後調用了registerHandlerMethodOnListenedChannel()方法完成了註冊,看一下具體實現:

		private void registerHandlerMethodOnListenedChannel(Method method, StreamListener streamListener, Object bean) {
			Assert.hasText(streamListener.value(), "The binding name cannot be null");
			if (!StringUtils.hasText(streamListener.value())) {
				throw new BeanInitializationException("A bound component name must be specified");
			}
			final String defaultOutputChannel = StreamListenerMethodUtils.getOutboundBindingTargetName(method);
			if (Void.TYPE.equals(method.getReturnType())) {
				Assert.isTrue(StringUtils.isEmpty(defaultOutputChannel),
						"An output channel cannot be specified for a method that does not return a value");
			}
			else {
				Assert.isTrue(!StringUtils.isEmpty(defaultOutputChannel),
						"An output channel must be specified for a method that can return a value");
			}
			StreamListenerMethodUtils.validateStreamListenerMessageHandler(method);
			mappedListenerMethods.add(streamListener.value(),
					new StreamListenerHandlerMethodMapping(bean, method, streamListener.condition(), defaultOutputChannel,
							streamListener.copyHeaders()));
		}

所以呢,最後的結果是,@StreamListener註解標註的方法會在系統啓動時由StreamListenerAnnotationBeanPostProcessor類發起解析,然後被StreamListenerAnnotationBeanPostProcessor類的內部類DefaultStreamListenerSetupMethodOrchestrator解析並註冊到mappedListenerMethods中,看一下mappedListenerMethods是個啥:

private final MultiValueMap<String, StreamListenerHandlerMethodMapping> mappedListenerMethods = new LinkedMultiValueMap<>();

額…一個map而已。name問題來了,註冊完了,它是怎麼工作的?在StreamListenerAnnotationBeanPostProcessor類全局搜mappedListenerMethods,可以看到它出現在了這段代碼裏:

	@Override
	public final void afterSingletonsInstantiated() {
		this.injectAndPostProcessDependencies();
		EvaluationContext evaluationContext = IntegrationContextUtils.getEvaluationContext(this.applicationContext.getBeanFactory());
		for (Map.Entry<String, List<StreamListenerHandlerMethodMapping>> mappedBindingEntry : mappedListenerMethods
				.entrySet()) {
			ArrayList<DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper> handlers = new ArrayList<>();
			for (StreamListenerHandlerMethodMapping mapping : mappedBindingEntry.getValue()) {
				final InvocableHandlerMethod invocableHandlerMethod = this.messageHandlerMethodFactory
						.createInvocableHandlerMethod(mapping.getTargetBean(),
								checkProxy(mapping.getMethod(), mapping.getTargetBean()));
				StreamListenerMessageHandler streamListenerMessageHandler = new StreamListenerMessageHandler(
						invocableHandlerMethod, resolveExpressionAsBoolean(mapping.getCopyHeaders(), "copyHeaders"),
						springIntegrationProperties.getMessageHandlerNotPropagatedHeaders());
				streamListenerMessageHandler.setApplicationContext(this.applicationContext);
				streamListenerMessageHandler.setBeanFactory(this.applicationContext.getBeanFactory());
				if (StringUtils.hasText(mapping.getDefaultOutputChannel())) {
					streamListenerMessageHandler.setOutputChannelName(mapping.getDefaultOutputChannel());
				}
				streamListenerMessageHandler.afterPropertiesSet();
				if (StringUtils.hasText(mapping.getCondition())) {
					String conditionAsString = resolveExpressionAsString(mapping.getCondition(), "condition");
					Expression condition = SPEL_EXPRESSION_PARSER.parseExpression(conditionAsString);
					handlers.add(
							new DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper(
									condition, streamListenerMessageHandler));
				}
				else {
					handlers.add(
							new DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper(
									null, streamListenerMessageHandler));
				}
			}
			if (handlers.size() > 1) {
				for (DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper handler : handlers) {
					Assert.isTrue(handler.isVoid(), StreamListenerErrorMessages.MULTIPLE_VALUE_RETURNING_METHODS);
				}
			}
			AbstractReplyProducingMessageHandler handler;

			if (handlers.size() > 1 || handlers.get(0).getCondition() != null) {
				handler = new DispatchingStreamListenerMessageHandler(handlers, evaluationContext);
			}
			else {
				handler = handlers.get(0).getStreamListenerMessageHandler();
			}
			handler.setApplicationContext(this.applicationContext);
			handler.setChannelResolver(this.binderAwareChannelResolver);
			handler.afterPropertiesSet();
			this.applicationContext.getBeanFactory().registerSingleton(handler.getClass().getSimpleName() + handler.hashCode(), handler);
			applicationContext.getBean(mappedBindingEntry.getKey(), SubscribableChannel.class).subscribe(handler);
		}
		this.mappedListenerMethods.clear();
	}

真是好頭疼的一大段代碼啊,完全不想看幹嘛的。所以,偷個懶?簡化下它:

	@Override
	public final void afterSingletonsInstantiated() {
		for (Map.Entry<String, List<StreamListenerHandlerMethodMapping>> mappedBindingEntry : mappedListenerMethods
				.entrySet()) {
			applicationContext.getBean(mappedBindingEntry.getKey(), SubscribableChannel.class).subscribe(handler);
		}
		this.mappedListenerMethods.clear();
	}

是不是舒服多了?做了什麼一目瞭然啊!遍歷整個mappedListenerMethods,按個取出元素,一同猛如虎的操作,然後取出了bean名稱爲mappedBindingEntry.getKey(),類型爲SubscribableChannel的bean對象執行了下subscribe()方法。然後,就訂閱了MQ消息???

嗯,終於看完了。就這樣吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章