A message is received on a channel, and is processed by a transformer. The output of the transformer is the following class, which is passed on to the next step in the flow:
public class DomainBean{
public DomainBean process() {
// code
return this;
}
}
Another bean has a #Transformer method which calls the process method on the bean above, as follows:
#Component
public class Handler {
#Transformer
public Object handle(Message<?> message) throws MessagingException {
DomainBean domainBean = (DomainBean) message;
domainBean.process();
return domainBean ;
}
}
Within the IntegrationFlow, the handle method is invoked as follows:
.transform(handler)
Ideally I would like to do away with the Handler bean class, and call domainBean.process() using an object method reference, as follows:
.transform(DomainBean::process)
When I try that, or .<DomainBean, DomainBean>transform(DomainBean::process)
compiler complains that
Non-static method cannot be referenced from a static context
Is there anyway to make this work?
Thanks
Your question isn't clear. There is really this method in the IntegrationFlowDefinition:
* Populate the {#link MessageTransformingHandler} instance for the provided {#link GenericTransformer}.
* #param genericTransformer the {#link GenericTransformer} to populate.
* #param <S> the source type - 'transform from'.
* #param <T> the target type - 'transform to'.
* #return the current {#link IntegrationFlowDefinition}.
* #see MethodInvokingTransformer
* #see LambdaMessageProcessor
*/
public <S, T> B transform(GenericTransformer<S, T> genericTransformer) {
Where that GenericTransformer really can be expressed as lambda or method reference and we have enough tests to confirm that. The code there looks like:
.transform("hello "::concat)
...
.<String, Integer>transform(Integer::parseInt)
...
.<String, String>transform(String::toUpperCase)
...
.<MessagingException, Message<?>>transform(MessagingException::getFailedMessage)
Maybe your problem that you need add exact generics agrs to the method? Therefore this:
.<DomainBean, DomainBean>transform(DomainBean::process)
However what I see that your DomainBean.process() is void. This signature doesn't fit the transformer purpose. Yes, you can do the same with a lambda what you have in the Handler or another solution is to use .handle(). This one really can call void method and, to be honest, that is exactly what fits to your logic - there is no any transformation logic so far.
However when service method is void there is no any continuation after this method call in the flow. Such a .handle() must be the last one-way endpoint in the flow definition. The void is equal to the Outbound Channel Adapter.
Related
From the Spring Integration documentation (https://docs.spring.io/spring-integration/docs/5.1.7.RELEASE/reference/html/#delayer) it is not clear to me what the messageGroupId in the DelayHandler means exactly and which value I have to set there exactly (is it arbitrary?). This value does not exist in the xml configuration, but does in the Java configuration.
#ServiceActivator(inputChannel = "input")
#Bean
public DelayHandler delayer() {
DelayHandler handler = new DelayHandler("delayer.messageGroupId"); // THIS constructor parameter is not clear to me
handler.setDefaultDelay(3_000L);
handler.setDelayExpressionString("headers['delay']");
handler.setOutputChannelName("output");
return handler;
}
It is explained in the JavaDocs of that constructor:
/**
* Create a DelayHandler with the given 'messageGroupId' that is used as 'key' for
* {#link MessageGroup} to store delayed Messages in the {#link MessageGroupStore}.
* The sending of Messages after the delay will be handled by registered in the
* ApplicationContext default
* {#link org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler}.
* #param messageGroupId The message group identifier.
* #see #getTaskScheduler()
*/
public DelayHandler(String messageGroupId) {
It is not required because the groupId is based on the required id attribute:
String id = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error("The 'id' attribute is required.", element);
}
...
builder.addConstructorArgValue(id + ".messageGroupId");
It is really mentioned and explained a little bit in the docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#delayer-namespace.
The value indeed is arbitrary, but it must be unique per your application, so different delayers don't steal messages from each other.
Given I have IntegrationFlow and inbound adapter for AMQP queue:
#Bean
public IntegrationFlow readMessagesFlow(
ConnectionFactory rabbitConnectionFactory,
ObjectMapper jacksonObjectMapper) {
return IntegrationFlows.from(
Amqp.inboundAdapter(rabbitConnectionFactory, QUEUE)
.messageConverter(new Jackson2JsonMessageConverter(jacksonObjectMapper))
)
.log(INFO, AMQP_LOGGER_CATEGORY)
.get();
}
When some external system sends a message with JSON body, I found they use __TypeId__=THEIR_INTERNAL_CLASS.
I would like to map JSON body to my own class.
Currently, it fails on ClassCastException because of THEIR_INTERNAL_CLASS not being available.
How can I tell Jackson2JsonMessageConverter to use my own class?
This is one way how to do it:
Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter(jacksonObjectMapper);
DefaultClassMapper defaultClassMapper = new DefaultClassMapper();
defaultClassMapper.setDefaultType(MyOwnClass.class);
messageConverter.setClassMapper(defaultClassMapper);
and use messageConverter in Amqp.inboundAdapter
See this method of converter:
/**
* Set the precedence for evaluating type information in message properties.
* When using {#code #RabbitListener} at the method level, the framework attempts
* to determine the target type for payload conversion from the method signature.
* If so, this type is provided in the
* {#link MessageProperties#getInferredArgumentType() inferredArgumentType}
* message property.
* <p> By default, if the type is concrete (not abstract, not an interface), this will
* be used ahead of type information provided in the {#code __TypeId__} and
* associated headers provided by the sender.
* <p> If you wish to force the use of the {#code __TypeId__} and associated headers
* (such as when the actual type is a subclass of the method argument type),
* set the precedence to {#link Jackson2JavaTypeMapper.TypePrecedence#TYPE_ID}.
* #param typePrecedence the precedence.
* #see DefaultJackson2JavaTypeMapper#setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence)
*/
public void setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence typePrecedence) {
and here are docs on the matter: https://docs.spring.io/spring-amqp/docs/2.2.11.RELEASE/reference/html/#json-message-converter
I want to use Java DSL for Spring Integration, but I can't figure out how to use message headers during transformation.
My old implementation had a Transformer like this:
#Transformer(inputChannel = "inputChannel", outputChannel = "outputChannel")
public EventB transform(
EventA eventA,
#Header("a_header") String aHeader,
#Header("other_header") String otherHeader){
return new EventB(eventA.getSomeField(), aHeader, otherHeader);
}
Now I have the following DSL:
#Bean
public IntegrationFlow aFlow(){
return IntegrationFlows.from(EventASink.INPUT)
.filter("headers['operation'] == 'OPERATION_A'")
.transform() //<-- What should I do here?
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
I looked at the implementation of transform() method and I found that it can receive a GenericTransformer as parameter, but it seems to work only with message payload and I also need the headers.
I also saw that some kind of reflection can be used, but I don't like it because its not refactor-safe.
Any advice? Thanks in advance.
Since the DSL is a part of the Framework and it is compiled before you start to use it, we can't infer any custom POJO methods, therefore there is no so clean way to count with any custom headers like in your sample.
The closet way to re-use your transform() with those annotations on parameters is with this .transform() contract:
/**
* Populate the {#code MessageTransformingHandler} for the {#link MethodInvokingTransformer}
* to invoke the service method at runtime.
* #param service the service to use.
* #param methodName the method to invoke.
* #return the current {#link IntegrationFlowDefinition}.
* #see MethodInvokingTransformer
*/
public B transform(Object service, String methodName)
So, you would need to declare a bean with that method and use it in the service argument meanwhile mention the method in the methodName argument.
Another way to get access to headers is to request the whole Message type for lambda:
/**
* Populate the {#link MessageTransformingHandler} instance for the provided
* {#link GenericTransformer} for the specific {#code payloadType} to convert at
* runtime.
* Use {#link #transform(Class, GenericTransformer)} if you need access to the
* entire message.
* #param payloadType the {#link Class} for expected payload type. It can also be
* {#code Message.class} if you wish to access the entire message in the transformer.
* Conversion to this type will be attempted, if necessary.
* #param genericTransformer the {#link GenericTransformer} to populate.
* #param <P> the payload type - 'transform from' or {#code Message.class}.
* #param <T> the target type - 'transform to'.
* #return the current {#link IntegrationFlowDefinition}.
* #see MethodInvokingTransformer
* #see LambdaMessageProcessor
*/
public <P, T> B transform(Class<P> payloadType, GenericTransformer<P, T> genericTransformer) {
In this case the code could be like this:
.transform(Message.class, m -> m.getHeaders())
I had the same issue. I needed both headers and payload. After a lot of tinkering I found a solution. I used .handle instead of .transform. GenericHandler's handle method provides both payload and headers.
In your case it would look something like:
#Bean
public IntegrationFlow aFlow(){
return IntegrationFlows.from(EventASink.INPUT)
.filter("headers['operation'] == 'OPERATION_A'")
.<EventA>handle((eventA, h) -> new EventB(
eventA.getSomeField(),
h.get("a_header", String.class),
h.get("other_header", String.class)))
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
I'm learning Groovy and I've seen this example:
button = new JButton('Push me!')
button.actionPerformed = { event ->
println button.text
}
There is no actionPerformed field/method on JButton....
Could someone explain how Closure is being registered by Groovy on actionPerformed ?
TL;DR
JButton metaclass registers 31 listeners methods (one of them is actionPerformed) and whenever you call button.actionPerformed = { event -> } Groovy executes a method like setProperty(object, field, value) which checks if there is a listener registered with a given field name - if does, it executes registered listener method (javax.swing.AbstractButton.addActionListener(java.awt.event.ActionListener) in this case).
Detailed explanation
Groovy uses MOP (metaobject protocol) for a dynamic runtime environment. It means that Groovy does not call methods directly like in Java, but uses this additional layer instead. It allows changing class behavior at a runtime.
Whenever we try to set a class field/property value like
button.actionPerformed = { event -> println "Clicked!" }
Groovy calls proper setProperty method. In case of a setting a property for class like JButton, following setProperty method gets called:
https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/MetaClassImpl.java#L2602
/**
* <p>Retrieves a property on the given receiver for the specified arguments. The sender is the class that is requesting the property from the object.
* The MetaClass will attempt to establish the method to invoke based on the name and arguments provided.
*
* <p>The useSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly
* to the super class if necessary
*
* #param sender The java.lang.Class instance that is mutating the property
* #param object The Object which the property is being set on
* #param name The name of the property
* #param newValue The new value of the property to set
* #param useSuper Whether the call is to a super class property
* #param fromInsideClass Whether the call was invoked from the inside or the outside of the class.
*/
public void setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) {
checkInitalised();
//----------------------------------------------------------------------
// handling of static
//----------------------------------------------------------------------
boolean isStatic = theClass != Class.class && object instanceof Class;
if (isStatic && object != theClass) {
MetaClass mc = registry.getMetaClass((Class) object);
mc.getProperty(sender, object, name, useSuper, fromInsideClass);
return;
}
// .....
//----------------------------------------------------------------------
// listener method
//----------------------------------------------------------------------
boolean ambiguousListener = false;
if (method == null) {
method = listeners.get(name);
ambiguousListener = method == AMBIGUOUS_LISTENER_METHOD;
if (method != null &&
!ambiguousListener &&
newValue instanceof Closure) {
// let's create a dynamic proxy
Object proxy = Proxy.newProxyInstance(
theClass.getClassLoader(),
new Class[]{method.getParameterTypes()[0].getTheClass()},
new ConvertedClosure((Closure) newValue, name));
arguments = new Object[]{proxy};
newValue = proxy;
} else {
method = null;
}
}
// ......
}
It goes to the block responsible for checking listener methods. Swing related classes register their listener methods so you can add a listener method like:
button.actionPerformed = { event -> .... }
instead of
button.addActionListener(new ActionListener() {
#Override
void actionPerformed(ActionEvent actionEvent) {
}
})
And here is the list of all 31 registered listeners:
Keys are the names of listeners and values are method objects that receives a closure set to a property. Of course it finds a listener method for a key actionPerformed - it gets a reference to a method
public void javax.swing.AbstractButton.addActionListener(java.awt.event.ActionListener)
and it passes a closure
{ event ->
println button.text
}
to it.
When these listeners like actionPerformed get registered to a metaclass?
Metaclass object gets initialized whenever you call a class constructor. Groovy in this case calls
org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(Object receiver, Object arg1, Object arg2)
https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/org/codehaus/groovy/runtime/callsite/AbstractCallSite.java#L245
If you follow call stack you will find these two major checkpoints:
at some point dynamic call constructor method reaches CallSiteArray.createCallConstructorSite() method that creates a metaclass object https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/org/codehaus/groovy/runtime/callsite/CallSiteArray.java#L86
MetaClassImpl.initialize() method calls addProperties() which sets up metaclass with e.g. listeners https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/MetaClassImpl.java#L3303
Inside MetaClassImpl.addProperties() Groovy lists all listener methods using BeanInfo class and registers all found listeners https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/MetaClassImpl.java#L3343
The full call stack to this MetaClassImpl.addProperties() method from IntelliJ IDEA debugger window looks like this:
You can set a breakpoint at any of these lines if you would like to dig even deeper. Hope it helps.
I injected overridden method toString into Object.metaClass:
Object.metaClass.toString ={
System.out.println("the string is $delegate")
}
and I thought that following code will execute this method:
1500.toString()
But it didn't not,nothing was printed to the console. That is what exactly confuses me: if something goes bad, then an error is to throw out; if Object.metaClass.toString is found and invoked, then the message will turn up, but why it is not working? What happened inside?
This behavior is correct, because java.lang.Integer overrides Object.toString() with its own implementation. If your assumption was correct then it would mean that you can break overridden method by forcing to use an implementation from parent class.
Consider following Groovy script:
Object.metaClass.toString = {
System.out.println("the string is $delegate")
}
class GroovyClassWithNoToString {}
class GroovyClassWithToString {
#Override
String toString() {
return "aaaa"
}
}
new GroovyClassWithNoToString().toString()
new GroovyClassWithToString().toString()
1500.toString()
Runtime.runtime.toString()
When you run it you will see something like:
the string is GroovyClassWithNoToString#3a93b025
the string is java.lang.Runtime#128d2484
You can see that GroovyClassWithNoToString.toString() called Object.toString() method and its modified version, also Runtime.toString() calls Object.toString() - I picked this class as an example of pure Java class that does not override toString() method.
As you can see overriding toString() method from Object level makes sense for classes that base on Object.toString() implementation. Classes that provide their own implementation of toString() wont use your dynamically modified method. It also explains why following code works:
Object.metaClass.printMessage = {
System.out.println("Hello!")
}
1500.printMessage()
In this example we are adding a new method called printMessage() to Object class and all classes that don't override this method will use this dynamic method we just created. Integer class does not have method like that one so it's gonna print out:
Hello!
as expected.
Also keep in mind that toString() should return a String and it's better to not print anything to output inside this method - you can end up with nasty StackOverflowError caused by circular calls to toString() method.
UPDATE: How toString() method is being picked by Groovy runtime?
Let me show you under the hood what happens when we call following script:
Object.metaClass.toString = {
System.out.println("Hello!")
}
1500.toString()
and let's see what does Groovy during the runtime. Groovy uses Meta Object Protocol (MOP) to e.g. invoke any method called in a Groovy code. In short, when you call any Java or Groovy method it uses MOP as an intermediate layer to find an execution plan for a method - call it directly or use e.g. a method that was injected dynamically.
In our case we use plain Java class - Integer. In this case Groovy will create an instance of PojoMetaMethodSite class to meta class implementation for Java class - an Integer. Every meta method is executed using one of the Groovy groovy.lang.MetaClass implementation. In this case groovy.lang.MetaClassImpl is being used. One of the last methods that picks a method to execute is MetaClassImpl.getMethodWithCachingInternal(Class sender, CallSite site, Class [] params). If you put a breakpoint in the beginning of this method and run a script with a debugger, you will see that this method is executed with following parameters:
In line 1331 you can see that helper method called chooseMethod(e.name, methods, params) is being used:
cacheEntry = new MetaMethodIndex.CacheEntry (params, (MetaMethod) chooseMethod(e.name, methods, params));
This method is responsible for picking the right method to execute when we try to invoke toString() on Integer object. Let's get there and see what happens. Here is what this method implementation looks like:
/**
* Chooses the correct method to use from a list of methods which match by
* name.
*
* #param methodOrList the possible methods to choose from
* #param arguments
*/
protected Object chooseMethod(String methodName, Object methodOrList, Class[] arguments) {
Object method = chooseMethodInternal(methodName, methodOrList, arguments);
if (method instanceof GeneratedMetaMethod.Proxy)
return ((GeneratedMetaMethod.Proxy)method).proxy ();
return method;
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/MetaClassImpl.java#L3158
Now let's see what parameters are received when we call our script:
What is most interesting in our case is the first element of methodOrList.data. It's a method object of:
public java.lang.String java.lang.Integer.toString()
which is the method toString() that Integer class overrides from its parent class. Groovy runtime picks this method, because it is the most accurate from the runtimes point of view - it is the most specific method for Integer class provided. If there is no toString() method overridden at the class level (e.g. Runtime class example I mentioned earlier) then the best candidate for invoking toString() method is a ClosureMetaMethod provided by us in Object.metaClass.toString = .... I hope it gives you a better understanding of what happens under the hood.
I don't think you can override the Object.toString() that way.
But this works:
Integer.metaClass.toString = { ->
System.out.println("the string is $delegate")
}
https://groovyconsole.appspot.com/script/5077208682987520