springwolf / springwolf-core

Automated documentation for event-driven applications built with Spring Boot
https://www.springwolf.dev
Apache License 2.0
250 stars 71 forks source link

Support for @KafkaListener beanRef #873

Open ruskaof opened 3 months ago

ruskaof commented 3 months ago

Describe the feature request According to the spring-kafka docs (https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/listener-annotation.html) it is possible to reference the current bean of a method annotated @KafkaListener using __listener, but this pseudo bean is not resolved by springwolf.

Example:

@Component
public class Listener {

    private final String topic = "topicname";
    private final String group = "groupname";

    @KafkaListener(topics = "#{__listener.topic}", groupId = "#{__listener.group}")
    public void listen(Object message) {

    }

    public String getTopic() {
        return this.topic;
    }

    public String getGroup() {
        return this.group;
    }
}

The kafka listener itself is created successfully in this case, but there is a SpelEvaluationException thrown in springwolf code:

2024-07-27T21:37:00.479+03:00 ERROR 9656 --- [test] [           main] i.g.s.c.a.o.DefaultOperationsService     : An error was encountered during operation scanning with io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner@6bee793f: Expression parsing failed

org.springframework.beans.factory.BeanExpressionException: Expression parsing failed
    at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:186) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.beans.factory.config.EmbeddedValueResolver.resolveStringValue(EmbeddedValueResolver.java:56) ~[spring-beans-6.1.11.jar:6.1.11]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[na:na]
    at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[na:na]
    at io.github.springwolf.plugins.kafka.asyncapi.scanners.common.KafkaListenerUtil.getChannelName(KafkaListenerUtil.java:34) ~[springwolf-kafka-1.4.0.jar:na]
    at io.github.springwolf.plugins.kafka.asyncapi.scanners.bindings.KafkaBindingFactory.getChannelName(KafkaBindingFactory.java:23) ~[springwolf-kafka-1.4.0.jar:na]
    at io.github.springwolf.plugins.kafka.asyncapi.scanners.bindings.KafkaBindingFactory.getChannelName(KafkaBindingFactory.java:17) ~[springwolf-kafka-1.4.0.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner.mapMethodToOperation(SpringAnnotationMethodLevelOperationsScanner.java:73) ~[springwolf-core-1.4.0.jar:na]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) ~[na:na]
    at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1715) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[na:na]
    at io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner.mapToOperations(SpringAnnotationOperationsScanner.java:31) ~[springwolf-core-1.4.0.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner.scan(SpringAnnotationOperationsScanner.java:25) ~[springwolf-core-1.4.0.jar:na]
    at io.github.springwolf.core.asyncapi.operations.DefaultOperationsService.findOperations(DefaultOperationsService.java:34) ~[springwolf-core-1.4.0.jar:na]
    at io.github.springwolf.core.asyncapi.DefaultAsyncApiService.initAsyncAPI(DefaultAsyncApiService.java:71) ~[springwolf-core-1.4.0.jar:na]
    at io.github.springwolf.core.asyncapi.DefaultAsyncApiService.getAsyncAPI(DefaultAsyncApiService.java:42) ~[springwolf-core-1.4.0.jar:na]
    at io.github.springwolf.core.SpringwolfInitApplicationListener.onApplicationEvent(SpringwolfInitApplicationListener.java:31) ~[springwolf-core-1.4.0.jar:na]
    at io.github.springwolf.core.SpringwolfInitApplicationListener.onApplicationEvent(SpringwolfInitApplicationListener.java:17) ~[springwolf-core-1.4.0.jar:na]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:452) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:385) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.boot.context.event.EventPublishingRunListener.ready(EventPublishingRunListener.java:109) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplicationRunListeners.lambda$ready$6(SpringApplicationRunListeners.java:80) ~[spring-boot-3.3.2.jar:3.3.2]
    at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplicationRunListeners.ready(SpringApplicationRunListeners.java:80) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:349) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.2.jar:3.3.2]
    at com.example.test.TestApplication.main(TestApplication.java:10) ~[main/:na]
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field '__listener' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:229) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:112) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:100) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:60) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:96) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273) ~[spring-expression-6.1.11.jar:6.1.11]
    at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:183) ~[spring-context-6.1.11.jar:6.1.11]
    ... 54 common frames omitted

Motivation This feature should be implemented to make springwolf more compatible with org.springframework.kafka.annotation.KafkaListener annotation.

Technical details I haven't studied the source code of springwolf enough to know how to implement this.

Describe alternatives you've considered I can always replace usages of the __listener bean to something else.

github-actions[bot] commented 3 months ago

Welcome to Springwolf. Thanks a lot for reporting your first issue. Please check out our contributors guide and feel free to join us on discord.

timonback commented 3 months ago

Hi @ruskaof, Thank you for the feature request and sharing this new KafkaListener feature.

Feel free to contribute, a good starting point is likely the KafkaListenerUtil in the kafka-plugin at https://github.com/springwolf/springwolf-core/blob/f0a0fccabf4cb2e20bb58c7e8493791424401dfe/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/scanners/common/KafkaListenerUtil.java#L29

We are happy to assist, either here on GitHub or on Discord.