Some Java DSL improvements

* Register `BeanDefinition`s for DSL components.
That way we get a gain when the `BeanDefinition` is involved,
e.g. Spring Cloud Function uses this approach to determine the generics
of the target `Function` class
* This programmatic `BeanDefinition` registration approach
(available since SF-5.0) allows us to avoid some manual lifecycle processes:
bean initialization, autowiring, event listener registration etc.
* Rework `IntegrationFlowContext` to register and remove `BeanDefinition`s as well
* Remove unused `registerComponents`  flag from the `StandardIntegrationFlow`.
With the proper logic in the `IntegrationFlowContext` it does not make sense any more
* Remove redundant `AnnotationGatewayProxyFactoryBean` in the `IntegrationFlows`;
populate `defaultRequestChannel` directly to the `GatewayProxyFactoryBean` instance
* Add an overloaded `IntegrationFlows.from(Class, String)` to allow to specify an
explicit bean name for the target gateway proxy
* Extract and populate bean name from the `@MessagingGateway.name()` in the
`AnnotationGatewayProxyFactoryBean`
* Stop lifecycles in the `AmqpTests` after using for proper test suit shutdown
This commit is contained in:
Artem Bilan
2017-10-25 09:12:53 -04:00
committed by Gary Russell
parent b69bbdc43b
commit e6225926c4
7 changed files with 120 additions and 107 deletions

View File

@@ -40,6 +40,7 @@ import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.amqp.channel.AbstractAmqpChannel;
@@ -91,13 +92,19 @@ public class AmqpTests {
@Qualifier("amqpInboundGatewayContainer")
private SimpleMessageListenerContainer amqpInboundGatewayContainer;
@Autowired
private Lifecycle asyncOutboundGateway;
@AfterClass
public static void tearDown() {
brokerRunning.removeTestQueues();
}
@Test
public void testAmqpInboundGatewayFlow() throws Exception {
public void testAmqpInboundGatewayFlow() {
assertNotNull(this.amqpInboundGatewayContainer);
assertSame(this.amqpTemplate, TestUtils.getPropertyValue(this.amqpInboundGateway, "amqpTemplate"));
Object result = this.amqpTemplate.convertSendAndReceive(this.amqpQueue.getName(), "world");
assertEquals("HELLO WORLD", result);
@@ -109,9 +116,6 @@ public class AmqpTests {
((RabbitTemplate) this.amqpTemplate).setReceiveTimeout(10000);
result = this.amqpTemplate.receiveAndConvert("defaultReplyTo");
assertEquals("HELLO WORLD", result);
assertSame(this.amqpTemplate, TestUtils.getPropertyValue(this.amqpInboundGateway, "amqpTemplate"));
assertNotNull(this.amqpInboundGatewayContainer);
}
@Autowired
@@ -141,6 +145,8 @@ public class AmqpTests {
assertNotNull(receive);
assertEquals("HELLO THROUGH THE AMQP", receive.getPayload());
((Lifecycle) this.amqpOutboundInput).stop();
}
@Test
@@ -167,6 +173,8 @@ public class AmqpTests {
Message<?> receive = replyChannel.receive(10000);
assertNotNull(receive);
assertEquals("HELLO ASYNC GATEWAY", receive.getPayload());
this.asyncOutboundGateway.stop();
}
@Autowired
@@ -264,7 +272,8 @@ public class AmqpTests {
@Bean
public IntegrationFlow amqpInboundFlow(ConnectionFactory rabbitConnectionFactory) {
return IntegrationFlows.from(Amqp.inboundAdapter(rabbitConnectionFactory, fooQueue()))
return IntegrationFlows.from(Amqp.inboundAdapter(rabbitConnectionFactory, fooQueue())
.id("amqpInboundFlowAdapter"))
.transform(String.class, String::toUpperCase)
.channel(Amqp.pollableChannel(rabbitConnectionFactory)
.queueName("amqpReplyChannel")
@@ -286,7 +295,8 @@ public class AmqpTests {
public IntegrationFlow amqpAsyncOutboundFlow(AsyncRabbitTemplate asyncRabbitTemplate) {
return f -> f
.handle(Amqp.asyncOutboundGateway(asyncRabbitTemplate)
.routingKeyFunction(m -> queue().getName()));
.routingKeyFunction(m -> queue().getName()),
e -> e.id("asyncOutboundGateway"));
}
@Bean

View File

@@ -17,26 +17,23 @@
package org.springframework.integration.config.dsl;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.io.DescriptiveResource;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.FixedSubscriberChannel;
@@ -67,17 +64,14 @@ import org.springframework.util.StringUtils;
*
* @author Artem Bilan
* @author Gary Russell
*
* @since 5.0
*/
public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware,
SmartInitializingSingleton {
private final Set<ApplicationListener<?>> applicationListeners = new HashSet<ApplicationListener<?>>();
public class IntegrationFlowBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware, SmartInitializingSingleton {
private ConfigurableListableBeanFactory beanFactory;
private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
@@ -86,8 +80,6 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
);
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
this.autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
this.autowiredAnnotationBeanPostProcessor.setBeanFactory(this.beanFactory);
}
@Override
@@ -111,13 +103,6 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
@Override
public void afterSingletonsInstantiated() {
if (this.beanFactory.containsBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
ApplicationEventMulticaster multicaster =
(ApplicationEventMulticaster) this.beanFactory.getBean(
AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME);
this.applicationListeners.forEach(multicaster::addApplicationListener);
}
for (String beanName : this.beanFactory.getBeanNamesForType(IntegrationFlow.class)) {
if (this.beanFactory.containsBeanDefinition(beanName)) {
String scope = this.beanFactory.getBeanDefinition(beanName).getScope();
@@ -135,8 +120,6 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
String flowNamePrefix = flowBeanName + ".";
int subFlowNameIndex = 0;
int channelNameIndex = 0;
boolean registerSingleton = flow.isRegisterComponents();
Map<Object, String> integrationComponents = flow.getIntegrationComponents();
Map<Object, String> targetIntegrationComponents = new LinkedHashMap<>(integrationComponents.size());
@@ -161,13 +144,13 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
String handlerBeanName = generateBeanName(messageHandler);
String[] handlerAlias = new String[] { id + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX };
registerComponent(messageHandler, handlerBeanName, flowBeanName, registerSingleton);
registerComponent(messageHandler, handlerBeanName, flowBeanName);
for (String alias : handlerAlias) {
this.beanFactory.registerAlias(handlerBeanName, alias);
}
}
registerComponent(endpoint, id, flowBeanName, registerSingleton);
registerComponent(endpoint, id, flowBeanName);
targetIntegrationComponents.put(endpoint, id);
}
else {
@@ -182,14 +165,14 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + channelNameIndex++;
}
}
registerComponent(component, channelBeanName, flowBeanName, registerSingleton);
registerComponent(component, channelBeanName, flowBeanName);
targetIntegrationComponents.put(component, channelBeanName);
}
else if (component instanceof MessageChannelReference) {
String channelBeanName = ((MessageChannelReference) component).getName();
if (!this.beanFactory.containsBean(channelBeanName)) {
DirectChannel directChannel = new DirectChannel();
registerComponent(directChannel, channelBeanName, flowBeanName, registerSingleton);
registerComponent(directChannel, channelBeanName, flowBeanName);
targetIntegrationComponents.put(directChannel, channelBeanName);
}
}
@@ -200,7 +183,7 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
channelBeanName = flowNamePrefix + "channel" +
BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + channelNameIndex++;
}
registerComponent(component, channelBeanName, flowBeanName, registerSingleton);
registerComponent(component, channelBeanName, flowBeanName);
targetIntegrationComponents.put(component, channelBeanName);
}
else if (component instanceof SourcePollingChannelAdapterSpec) {
@@ -221,7 +204,7 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
if (!StringUtils.hasText(id)) {
id = generateBeanName(pollingChannelAdapterFactoryBean, entry.getValue());
}
registerComponent(pollingChannelAdapterFactoryBean, id, flowBeanName, registerSingleton);
registerComponent(pollingChannelAdapterFactoryBean, id, flowBeanName);
targetIntegrationComponents.put(pollingChannelAdapterFactoryBean, id);
MessageSource<?> messageSource = spec.get().getT2();
@@ -233,7 +216,7 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
&& ((NamedComponent) messageSource).getComponentName() != null) {
messageSourceId = ((NamedComponent) messageSource).getComponentName();
}
registerComponent(messageSource, messageSourceId, flowBeanName, registerSingleton);
registerComponent(messageSource, messageSourceId, flowBeanName);
}
}
else if (component instanceof StandardIntegrationFlow) {
@@ -242,19 +225,31 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
? entry.getValue()
: flowNamePrefix + "subFlow" +
BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + subFlowNameIndex++;
registerComponent(component, subFlowBeanName, flowBeanName, registerSingleton);
registerComponent(component, subFlowBeanName, flowBeanName);
targetIntegrationComponents.put(component, subFlowBeanName);
}
else if (component instanceof AnnotationGatewayProxyFactoryBean) {
String gatewayId = entry.getValue() != null
? entry.getValue()
: flowNamePrefix + "gateway";
registerComponent(component, gatewayId, flowBeanName, registerSingleton);
AnnotationGatewayProxyFactoryBean gateway = (AnnotationGatewayProxyFactoryBean) component;
String gatewayId = entry.getValue();
if (gatewayId == null) {
gatewayId = gateway.getComponentName();
}
if (gatewayId == null) {
gatewayId = flowNamePrefix + "gateway";
}
registerComponent(gateway, gatewayId, flowBeanName,
beanDefinition -> {
((AbstractBeanDefinition) beanDefinition)
.setSource(new DescriptiveResource(gateway.getObjectType().getName()));
});
targetIntegrationComponents.put(component, gatewayId);
}
else {
String generatedBeanName = generateBeanName(component, entry.getValue());
registerComponent(component, generatedBeanName, flowBeanName, registerSingleton);
registerComponent(component, generatedBeanName, flowBeanName);
targetIntegrationComponents.put(component, generatedBeanName);
}
}
@@ -276,7 +271,7 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
}
private void processIntegrationComponentSpec(IntegrationComponentSpec<?, ?> bean) {
registerComponent(bean.get(), generateBeanName(bean.get(), bean.getId()), null, false);
registerComponent(bean.get(), generateBeanName(bean.get(), bean.getId()));
if (bean instanceof ComponentsRegistration) {
Map<Object, String> componentsToRegister = ((ComponentsRegistration) bean).getComponentsToRegister();
if (!CollectionUtils.isEmpty(componentsToRegister)) {
@@ -296,26 +291,25 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean
}
private void registerComponent(Object component, String beanName) {
registerComponent(component, beanName, null, true);
registerComponent(component, beanName, null);
}
private void registerComponent(Object component, String beanName, String parentName, boolean registerSingleton) {
if (component instanceof ApplicationListener) {
this.applicationListeners.add((ApplicationListener<?>) component);
}
this.autowiredAnnotationBeanPostProcessor.processInjection(component);
this.beanFactory.initializeBean(component, beanName);
if (registerSingleton) {
this.beanFactory.registerSingleton(beanName, component);
if (parentName != null) {
this.beanFactory.registerDependentBean(parentName, beanName);
}
@SuppressWarnings("unchecked")
private void registerComponent(Object component, String beanName, String parentName,
BeanDefinitionCustomizer... customizers) {
BeanDefinition beanDefinition =
BeanDefinitionBuilder.genericBeanDefinition((Class<Object>) component.getClass(), () -> component)
.applyCustomizers(customizers)
.getRawBeanDefinition();
((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(beanName, beanDefinition);
if (parentName != null) {
this.beanFactory.registerDependentBean(parentName, beanName);
}
if (component instanceof DisposableBean) {
((DefaultSingletonBeanRegistry) this.beanFactory)
.registerDisposableBean(beanName, (DisposableBean) component);
}
this.beanFactory.getBean(beanName);
}
private String generateBeanName(Object instance) {

View File

@@ -302,28 +302,38 @@ public final class IntegrationFlows {
* Populate the {@link MessageChannel} to the new {@link IntegrationFlowBuilder}
* chain, which becomes as a {@code requestChannel} for the Messaging Gateway(s) built
* on the provided service interface.
* <p>A gateway proxy bean for provided service interface is registered under a name of
* the {@link IntegrationFlow} bean plus {@code .gateway} suffix.
* <p>A gateway proxy bean for provided service interface is registered under a name
* from the
* {@link org.springframework.integration.annotation.MessagingGateway#name()} if present
* or from the {@link IntegrationFlow} bean name plus {@code .gateway} suffix.
* @param serviceInterface the service interface class with an optional
* {@link org.springframework.integration.annotation.MessagingGateway} annotation.
* @return new {@link IntegrationFlowBuilder}.
*/
public static IntegrationFlowBuilder from(Class<?> serviceInterface) {
return from(serviceInterface, null);
}
/**
* Populate the {@link MessageChannel} to the new {@link IntegrationFlowBuilder}
* chain, which becomes as a {@code requestChannel} for the Messaging Gateway(s) built
* on the provided service interface.
* <p>A gateway proxy bean for provided service interface is registered under a name of
* the provided {@code beanName} if not null, or from the
* {@link org.springframework.integration.annotation.MessagingGateway#name()} if present
* or as a fallback to the {@link IntegrationFlow} bean name plus {@code .gateway} suffix.
* @param serviceInterface the service interface class with an optional
* {@link org.springframework.integration.annotation.MessagingGateway} annotation.
* @param beanName the bean name to be used for registering bean for the gateway proxy
* @return new {@link IntegrationFlowBuilder}.
*/
public static IntegrationFlowBuilder from(Class<?> serviceInterface, String beanName) {
final DirectChannel gatewayRequestChannel = new DirectChannel();
GatewayProxyFactoryBean gatewayProxyFactoryBean =
new AnnotationGatewayProxyFactoryBean(serviceInterface) {
GatewayProxyFactoryBean gatewayProxyFactoryBean = new AnnotationGatewayProxyFactoryBean(serviceInterface);
@Override
protected void onInit() {
super.onInit();
getGateways()
.values()
.forEach(gateway ->
gateway.setRequestChannel(gatewayRequestChannel));
}
};
gatewayProxyFactoryBean.setDefaultRequestChannel(gatewayRequestChannel);
gatewayProxyFactoryBean.setBeanName(beanName);
return from(gatewayRequestChannel)
.addComponent(gatewayProxyFactoryBean);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-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.
@@ -69,23 +69,12 @@ public class StandardIntegrationFlow implements IntegrationFlow, SmartLifecycle
private final List<SmartLifecycle> lifecycles = new LinkedList<>();
private final boolean registerComponents = true; // NOSONAR
private boolean running;
StandardIntegrationFlow(Map<Object, String> integrationComponents) {
this.integrationComponents = new LinkedHashMap<>(integrationComponents);
}
//TODO Figure out some custom DestinationResolver when we don't register singletons - remove NOSONAR above when done
/*public void setRegisterComponents(boolean registerComponents) {
this.registerComponents = registerComponents;
}*/
public boolean isRegisterComponents() {
return this.registerComponents;
}
public void setIntegrationComponents(Map<Object, String> integrationComponents) {
this.integrationComponents.clear();
this.integrationComponents.putAll(integrationComponents);

View File

@@ -16,6 +16,7 @@
package org.springframework.integration.dsl.context;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -24,10 +25,10 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.support.context.NamedComponent;
@@ -68,8 +69,6 @@ public final class IntegrationFlowContext implements BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor;
private IntegrationFlowContext() {
}
@@ -80,8 +79,6 @@ public final class IntegrationFlowContext implements BeanFactoryAware {
"'ConfigurableListableBeanFactory'. " +
"Consider using 'GenericApplicationContext' implementation.");
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
this.autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
this.autowiredAnnotationBeanPostProcessor.setBeanFactory(this.beanFactory);
}
/**
@@ -113,22 +110,23 @@ public final class IntegrationFlowContext implements BeanFactoryAware {
this.registry.put(flowId, builder.integrationFlowRegistration);
}
@SuppressWarnings("unchecked")
private Object registerBean(Object bean, String beanName, String parentName) {
if (beanName == null) {
beanName = generateBeanName(bean, parentName);
}
this.autowiredAnnotationBeanPostProcessor.processInjection(bean);
bean = this.beanFactory.initializeBean(bean, beanName);
this.beanFactory.registerSingleton(beanName, bean);
BeanDefinition beanDefinition =
BeanDefinitionBuilder.genericBeanDefinition((Class<Object>) bean.getClass(), () -> bean)
.getRawBeanDefinition();
((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(beanName, beanDefinition);
if (parentName != null) {
this.beanFactory.registerDependentBean(parentName, beanName);
}
if (bean instanceof DisposableBean) {
((DefaultSingletonBeanRegistry) this.beanFactory)
.registerDisposableBean(beanName, (DisposableBean) bean);
}
return bean;
return this.beanFactory.getBean(beanName);
}
/**
@@ -150,7 +148,11 @@ public final class IntegrationFlowContext implements BeanFactoryAware {
if (this.registry.containsKey(flowId)) {
IntegrationFlowRegistration flowRegistration = this.registry.remove(flowId);
flowRegistration.stop();
((DefaultSingletonBeanRegistry) this.beanFactory).destroySingleton(flowId);
Arrays.stream(this.beanFactory.getDependentBeans(flowId))
.forEach(((BeanDefinitionRegistry) this.beanFactory)::removeBeanDefinition);
((BeanDefinitionRegistry) this.beanFactory).removeBeanDefinition(flowId);
}
else {
throw new IllegalStateException("Only manually registered IntegrationFlows can be removed. "
@@ -207,7 +209,7 @@ public final class IntegrationFlowContext implements BeanFactoryAware {
private boolean autoStartup = true;
IntegrationFlowRegistrationBuilder(IntegrationFlow integrationFlow) {
private IntegrationFlowRegistrationBuilder(IntegrationFlow integrationFlow) {
this.integrationFlowRegistration = new IntegrationFlowRegistration(integrationFlow);
this.integrationFlowRegistration.setBeanFactory(IntegrationFlowContext.this.beanFactory);
this.integrationFlowRegistration.setIntegrationFlowContext(IntegrationFlowContext.this);

View File

@@ -47,13 +47,20 @@ public class AnnotationGatewayProxyFactoryBean extends GatewayProxyFactoryBean {
public AnnotationGatewayProxyFactoryBean(Class<?> serviceInterface) {
super(serviceInterface);
AnnotationAttributes gatewayAttributes = AnnotatedElementUtils.getMergedAnnotationAttributes(serviceInterface,
MessagingGateway.class.getName(), false, true);
AnnotationAttributes gatewayAttributes =
AnnotatedElementUtils.getMergedAnnotationAttributes(serviceInterface,
MessagingGateway.class.getName(), false, true);
if (gatewayAttributes == null) {
gatewayAttributes = AnnotationUtils.getAnnotationAttributes(
AnnotationUtils.synthesizeAnnotation(MessagingGateway.class), false, true);
}
this.gatewayAttributes = gatewayAttributes;
String id = gatewayAttributes.getString("name");
if (!StringUtils.hasText(id)) {
setBeanName(id);
}
}
@Override

View File

@@ -444,6 +444,7 @@ public class IntegrationFlowTests {
}
@Autowired
@Qualifier("errorRecovererFunction")
private Function<String, String> errorRecovererFlowGateway;
@Test
@@ -789,7 +790,7 @@ public class IntegrationFlowTests {
@Bean
public IntegrationFlow errorRecovererFlow() {
return IntegrationFlows.from(Function.class)
return IntegrationFlows.from(Function.class, "errorRecovererFunction")
.handle((GenericHandler<?>) (p, h) -> {
throw new RuntimeException("intentional");
}, e -> e.advice(retryAdvice()))