1

Since method postReceive of org.springframework.messaging.support.ChannelInterceptor is not invoked in org.springframework.messaging.SubscribableChannel. Is there any way to intercept all of incoming messages for method annotated@StreamListener(Sink.INPUT)?

For example:

Intercept the message before go into handle method

@StreamListener(Sink.INPUT)
public void handle(Foo foo) {
    // ...
}

Below is my setup for Spring Cloud Stream

public interface EventSink {

    String INPUT1 = "input1";
    String INPUT2 = "input2";

    @Input(INPUT1)
    SubscribableChannel input1();

    @Input(INPUT2)
    SubscribableChannel input2();   
}

public interface EventSource {

    String OUTPUT1 = "output1";
    String OUTPUT2 = "output2";

    @Output(OUTPUT1)
    MessageChannel output1();

    @Output(OUTPUT2)
    MessageChannel output2()';
}

spring:
  cloud:
    stream:
      bindings:
        input1:
          destination: input1
        input2:
          destination: input2     
        output1:
          destination: output1
        output2:
          destination: output2

public class EventHandler {

    @StreamListener(EventSink.INPUT1)
    public void handle(Foo1 foo) {
        // ...
    }

    @StreamListener(EventSink.INPUT2)
    public void handle(Foo2 foo) {
        // ...
    }

}

@Service
public class Bar1Service {

    @Autowired
    private EventSource source;

    public void bar1() {
        source.output1().send(MessageBuilder.withPayload("bar1").build());
    }

}

@Service
public class Bar2Service {

    @Autowired
    private EventSource source;

    public void bar2() {
        source.output2().send(MessageBuilder.withPayload("bar2").build());
    }

}
user1831877
  • 113
  • 2
  • 10

1 Answers1

0

WIth a DirectChannel the binder invokes your listener on the same thread so preSend is appropriate here.

However, you don't have to mess with ThreadLocal, you can access headers using the method signature...

@StreamListener(Processor.INPUT)
public void handle(Foo foo, @Header("bar") String bar) {
    ...
}

EDIT

@EnableBinding(Processor.class)
public class So41459187Application {

    public static void main(String[] args) {
        SpringApplication.run(So41459187Application.class, args);
    }

    @StreamListener(Processor.INPUT)
    @SendTo(Processor.OUTPUT)
    public String handle(String in) {
        return in.toUpperCase();
    }

    @Configuration
    public static class Config {

        @Bean
        public BeanPostProcessor channelConfigurer() {
            return new BeanPostProcessor() {

                @Override
                public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                    return bean;
                }

                @Override
                public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                    if ("input".equals(beanName)) {
                        ((AbstractMessageChannel) bean).addInterceptor(new ChannelInterceptorAdapter() {

                            @Override
                            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                                System.out.println("PreSend on INPUT: " + message);
                                return message;
                            }

                        });
                    }
                    else if ("output".equals(beanName)) {
                        ((AbstractMessageChannel) bean).addInterceptor(new ChannelInterceptorAdapter() {

                            @Override
                            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                                System.out.println("PreSend on OUTPUT: " + message);
                                return message;
                            }

                        });
                    }
                    return bean;
                }

            };
        }
    }

}
Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Thanks for reply, appreciated it! I am using `ThreadLocal` is because there are several `handle` methods listening on different channels, so I was looking for a global way to retrieve information from headers for all `handle` methods. So that I don't have to put `@Headers` or `MessageHeaders` on every of `handle` methods. – user1831877 Jan 05 '17 at 04:41
  • And I have confirmed that `preSend` is appropriate. (Thanks again) But I am confused now. Both inbound and outbound channels will invoke `preSend` method. For inbound channel I have to extract information from headers and for outbound channel I need to set information in the headers to pass to consumers. How can I separate these two businesses? – user1831877 Jan 05 '17 at 04:41
  • Or maybe I can config different interceptors for producer and consumer on bindings? – user1831877 Jan 05 '17 at 06:48
  • Yes, you simply need to configure a different interceptor on each channel. – Gary Russell Jan 05 '17 at 14:12
  • Could you show me how can I do this in spring cloud stream? Since channel is auto generated in spring cloud stream and I cannot find a way to configure about this. – user1831877 Jan 05 '17 at 15:04
  • How are you currently configuring your interceptor? – Gary Russell Jan 05 '17 at 15:08
  • There are several ways to do it; I edited my answer with one of them. – Gary Russell Jan 05 '17 at 15:48
  • I am using `@GlobalChannelInterceptor` right now, cause I did not find a way to config interceptor. I have updated my setup for spring cloud stream in the question above. – user1831877 Jan 05 '17 at 15:50
  • When using global interceptors you can simply use the `patterns` property to determine which channel name(s) the interceptor is applied to. – Gary Russell Jan 05 '17 at 16:22
  • Thank you! Both solutions are perfect! – user1831877 Jan 06 '17 at 05:09
  • @user1831877 Can you share how the code looks like? i have a similar use case where i need to process headers for Messages coming into my SubscribableChannel and set a thread local. – java_geek May 13 '20 at 10:58
  • @GaryRussell Isnt the additional check for each bean name an overhead? Is there any alternate way of doing this through AOP; I found this link https://stackoverflow.com/questions/50682722/spring-boot-2-0-2-interception-of-cloud-stream-annotations-with-aop-not-working; However the ChannelInterceptorManager doesnt seem to be available in spring cloud stream 3.0.1. Please advise – java_geek May 13 '20 at 12:46
  • The bean post processor `postProcessAfterInitialization()` only runs once, during initialization to add the interceptors one time only. This is more efficient than AOP. – Gary Russell May 13 '20 at 13:46