I am working on my first spring-integration use case. I would like to do everything in pure Spring Java Config (no XML configuration and DSL, only annotations). So far everything was OK despite lack of documentation and examples.
But now I have problem with delayer. There is no obvious annotation and there is nothing in documentation regarding configuring delayer in DSL or annotation. Is delayer supported only in XML configuration?
The Java DSL on the matter should be obvious:
#Bean
public IntegrationFlow delayFlow() {
return IntegrationFlows.from("delayInput")
.delay("delayer", d -> d
.delayExpression("200")
.advice(this.delayedAdvice)
.messageStore(messageStore()))
.get();
}
For the raw Java & Annotation configuration pay attention to Programming Tips and Tricks.
The main point for you is this:
If you are familiar with Spring Integration XML configuration already, starting with version 4.3, we provide in the XSD elements definitions the description with the pointer which target classes are used to produce beans for the adapter or gateway.
So, going to the <delayer> XSD we see:
<xsd:documentation>
Defines a Consumer Endpoint for the 'org.springframework.integration.handler.DelayHandler'
that passes a Message to the output-channel after a delay.
Having a sample above about sendChatMessageHandler() #Bean we can do the same for the DelayHandler:
#Bean
#ServiceActivator(inputChannel = "delayInput")
public MessageHandler delayMessageHandler() {
DelayHandler delayHandler = new DelayHandler("myGroup");
...
return delayHandler;
}
NOTE: The latest documentation for version 5.0 will contain a sample how to configure delayer via Annotations and Java DSL.
Related
Spring Integration here. I was expecting to see a normalize(...) method off the IntegrationFlow DSL and was surprised to find there wasn't one (like .route(...) or .aggregate(...), etc.).
In fact, some digging on Google and the Spring Integration docs, and I can't seem to find any built-in support for the Normalizer EIP. So I've taken a crack at my own:
public class Normalizer extends AbstractTransformer {
private Class<?> targetClass;
private GenericConverter genericConverter;
public Normalizer(Class<?> targetClass, GenericConverter genericConverter) {
Optional<GenericConverter.ConvertiblePair> maybePair = genericConverter.getConvertibleTypes().stream()
.filter(convertiblePair -> !convertiblePair.getTargetType().equals(targetClass))
.findAny();
assert(maybePair.isEmpty());
this.targetClass = targetClass;
this.genericConverter = genericConverter;
}
#Override
protected Object doTransform(Message<?> message) {
Object inbound = message.getPayload();
return genericConverter.convert(inbound, TypeDescriptor.forObject(inbound), TypeDescriptor.valueOf(targetClass));
}
}
The idea is that Spring already provides the GenericConverter SPI for converting multiple source types to 1+ target type instance. We just need a specialized flavor of that that has the same target type for all convertible pairings. So here we extend AbstractTransformer and pass it one of these GenericConverters to use. During initialization we just verify that all the possible convertible pairs convert to the same targetClass specified for the Normalizer.
The idea is I could instantiate it like so:
#Bean
public Normalizer<Fizz> fizzNormalizer(GenericConverter fizzConverter) {
return new Normalizer(Fizz.class, fizzConverter);
}
And then put it in a flow:
IntegrationFlow someFlow = IntegrationFlows.from(someChannel())
.transform(fizzNormalizer())
// other components
.get();
While I believe this will work, before I start using it too heavily I want to make sure I'm not overlooking anything in the Spring Integration framework that will accomplish/satisfy the Normalizer EIP for me. No point in trying to reinvent the wheel and all that jazz. Thanks for any insight.
If you take a closer look into that EI pattern, then you see:
Use a Normalizer to route each message type through a custom Message Translator so that the resulting messages match a common format.
The crucial part of this pattern that it is a composed one with a router as input endpoint and a set of transformers for each inbound message type.
Since it is that kind of component which is data model dependent and more over the routing and transforming logic might differ from use-case to use-case, it is really hard to make an out-of-the-box single configurable component.
Therefore you need to investigate what type of routing you need to do to chose a proper one for input: https://docs.spring.io/spring-integration/docs/current/reference/html/message-routing.html#router
Then for every routed type you nee to implement respective transformer to produce a canonical data mode.
All of the can be just wrapped into a #MessagegingGateway API to hide the normalize behind so-called pattern implementation.
That's what I would do to follow that EI pattern recommendations.
However if your use-case is so simple as just convert from one type to another, so yes, then you can rely on the ConversionService. You register your custom Converter: https://docs.spring.io/spring-integration/docs/current/reference/html/endpoint.html#payload-type-conversion. And then just use a .convert(Class) API from IntegrationFlowDefinition.
But again: since there is no easy way to cover all the possible domain use-cases, we cannot provide an out-of-the-box Normalizer implementation.
1) I would like to create a bean of HttpRequestExecutingMessageHandler (outbound channel adapter for HTTP) and specify the channel via an annotation like #OutboundChannelAdapter, why this is not possible? I suppose there is some design decision that I'm not understanding.
2) What is the suggested way of define HttpRequestExecutingMessageHandler without using XML configuration files? Do I have to configure the bean and set it manually?
Thanks in advance.
The #ServiceActivator fully covers that functionality. Unlike #Transformer it doesn't require a return value. So, your POJO method can be just void and the flow is going to stop there similar way a <outbound-channel-adapter> does that in XML configuration.
But in case of HttpRequestExecutingMessageHandler we need to worry about some extra option to make it one-way and stop there without care about any HTTP reply.
So, for the HttpRequestExecutingMessageHandler you need to declare a bean like:
#Bean
#ServiceActivator(inputChannel = )
public HttpRequestExecutingMessageHandler httpRequestExecutingMessageHandler() {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler();
handler.setExpectReply(false)
return handler;
}
I think we need to improve docs on the matter anyway, but you can take a look into Java DSL configuration instead: https://docs.spring.io/spring-integration/docs/current/reference/html/#http-java-config. There is an Http.outboundChannelAdapter() for convenience.
The reference manul talked about how to set this in XML, and I saw the Jira issue for advanced configuration for #Aggregator completed but not seeing those advanced properties. So if using annotation, how to set expire group?
Well, according that JIRA ticket there is indeed a sample in the Reference Manual:
#ServiceActivator(inputChannel = "aggregatorChannel")
#Bean
public MessageHandler aggregator(MessageGroupStore jdbcMessageGroupStore) {
AggregatingMessageHandler aggregator =
new AggregatingMessageHandler(new DefaultAggregatingMessageGroupProcessor(),
jdbcMessageGroupStore);
aggregator.setOutputChannel(resultsChannel());
aggregator.setGroupTimeoutExpression(new ValueExpression<>(500L));
aggregator.setTaskScheduler(this.taskScheduler);
return aggregator;
}
And there is an explicit note on the matter:
Annotation configuration (#Aggregator and others) for the Aggregator component covers only simple use cases, where most default options are sufficient. If you need more control over those options using Annotation configuration, consider using a #Bean definition for the AggregatingMessageHandler and mark its #Bean method with #ServiceActivator
Even would be better to use this:
Starting with the version 4.2 the AggregatorFactoryBean is available, to simplify Java configuration for the AggregatingMessageHandler.
Seems for me everything is covered in the Docs. Is anything missed?
I mean the AggregatorFactoryBean has an option you need:
public void setExpireGroupsUponCompletion(Boolean expireGroupsUponCompletion) {
Is that not enough?
I have been working on a "paved road" for setting up asynchronous messaging between two micro services using AMQP. We want to promote the use of separate domain objects for each service, which means that each service must define their own copy of any objects passed across the queue.
We are using Jackson2JsonMessageConverter on both the producer and the consumer side and we are using the Java DSL to wire the flows to/from the queues.
I am sure there is a way to do this, but it is escaping me: I want the consumer side to ignore the __TypeID__ header that is passed from the producer, as the consumer may have a different representation of that event (and it will likely be in in a different java package).
It appears there was work done such that if using the annotation #RabbitListener, an inferredArgumentTypeargument is derived and will override the header information. This is exactly what I would like to do, but I would like to use the Java DSL to do it. I have not yet found a clean way in which to do this and maybe I am just missing something obvious. It seems it would be fairly straight forward to derive the type when using the following DSL:
return IntegrationFlows
.from(
Amqp.inboundAdapter(factory, queueRemoteTaskStatus())
.concurrentConsumers(10)
.errorHandler(errorHandler)
.messageConverter(messageConverter)
)
.channel(channelRemoteTaskStatusIn())
.handle(listener, "handleRemoteTaskStatus")
.get();
However, this results in a ClassNotFound exception. The only way I have found to get around this, so far, is to set a custom message converter, which requires explicit definition of the type.
public class ForcedTypeJsonMessageConverter extends Jackson2JsonMessageConverter {
ForcedTypeJsonMessageConverter(final Class<?> forcedType) {
setClassMapper(new ClassMapper() {
#Override
public void fromClass(Class<?> clazz, MessageProperties properties) {
//this class is only used for inbound marshalling.
}
#Override
public Class<?> toClass(MessageProperties properties) {
return forcedType;
}
});
}
}
I would really like this to be derived, so the developer does not have to really deal with this.
Is there an easier way to do this?
The simplest way is to configure the Jackson converter's DefaultJackson2JavaTypeMapper with TypeIdMapping (setIdClassMapping()).
On the sending system, map foo:com.one.Foo and on the receiving system map foo:com.two.Foo.
Then, the __TypeId__ header gets foo and the receiving system will map it to its representation of a Foo.
EDIT
Another option would be to add an afterReceiveMessagePostProcessor to the inbound channel adapter's listener container - it could change the __TypeId__ header.
I am trying to use AMQP-backed message channels in my Spring Integration app, but I think I am fundamentally misunderstanding something, specifically around the Message<?> interface and how instances of GenericMessage<?> are written to and read from, a RabbitMQ queue.
Given I have a Spring Integration app containing the following domain model object:
#Immutable
class Foo {
String name
long quantity
}
and I declare an AMQP backed message channel called fooChannel as follows:
#Bean
public AmqpChannelFactoryBean deliveryPlacementChannel(CachingConnectionFactory connectionFactory) {
AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean(true)
factoryBean.setConnectionFactory(connectionFactory)
factoryBean.setQueueName("foo")
factoryBean.beanName = 'fooChannel'
factoryBean.setPubSub(false)
factoryBean
}
When I initially tried to send a message to my fooChannel I received a java.io.NotSerializableException. I realised this to be caused by the fact that the RabbitTemplate used by my AMQP-backed fooChannel was using a org.springframework.amqp.support.converter.SimpleMessageConverter which can only work with Strings, Serializable instances, or byte arrays, of which my Foo model is none of those things.
Therefore, I thought that I should use a org.springframework.amqp.support.converter.Jackson2JsonMessageConverter to ensure my Foo model is properly converted to/from and AMQP message. However, it appears that the type of the message that is being added to the RabbitMQ queue which is backing my fooChannel is of type org.springframework.messaging.support.GenericMessage. This means that when my AMQP backed fooChannel tries to consume messages from the RabbitMQ queue it receives the following exception:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class org.springframework.messaging.support.GenericMessage]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
From looking at the GenericMessage class I see that is designed to be immutable, which clearly explains why the Jackson2JsonMessageConverter can't convert from JSON to the GenericMessage type. However, I am unsure what I should be doing in order to allow my fooChannel to be backed by AMQP and have the conversion of my Spring Integration messages containing my Foo model work correctly?
In terms of the flow of my application I have the following Transformer component which consumes Bar models from the (non-AMQP backed) barChannel and places Foo models on the fooChannel as follows:
#Transformer(inputChannel = 'barChannel', outputChannel = 'fooChannel')
public Foo transform(Bar bar) {
//transform logic removed for brevity
new Foo(name: 'Foo1', quantity: 1)
}
I then have a ServiceActivator component which I wish to have consume from my fooChannel as follows:
#ServiceActivator(inputChannel = 'fooChannel')
void consumeFoos(Foo foo){
// Do something with foo
}
I am using spring-integration-core:4.2.5.RELEASE and spring-integration-amqp:4.2.5.RELEASE.
Can anyone please advise where I am going wrong with the configuration of my Spring Integration application?
If any further information is needed to in order to better clarify my question or problem, please let me know. Thanks
Yes - amqp-backed channels are currently limited to Java serializable objects.
We should provide an option to map the Message<?> to a Spring AMQP Message (like the channel adapters do) rather than...
this.amqpTemplate.convertAndSend(this.getExchangeName(), this.getRoutingKey(), message);
...which converts the entire message.
You could use a pair of channel adapters (outbound/inbound) instead of a channel.
Since you are using Java config, you could wrap the adapter pair in a new MessageChannel implementation.
I opened a JIRA Issue for this.