INT-4225: Add MessageConverters Infrastructure
JIRA: https://jira.spring.io/browse/INT-4225 * To allow flexible way to convert incoming messages for the target arguments in method invocation add `ConfigurableCompositeMessageConverter` global bean under `ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME` * Retrieve that bean for the `MessageHandlerMethodFactor` in the `MessagingMethodInvokerHelper` to enrich afterwards target `HandlerMethodArgumentResolver`s * The `ContentTypeConversionTests` demonstrates conversion incoming JOSN `string` into the target POJO argument in the service method * Add `@Primary` support * Add documentation about `@Primary` and `contentType` conversion INT-1800: Add MTOM Support for WS JIRA: https://jira.spring.io/browse/INT-1800 The Simple WebService Inbound and Outbound Gateways can operate with `WebServiceMessage`s directly. This allows to create those messages manually and add attachments to them. Or, on the other hand, process incoming messages with attachments manually. For this purpose the `SimpleWebServiceOutboundGateway` is supplied with new `extractPayload` property. Also the `UnmarshallingTransformer` can now process `MimeMessage` as payload to unmarshal it into object graph with attachments if that INT-4225: Add MessageConverters infrastructure JIRA: https://jira.spring.io/browse/INT-4225 * To allow flexible way to convert incoming messages for the target arguments in method invocation add `ConfigurableCompositeMessageConverter` global bean under `ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME` * Retrieve that bean for the `MessageHandlerMethodFactor` in the `MessagingMethodInvokerHelper` to enrich afterwards target `HandlerMethodArgumentResolver`s * The `ContentTypeConversionTests` demonstrates conversion incoming JOSN `string` into the target POJO argument in the service method * Add `@Primary` support * Add documentation about `@Primary` and `contentType` conversion Doc Polishing Change `@Primary` to `@Default` Doc Polishing
This commit is contained in:
committed by
Gary Russell
parent
5fe605470b
commit
28e8f23fd0
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.annotation;
|
||||
|
||||
import static java.lang.annotation.ElementType.CONSTRUCTOR;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the class member has some default meaning.
|
||||
* <p>
|
||||
* The target parsing logic may vary.
|
||||
* One of the use-cases is service with several methods which are
|
||||
* selected for invocation at runtime by some condition.
|
||||
* The method with this {@link Default} annotation may mean
|
||||
* a fallback option when no one other method meets condition.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Default {
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,8 +47,10 @@ import org.springframework.integration.config.annotation.MessagingAnnotationPost
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.context.IntegrationProperties;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
|
||||
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -86,19 +88,20 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
|
||||
*/
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
this.registerImplicitChannelCreator(registry);
|
||||
this.registerIntegrationConfigurationBeanFactoryPostProcessor(registry);
|
||||
this.registerIntegrationEvaluationContext(registry);
|
||||
this.registerIntegrationProperties(registry);
|
||||
this.registerHeaderChannelRegistry(registry);
|
||||
this.registerGlobalChannelInterceptorProcessor(registry);
|
||||
this.registerBuiltInBeans(registry);
|
||||
this.registerDefaultConfiguringBeanFactoryPostProcessor(registry);
|
||||
this.registerDefaultDatatypeChannelMessageConverter(registry);
|
||||
registerImplicitChannelCreator(registry);
|
||||
registerIntegrationConfigurationBeanFactoryPostProcessor(registry);
|
||||
registerIntegrationEvaluationContext(registry);
|
||||
registerIntegrationProperties(registry);
|
||||
registerHeaderChannelRegistry(registry);
|
||||
registerGlobalChannelInterceptorProcessor(registry);
|
||||
registerBuiltInBeans(registry);
|
||||
registerDefaultConfiguringBeanFactoryPostProcessor(registry);
|
||||
registerDefaultDatatypeChannelMessageConverter(registry);
|
||||
registerArgumentResolverMessageConverter(registry);
|
||||
if (importingClassMetadata != null) {
|
||||
this.registerMessagingAnnotationPostProcessors(importingClassMetadata, registry);
|
||||
registerMessagingAnnotationPostProcessors(importingClassMetadata, registry);
|
||||
}
|
||||
this.registerMessageBuilderFactory(registry);
|
||||
registerMessageBuilderFactory(registry);
|
||||
IntegrationConfigUtils.registerRoleControllerDefinitionIfNecessary(registry);
|
||||
}
|
||||
|
||||
@@ -405,6 +408,21 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the default {@link CompositeMessageConverter} for argument resolvers
|
||||
* during handler method invocation.
|
||||
* @param registry the registry.
|
||||
*/
|
||||
private void registerArgumentResolverMessageConverter(BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
|
||||
BeanDefinitionBuilder postProcessorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ConfigurableCompositeMessageConverter.class);
|
||||
registry.registerBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
|
||||
postProcessorBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void registerMessageBuilderFactory(BeanDefinitionRegistry registry) {
|
||||
boolean alreadyRegistered = false;
|
||||
if (registry instanceof ListableBeanFactory) {
|
||||
|
||||
@@ -81,8 +81,11 @@ public abstract class IntegrationContextUtils {
|
||||
|
||||
public static final String INTEGRATION_GRAPH_SERVER_BEAN_NAME = "integrationGraphServer";
|
||||
|
||||
|
||||
public static final String SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME = "spelPropertyAccessorRegistrar";
|
||||
|
||||
public static final String ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME = "integrationArgumentResolverMessageConverter";
|
||||
|
||||
/**
|
||||
* @param beanFactory BeanFactory for lookup, must not be null.
|
||||
* @return The {@link MetadataStore} bean whose name is "metadataStore".
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.support.converter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.integration.support.json.JacksonJsonUtils;
|
||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.messaging.converter.GenericMessageConverter;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
|
||||
/**
|
||||
* A {@link CompositeMessageConverter} extension with some default {@link MessageConverter}s
|
||||
* which can be overridden with the given converters
|
||||
* or added in the end of target {@code converters} collection.
|
||||
* <p>
|
||||
* The default converts are (declared exactly in this order):
|
||||
* <ul>
|
||||
* <li> {@link MappingJackson2MessageConverter} if Jackson processor is present in classpath;
|
||||
* <li> {@link ByteArrayMessageConverter}
|
||||
* <li> {@link ObjectStringMessageConverter}
|
||||
* <li> {@link GenericMessageConverter}
|
||||
* </ul>
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ConfigurableCompositeMessageConverter extends CompositeMessageConverter {
|
||||
|
||||
/**
|
||||
* Create an instance with the default converters.
|
||||
*/
|
||||
public ConfigurableCompositeMessageConverter() {
|
||||
super(initDefaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the given converters and without defaults.
|
||||
* @param converters the converters to use
|
||||
*/
|
||||
public ConfigurableCompositeMessageConverter(Collection<MessageConverter> converters) {
|
||||
this(converters, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the given converters and with defaults in the end.
|
||||
* @param converters the converters to use
|
||||
* @param registerDefaults register or not default converts
|
||||
*/
|
||||
public ConfigurableCompositeMessageConverter(Collection<MessageConverter> converters, boolean registerDefaults) {
|
||||
super(registerDefaults ?
|
||||
Stream.concat(converters.stream(), initDefaults().stream()).collect(Collectors.toList())
|
||||
: converters);
|
||||
}
|
||||
|
||||
private static Collection<MessageConverter> initDefaults() {
|
||||
List<MessageConverter> converters = new LinkedList<>();
|
||||
|
||||
if (JacksonJsonUtils.isJackson2Present()) {
|
||||
converters.add(new MappingJackson2MessageConverter());
|
||||
}
|
||||
converters.add(new ByteArrayMessageConverter());
|
||||
converters.add(new ObjectStringMessageConverter());
|
||||
|
||||
// TODO do we port it together with MessageConverterUtils ?
|
||||
// converters.add(new JavaSerializationMessageConverter());
|
||||
|
||||
converters.add(new GenericMessageConverter());
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.support.converter;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
|
||||
/**
|
||||
* A {@link StringMessageConverter} extension to convert any object to string.
|
||||
* <p>
|
||||
* Delegates to super when payload is {@code byte[]} or {@code String}.
|
||||
* Performs {@link Object#toString()} in other cases.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ObjectStringMessageConverter extends StringMessageConverter {
|
||||
|
||||
@Override
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
Object payload = message.getPayload();
|
||||
if (payload instanceof String || payload instanceof byte[]) {
|
||||
return super.convertFromInternal(message, targetClass, conversionHint);
|
||||
}
|
||||
else {
|
||||
return payload.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,8 +57,10 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.annotation.Default;
|
||||
import org.springframework.integration.annotation.Payloads;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.handler.support.CollectionArgumentResolver;
|
||||
import org.springframework.integration.handler.support.MapArgumentResolver;
|
||||
import org.springframework.integration.handler.support.PayloadExpressionArgumentResolver;
|
||||
@@ -67,6 +69,7 @@ import org.springframework.integration.support.MutableMessage;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.converter.MessageConversionException;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
@@ -158,6 +161,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
|
||||
private boolean useSpelInvoker;
|
||||
|
||||
private HandlerMethod defaultHandlerMethod;
|
||||
|
||||
public MessagingMethodInvokerHelper(Object targetObject, Method method, Class<?> expectedType,
|
||||
boolean canProcessMessageList) {
|
||||
@@ -271,6 +275,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, method);
|
||||
this.handlerMethod = new HandlerMethod(invocableHandlerMethod, canProcessMessageList);
|
||||
this.defaultHandlerMethod = null;
|
||||
}
|
||||
catch (IneligibleMethodException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
@@ -399,15 +404,29 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
customArgumentResolvers.add(mapArgumentResolver);
|
||||
|
||||
this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers);
|
||||
|
||||
if (getBeanFactory() != null &&
|
||||
getBeanFactory()
|
||||
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
|
||||
this.messageHandlerMethodFactory
|
||||
.setMessageConverter(getBeanFactory()
|
||||
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
|
||||
MessageConverter.class));
|
||||
}
|
||||
|
||||
this.messageHandlerMethodFactory.afterPropertiesSet();
|
||||
prepareEvaluationContext();
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
HandlerMethod candidate = this.findHandlerMethodForParameters(parameters);
|
||||
Expression expression = candidate.expression;
|
||||
|
||||
HandlerMethod candidate = findHandlerMethodForParameters(parameters);
|
||||
if (candidate == null) {
|
||||
candidate = this.defaultHandlerMethod;
|
||||
}
|
||||
Assert.notNull(candidate, "No candidate methods found for messages.");
|
||||
Expression expression = candidate.expression;
|
||||
|
||||
T result;
|
||||
if (this.useSpelInvoker || candidate.spelOnly) {
|
||||
@@ -443,8 +462,8 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
if (!(e.getCause() instanceof IllegalArgumentException) ||
|
||||
!e.getStackTrace()[0].getClassName().equals(InvocableHandlerMethod.class.getName()) ||
|
||||
(!"argument type mismatch".equals(e.getCause().getMessage()) &&
|
||||
// JVM generates GeneratedMethodAccessor### after several calls with less error checking
|
||||
!e.getCause().getMessage().startsWith("java.lang.ClassCastException@"))) {
|
||||
// JVM generates GeneratedMethodAccessor### after several calls with less error checking
|
||||
!e.getCause().getMessage().startsWith("java.lang.ClassCastException@"))) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -545,6 +564,11 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (AnnotationUtils.getAnnotation(method1, Default.class) != null) {
|
||||
Assert.state(this.defaultHandlerMethod == null,
|
||||
() -> "Only one method can be @Default, but there are more for: " + targetObject);
|
||||
this.defaultHandlerMethod = handlerMethod1;
|
||||
}
|
||||
Class<?> targetParameterType = handlerMethod1.getTargetParameterType();
|
||||
if (matchesAnnotation || annotationType == null) {
|
||||
if (handlerMethod1.isMessageMethod()) {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.annotation.Default;
|
||||
import org.springframework.integration.annotation.Gateway;
|
||||
import org.springframework.integration.annotation.GatewayHeader;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.dsl.channel.MessageChannels;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.support.ChannelInterceptorAdapter;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
public class ContentTypeConversionTests {
|
||||
|
||||
@Autowired
|
||||
private AtomicReference<Object> sendData;
|
||||
|
||||
@Autowired
|
||||
private ServiceGateway serviceGateway;
|
||||
|
||||
@Test
|
||||
public void testContentTypeBasedConversion() {
|
||||
String json = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42}";
|
||||
TestPerson testPerson = this.serviceGateway.convertJsonToPerson(json);
|
||||
|
||||
assertEquals(json, this.sendData.get());
|
||||
assertEquals("John", testPerson.getFirstName());
|
||||
assertEquals(42, testPerson.getAge());
|
||||
|
||||
Map<String, String> map = Collections.singletonMap("foo", "bar");
|
||||
String result = this.serviceGateway.mapToString(map);
|
||||
assertEquals(map.toString(), result);
|
||||
}
|
||||
|
||||
@MessagingGateway
|
||||
interface ServiceGateway {
|
||||
|
||||
@Gateway(headers = @GatewayHeader(name = MessageHeaders.CONTENT_TYPE, value = "application/json"))
|
||||
TestPerson convertJsonToPerson(String jsonPerson);
|
||||
|
||||
String mapToString(Map<?, ?> map);
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public AtomicReference<Object> sendData() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageChannel serviceChannel(final AtomicReference<Object> sendData) {
|
||||
return MessageChannels.direct()
|
||||
.interceptor(new ChannelInterceptorAdapter() {
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
sendData.set(message.getPayload());
|
||||
return super.preSend(message, channel);
|
||||
}
|
||||
|
||||
})
|
||||
.get();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow serviceFlow() {
|
||||
return IntegrationFlows.from(ServiceGateway.class)
|
||||
.channel("serviceChannel")
|
||||
.handle(this)
|
||||
.get();
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
@Default
|
||||
public TestPerson handleJson(TestPerson person) {
|
||||
return person;
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
public String handleMap(@Payload Map<?, ?> map) {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -437,7 +437,7 @@ Throughout the reference manual, you will also see specific configuration and im
|
||||
In the case of an Object, such a parameter will be mapped to a Message payload or part of the payload or header (when using the Spring Expression Language).
|
||||
However there are times when the type of input parameter of the endpoint method does not match the type of the payload or its part.
|
||||
In this scenario we need to perform type conversion.
|
||||
Spring Integration provides a convenient way for registering type converters (using the Spring 3.x ConversionService) within its own instance of a conversion service bean named _integrationConversionService_.
|
||||
Spring Integration provides a convenient way for registering type converters (using the Spring `ConversionService`) within its own instance of a conversion service bean named _integrationConversionService_.
|
||||
That bean is automatically created as soon as the first converter is defined using the Spring Integration infrastructure.
|
||||
To register a Converter all you need is to implement `org.springframework.core.convert.converter.Converter`, `org.springframework.core.convert.converter.GenericConverter` or `org.springframework.core.convert.converter.ConverterFactory`.
|
||||
|
||||
@@ -510,6 +510,38 @@ However, if you do want to use the Spring _conversionService_ as the Spring Inte
|
||||
In this case the _conversionService_'s Converters will be available for Spring Integration runtime conversion.
|
||||
=====
|
||||
|
||||
[[content-type-conversion]]
|
||||
==== Content Type Conversion
|
||||
|
||||
Starting with _version 5.0_, by default, the method invocation mechanism is based on the `org.springframework.messaging.handler.invocation.InvocableHandlerMethod` infrastructure.
|
||||
Its `HandlerMethodArgumentResolver` implementations (e.g. `PayloadArgumentResolver` and `MessageMethodArgumentResolver`) can use the `MessageConverter` abstraction to convert an incoming `payload` to the target method argument type.
|
||||
The conversion can be based on the `contentType` message header.
|
||||
For this purpose Spring Integration provides the `ConfigurableCompositeMessageConverter` that delegates to a list of registered converters to be invoked until one of them returns a non-null result.
|
||||
By default this converter provides (in strict order):
|
||||
|
||||
* `MappingJackson2MessageConverter` if Jackson processor is present in classpath;
|
||||
* `ByteArrayMessageConverter`
|
||||
* `ObjectStringMessageConverter`
|
||||
* `GenericMessageConverter`
|
||||
|
||||
Please, consult their JavaDocs for more information about their purpose and appropriate `contentType` value for conversion.
|
||||
The `ConfigurableCompositeMessageConverter` is used because it can be be supplied with any other `MessageConverter` s including or excluding above mentioned default converters and registered as an appropriate bean in the application context overriding the default one:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean(name = IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)
|
||||
public ConfigurableCompositeMessageConverter compositeMessageConverter() {
|
||||
List<MessageConverter> converters =
|
||||
Arrays.asList(new MarshallingMessageConverter(jaxb2Marshaller()),
|
||||
new JavaSerializationMessageConverter());
|
||||
return new ConfigurableCompositeMessageConverter(converters);
|
||||
}
|
||||
----
|
||||
And those two new converters will be registered in the composite before the defaults.
|
||||
You can also not use a `ConfigurableCompositeMessageConverter`, but provide your own `MessageConverter` by registering a bean with the name `integrationArgumentResolverMessageConverter` (`IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME` constant).
|
||||
|
||||
NOTE: The `MessageConverter`-based (including `contentType` header) conversion isn't available when using SpEL method invocation.
|
||||
In this case, only regular class to class conversion mentioned above in the <<payload-type-conversion>> is available.
|
||||
|
||||
[[async-polling]]
|
||||
==== Asynchronous polling
|
||||
|
||||
@@ -18,7 +18,18 @@ To create a Service Activator, use the 'service-activator' element with the 'inp
|
||||
<int:service-activator input-channel="exampleChannel" ref="exampleHandler"/>
|
||||
----
|
||||
|
||||
The configuration above assumes that "exampleHandler" either contains a single method annotated with the @ServiceActivator annotation or that it contains only one public method at all.
|
||||
The configuration above selects all methods from the `exampleHandler` which meet one of the Messaging requirements:
|
||||
|
||||
- annotated with `@ServiceActivator`;
|
||||
- is `public`;
|
||||
- not `void` return if `requiresReply == true`.
|
||||
|
||||
The target method for invocation at runtime is selected for each request message by their `payload` type.
|
||||
Or as a fallback to `Message<?>` type if such a method is present on target class.
|
||||
|
||||
Starting with _version 5.0_, one service method can be marked with the `@org.springframework.integration.annotation.Default` as a fallback for all non-matching cases.
|
||||
This can be useful when using <<content-type-conversion>> with the target method being invoked after conversion.
|
||||
|
||||
To delegate to an explicitly defined method of any object, simply add the "method" attribute.
|
||||
|
||||
[source,xml]
|
||||
|
||||
@@ -104,7 +104,7 @@ With this sample:
|
||||
|
||||
* That `EvaluationContext` instance is injected into the `ExpressionEvaluatingTransformer` bean.
|
||||
|
||||
To provide a SpEL Function via Java Configuration you should declare a `SpelFunctionFactoryBean` bean for each function.
|
||||
To provide a SpEL Function via Java Configuration, you should declare a `SpelFunctionFactoryBean` bean for each function.
|
||||
The sample above can be configured as follows:
|
||||
|
||||
[source,java]
|
||||
@@ -168,8 +168,8 @@ Instead of configuring the factory bean above, simply add one or more of these c
|
||||
|
||||
With this sample, two custom `PropertyAccessor` s will be injected to the `EvaluationContext` in the order that they are declared.
|
||||
|
||||
To provide `PropertyAccessor` s via Java Configuration you should declare `SpelPropertyAccessorRegistrar` bean with the `spelPropertyAccessorRegistrar` (the `IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME` constant) name.
|
||||
The sample above can be configured like:
|
||||
To provide `PropertyAccessor` s via Java Configuration, you should declare a `SpelPropertyAccessorRegistrar` bean with the name `spelPropertyAccessorRegistrar` (`IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME` constant).
|
||||
The sample above can be configured as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@@ -19,6 +19,12 @@ See <<mongodb-outbound-gateway>> for more information.
|
||||
The new `AsyncHttpRequestExecutingMessageHandler` adds support for `AsyncRestTemplate` for outbound channel adapter and gateway.
|
||||
See <<AsyncHttpRequestExecutingMessageHandler>> for more information.
|
||||
|
||||
==== Content Type Conversion
|
||||
|
||||
Now that we use the new `InvocableHandlerMethod` -based infrastructure for service method invocations, we can perform `contentType` conversion from payload to target method argument.
|
||||
See <<content-type-conversion>> for more information.
|
||||
|
||||
|
||||
[[x5.0-general]]
|
||||
=== General Changes
|
||||
|
||||
@@ -36,6 +42,9 @@ See <<tx-handle-message-advice>> for more information.
|
||||
The `SmartLifecycleRoleController` now provides methods to obtain status of endpoints in roles.
|
||||
See <<endpoint-roles>> for more information.
|
||||
|
||||
When targeting POJO objects as message handlers, one of the service methods can now be marked with the `@Default` annotation to provide a fallback mechanism for non-matched conditions.
|
||||
See <<service-activator-namespace>> for more information.
|
||||
|
||||
==== JMS Changes
|
||||
|
||||
Previously, Spring Integration JMS XML configuration used a default bean name `connectionFactory` for the JMS Connection Factory, allowing the property to be omitted from component definitions.
|
||||
|
||||
Reference in New Issue
Block a user