问题
I tried to keep the title as explicit and simple as possible.
Basically, I need to intercept the usage of Cloud stream's @Input and @Output annotations. This is needed to automatically add a specific ChannelInterceptor to each MessageChannel. (The behaviour in the preSend method will be slightly different whether the message has been produced or consumed).
For example, if I declare this advice
@Around("@annotation(org.springframework.cloud.stream.annotation.Input)")
public Object interceptInput(ProceedingJoinPoint joinPoint) throws Throwable {
LOG.debug("Intercepted @Input from method : {}", joinPoint.getSignature());
Object returnValue = null;
try {
returnValue = joinPoint.proceed();
ChannelInterceptorManager.addInputInterceptor((AbstractMessageChannel)returnValue);
} catch (Exception e) {
LOG.warn("@Input error", e);
}
return returnValue;
}
and I declare this example class
@EnableBinding(Sink.class)
@Component
public class MyClass {
@StreamListener(Sink.INPUT)
public void handle(Object message) {
// preSend has been called before this method
}
}
This worked perfectly fine with Spring Boot 2.0.1, but not with Spring Boot 2.0.2 and I'm struggling to understand why.
I haven't tried other Cloud stream's annotations, but basic Aop works fine.
Keep in mind that this is meant to be used in a JAR, as such I don't know in advance the classes or the channel names which will be used, I need this to be automatic and transparent to the developer.
Thanks !
Edit : In case anyone reading this isn't familiar with Cloud stream, the Sink interface declares a method annotated with @Input, so enabling binding on it will do the trick.
回答1:
So, BPP doesn't fully solve the issue, as I need to differentiate MessageChannel created with @Input from those created with @Output. The MessageChannel bean does not carry this information. This is why I used Aop in the first place, to intercept those two annotations separately.
For insight : I have also thought of using @GlobalChannelInterceptor with patterns containing either "input" or "output", but that would mean enforcing such patterns to the end user. I'm keeping this solution as last resort, but I would like this process to be completely invisible and impact-less while using the jar. This is where AOP came in handy, but this new behaviour from 2.0.2 is certainly problematic in my case.
Edit : So the issue with the version change is the bean initialization order, for anyone having a similar issue with Spring boot 2.0.2. If you have control over each bean you need, I suggest you take a look at @DependsOn.
Ultimately, I solved my specific issue by using BeanPostProcessor instead of AOP to separate inputs from outputs, as suggested by @Oleg Zhurakousky. Below is a working method :
@Autowired
private AbstractBeanFactory beanFactory;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractMessageChannel) {
try {
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName);
Method method = beanDefinition.getResolvedFactoryMethod();
if (method != null) {
if (AnnotationUtils.findAnnotation(method, Input.class) != null) {
((AbstractMessageChannel)bean).addInterceptor(/*Your input ChannelInterceptor*/);
} else if (AnnotationUtils.findAnnotation(method, Output.class) != null) {
((AbstractMessageChannel)bean).addInterceptor(/*Your output ChannelInterceptor*/);
}
}
} catch (Exception e) {
// exception can be thrown by the bean factory
}
}
return bean;
}
回答2:
Not sure what happened between boot 2.0.1 and 2.0.2, but the above sounds like a pretty complex way of doing something simple. Why not just register BPP where you can add pre/post channel interceptors during initialization time.
来源:https://stackoverflow.com/questions/50682722/spring-boot-2-0-2-interception-of-cloud-stream-annotations-with-aop-not-working