Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RabbitListener does not include inferrer value when injecting header object #1441

Closed
nightknight77 opened this issue Mar 23, 2022 · 4 comments · Fixed by #1442
Closed

RabbitListener does not include inferrer value when injecting header object #1441

nightknight77 opened this issue Mar 23, 2022 · 4 comments · Fixed by #1442

Comments

@nightknight77
Copy link

nightknight77 commented Mar 23, 2022

Version:
Spring Boot 2.6 - spring starter

############

Description:

The documentation of the RabbitListener annotation suggests multiple ways on how to retrieve the message headers. If you use MessageHeaders and inject it as an argument for getting access to all headers, then the inferrer header (which in my understanding contains the consumer model) is not provided with a value anymore and message converter implementations like the Jackson2JsonMessageConverter are then using the TYPE_ID property and convert it into the given domain model. That property is provided by the producer and usually contains the fully qualified package name, so the converter on the consumer side looks for the domain model of the producer. If the consumer has its model not in the exact same package structure, it throws an ClassNotFound exception. This is confusing because if you cut the MessageHeader object and instead inject a simple Map<String, Object> and annotate that with @headers (also suggested in the docs) then it works again and the inferrer value is provided. So my guess is this behaviour is not intended.

Steps to reproduce the behavior:

  1. Use two models for json conversion. The package structure must differ.
  2. Add next to your model an additional parameter of type MessageHeaders.
  3. Receive a message on consumer side.

Actual behavior
ClassnotFound exception is thrown and converter cannot convert into inferrer type.

Expected behavior
Inferrer type is provided and converts successfully into consumers model.

Sample
A short sample will be provided soon.

@garyrussell
Copy link
Contributor

Thanks for reporting; reproduced:

@SpringBootApplication
public class Rgh1441Application {

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

	@RabbitListener(queues = "foo")
	public void listen1(Foo foo, @Headers Map<String, Object> headers) {
		System.out.println("1: " + foo);
	}

	@RabbitListener(queues = "bar")
	public void listen2(Foo foo, MessageHeaders headers) {
		System.out.println("2: " + foo);
	}

	@Bean
	ApplicationRunner runner(RabbitTemplate template, AbstractRabbitListenerContainerFactory<?> factory) {
		factory.setMessageConverter(converter());
		return args -> {
			template.convertAndSend("foo", "{\"bar\":\"baz\"}", msg -> {
				msg.getMessageProperties().setContentType("application/json");
				return msg;
			});
			template.convertAndSend("bar", "{\"bar\":\"baz\"}", msg -> {
				msg.getMessageProperties().setContentType("application/json");
				return msg;
			});
		};
	}

	Jackson2JsonMessageConverter converter() {
		return new Jackson2JsonMessageConverter();
	}

	public static class Foo {

		private String bar;

		public String getBar() {
			return this.bar;
		}

		public void setBar(String bar) {
			this.bar = bar;
		}

		@Override
		public String toString() {
			return "Foo [bar=" + this.bar + "]";
		}

	}

}
1: Foo [bar=baz]
...
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method could not be invoked with the incoming message
Endpoint handler details:
Method [public void com.example.demo.Rgh1441Application.listen2(com.example.demo.Rgh1441Application$Foo,org.springframework.messaging.MessageHeaders)]
...
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [java.util.LinkedHashMap] to [com.example.demo.Rgh1441Application$Foo] for GenericMessage [payload={bar=baz}, headers={amqp_receivedDeliveryMode=PERSISTENT, amqp_receivedRoutingKey=bar, amqp_contentEncoding=UTF-8, amqp_deliveryTag=1, amqp_consumerQueue=bar, amqp_redelivered=false, id=b0a6eec6-b59b-a887-7622-1e56818a6201, amqp_consumerTag=amq.ctag-0Op6ygNvKbB523G-2fjZyA, amqp_lastInBatch=false, contentType=application/json, timestamp=1648065323359}]

@garyrussell
Copy link
Contributor

The issue is in the code that determines whether the parameter is a payload or not; since we have 2 non-annotated parameters (and there is no special logic for MessageHeaders, the code can't determine which one should be the payload.

The solution is to also add @Headers to the MessageHeaders parameter...

@RabbitListener(queues = "foo")
public void listen1(Foo foo, @Headers Map<String, Object> headers) {
	System.out.println("1: " + foo);
}

@RabbitListener(queues = "bar")
public void listen2(@Payload Foo foo, @Headers MessageHeaders headers) {
	System.out.println("2: " + foo);
}
1: Foo [bar=baz]
2: Foo [bar=baz]

@nightknight77
Copy link
Author

nightknight77 commented Mar 23, 2022

I see but then the documentation is misleading.

`...Annotated methods are allowed to have flexible signatures similar to what MessageMapping provides, that is
....
@Payload-annotated method arguments including the support of validation

@Header-annotated method arguments to extract a specific header value, including standard AMQP headers defined by AmqpHeaders

@Headers-annotated argument that must also be assignable to java.util.Map for getting access to all headers.

MessageHeaders arguments for getting access to all headers.
...`

Anyway thanks for your efforts!

@garyrussell
Copy link
Contributor

Right; I am going to fix it; I just wanted to tell you how to resolve it without any framework changes.

@garyrussell garyrussell modified the milestones: Backlog, 3.0.0-M3 Mar 23, 2022
garyrussell added a commit to garyrussell/spring-amqp that referenced this issue Mar 23, 2022
Resolves spring-projects#1441

Previously, `MessageHeaders` had to be annotated with `@Headers` so that
it was ignored during payload parameter resolution; otherwise it caused
ambiguity.

Ignore `MessageHeaders` even when not so annotated.

Also fix some tests that were checking the same topic and count down latch so were
unconditionally passing.

Change one of those tests to verify the fix.

**cherry-pick to 2.4.x, 2.3.x**
garyrussell added a commit to garyrussell/spring-amqp that referenced this issue Mar 23, 2022
Resolves spring-projects#1441

Previously, `MessageHeaders` had to be annotated with `@Headers` so that
it was ignored during payload parameter resolution; otherwise it caused
ambiguity.

Ignore `MessageHeaders` even when not so annotated.

Also fix some tests that were checking the same topic and count down latch so were
unconditionally passing.

Change one of those tests to verify the fix.

**cherry-pick to 2.4.x, 2.3.x**
artembilan pushed a commit that referenced this issue Mar 29, 2022
Resolves #1441

Previously, `MessageHeaders` had to be annotated with `@Headers` so that
it was ignored during payload parameter resolution; otherwise it caused
ambiguity.

Ignore `MessageHeaders` even when not so annotated.

Also fix some tests that were checking the same topic and count down latch so were
unconditionally passing.

Change one of those tests to verify the fix.

**cherry-pick to 2.4.x, 2.3.x**
artembilan pushed a commit that referenced this issue Mar 29, 2022
Resolves #1441

Previously, `MessageHeaders` had to be annotated with `@Headers` so that
it was ignored during payload parameter resolution; otherwise it caused
ambiguity.

Ignore `MessageHeaders` even when not so annotated.

Also fix some tests that were checking the same topic and count down latch so were
unconditionally passing.

Change one of those tests to verify the fix.

**cherry-pick to 2.4.x, 2.3.x**
artembilan pushed a commit that referenced this issue Mar 29, 2022
Resolves #1441

Previously, `MessageHeaders` had to be annotated with `@Headers` so that
it was ignored during payload parameter resolution; otherwise it caused
ambiguity.

Ignore `MessageHeaders` even when not so annotated.

Also fix some tests that were checking the same topic and count down latch so were
unconditionally passing.

Change one of those tests to verify the fix.

**cherry-pick to 2.4.x, 2.3.x**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants