spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.35k stars 38.03k forks source link

Allow MessageHeaderAccessor to be created with existing headers #33153

Closed Nephery closed 2 months ago

Nephery commented 3 months ago

This issue was split from PR: https://github.com/spring-projects/spring-framework/pull/33137


Add a constructor that lets users create a MessageHeaderAccessor object given only a headers map.

This is a small optimization in high volume scenarios to avoid creating unnecessary Message<?> objects when you only have a headers map.

The specific use case I'm looking at, is being able to create a MessageHeaderAccessor object to process headers from a "consolidated headers" object (such as from the Rabbit Binder or Solace Binder) created for a Message<List<?>> consumed from Spring Cloud Stream's batched consumers.

e.g.: Being able to do something like this:

@Bean
Consumer<Message<List<Payload>>> input() {
    return batchMsg -> {
        List<Payload> batchedPayloads = batchMsg.getPayload();
        List<Map<String, Object>> batchedHeaders = (List<Map<String, Object>>) batchMsg.getHeaders().get(SolaceBinderHeaders.BATCHED_HEADERS);

        for (int i = 0; i < batchedPayloads.size(); i++) {
            Map<String, Object> headersMap = batchedHeaders.get(i);
            MessageHeaderAccessor accessor = null;
            if (headersMap instanceof MessageHeaders headers) {
                accessor = MessageHeaderAccessor.getAccessor(headers, null);

            }
            if (accessor == null || || !headers.isMutable()) {
                accessor = new MessageHeaderAccessor(headersMap);
            }

            // Do some logic with the accessor
        }
    };
}

With the current implementation, instead of just being able to do:

MessageHeaderAccessor accessor = new MessageHeaderAccessor(headersMap);

I would need to do this, which is pointless if the accessor's constructor will just extract the headers from the Message<?>:

MessageHeaderAccessor accessor = new MessageHeaderAccessor(new GenericMessage<?>(payload, headersMap));
Nephery commented 3 months ago

One problem with your change is that there are now two constructors with a single argument that are nullable so this will break backward compatibility.

@snicoll as an alternative solution, maybe you could create a public static MessageHeaderAccessor getMutableAccessor(Map<String, Object> headers, ...) which delegates to a private MessageHeaderAccessor(Map<String, Object> headers) constructor?

Then either change the new function's name or add a second parameter (like @Nullable Class<T> requiredType) so that it doesn't conflict with the existing getMutableAccessor(Message<?> message)?