diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Default.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Default.java new file mode 100644 index 0000000000..7596d2eb82 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Default.java @@ -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. + *

+ * 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 { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java index 679b579a3f..186281ac5e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java @@ -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) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java index a283bcebdd..2a9db2d3b9 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java @@ -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". diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java new file mode 100644 index 0000000000..92036f09d8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java @@ -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. + *

+ * The default converts are (declared exactly in this order): + *

+ * + * @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 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 converters, boolean registerDefaults) { + super(registerDefaults ? + Stream.concat(converters.stream(), initDefaults().stream()).collect(Collectors.toList()) + : converters); + } + + private static Collection initDefaults() { + List 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; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ObjectStringMessageConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ObjectStringMessageConverter.java new file mode 100644 index 0000000000..64644d174f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ObjectStringMessageConverter.java @@ -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. + *

+ * 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(); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java index 2bf31e0485..51e1f6c134 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java @@ -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 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 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 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 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 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()) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ContentTypeConversionTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ContentTypeConversionTests.java new file mode 100644 index 0000000000..e87725f054 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ContentTypeConversionTests.java @@ -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 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 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 sendData() { + return new AtomicReference<>(); + } + + @Bean + public MessageChannel serviceChannel(final AtomicReference 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(); + } + + } + +} diff --git a/src/reference/asciidoc/endpoint.adoc b/src/reference/asciidoc/endpoint.adoc index 1e384fe57b..3e165027e9 100644 --- a/src/reference/asciidoc/endpoint.adoc +++ b/src/reference/asciidoc/endpoint.adoc @@ -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 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 <> is available. [[async-polling]] ==== Asynchronous polling diff --git a/src/reference/asciidoc/service-activator.adoc b/src/reference/asciidoc/service-activator.adoc index 5e9a263fc2..88718e4eb2 100644 --- a/src/reference/asciidoc/service-activator.adoc +++ b/src/reference/asciidoc/service-activator.adoc @@ -18,7 +18,18 @@ To create a Service Activator, use the 'service-activator' element with the 'inp ---- -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 <> 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] diff --git a/src/reference/asciidoc/spel.adoc b/src/reference/asciidoc/spel.adoc index f4b7cef608..4e83df641c 100644 --- a/src/reference/asciidoc/spel.adoc +++ b/src/reference/asciidoc/spel.adoc @@ -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] ---- diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 91627e6024..156ff04670 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -19,6 +19,12 @@ See <> for more information. The new `AsyncHttpRequestExecutingMessageHandler` adds support for `AsyncRestTemplate` for outbound channel adapter and gateway. See <> 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 <> for more information. + + [[x5.0-general]] === General Changes @@ -36,6 +42,9 @@ See <> for more information. The `SmartLifecycleRoleController` now provides methods to obtain status of endpoints in roles. See <> 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 <> 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.