简体   繁体   中英

In Java Spring Integration, can a transformer element include router functionality?

I need a component that:

  1. Receives a message, enacts a transformation on both the payload and header of the message (so far it acts like a transformer).

  2. Then, based on the values passed in on the header route to an appropriate channel (acting like a header-router).

I'd like to configure this purely using annotations in java rather than XML, but I'll absolutely take what I can get in that regard. If someone knows the XML solution please pass it along.

If it helps, my use case is that I want the transformation that the transformer enacts on a message payload to be dependent on a custom loaded header value. I also want the channel that is used to sent the message from the transformer to be dependent on the header value.

I am aware of the solution that involves multiple transformers, one per transformation type as defined in the header value, and then a router. I'm trying to avoid this solution and only use at most a single router and single transformer.

Thank you for your help.

In Spring Integration channels act as any other beans. You could use a service activator to invoke a method on any bean. That bean could have the required channels injected. You could use the @Qualifier annotation to select, which channel should be injected, or just autowire a Map<String, MessageChannel> wich would get indexed by the bean name of the channel. It could pass transformed messages to those channels.

A Spring Boot app:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.config.EnableIntegration;

@SpringBootApplication
@EnableIntegration
@ImportResource("classpath:int.xml")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }


}

The gateway interface:

package demo;

public interface MyGateway {
    public void send(Object o);
}

The service to be invoked:

package demo;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final Map<String, MessageChannel> channels;

    @Autowired
    public MyService(Map<String, MessageChannel> channels) {
        super();
        this.channels = channels;
    }

    public void transformAndRoute(Message<?> in) {
        // do your business logic here
        Message<?> out = MessageBuilder.fromMessage(in).build();

        // if(something)...
        channels.get("fooChannel").send(out);
    }

}

Integration config:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration
    http://www.springframework.org/schema/integration/spring-integration.xsd">

    <int:gateway id="myGateway" service-interface="demo.MyGateway"
        default-request-channel="inChannel" />

    <int:service-activator input-channel="inChannel"
        ref="myService" method="transformAndRoute" />

    <int:channel id="inChannel" />


    <int:logging-channel-adapter id="fooChannel" level="INFO" log-full-message="true"/>

</beans>

And finally a simple integration test:

package demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=DemoApplication.class)
public class MyGatewayIT {

    @Autowired
    MyGateway myGateway;

    @Test
    public void test() {
        myGateway.send(new Object());

        // do your assertions here
    }

}

Output:

14:17:19.733 [main] DEBUG o.s.i.handler.LoggingHandler - org.springframework.integration.handler.LoggingHandler#0 received message: GenericMessage [payload=java.lang.Object@6f2cfcc2, headers={id=6791344c-07b4-d420-0d17-e2344f4bf15b, timestamp=1437826639733}]

BUT

The main benefit of developing message based systems is that you create your application out of small, loosely coupled components that are easy to test, reuse and change. Creating a component that plays two roles in a process brakes that rule. Additionally your code is realy tied to Spring Integration if you create it as above. What you could do instead is create a couple of components that each have a single responsibility, and then configure them to act in a chain.

What you could do is create a transformer that modifies the payload and headers of the message. That transformer would encapsulate your business logic and would set a header (say, myRoutingHeader ) that can be later used in routing. It would probably be even better to have a transformer for business logic, and a header enricher for adding the header. But let's assume that you are doing it in a single class. Define that class as a bean (say myTransformer ). Then in your config :

<int:channel id="inChannel/>

<!-- if performance is important consider using a SpEL expression to 
    invoke your method instead as they can be configured to be compiled -->
<int:transformer ref="myTransformer" input-channel="inChannel"
             method="transform" output-channel="routingChannel"/>

<int:channel id="routingChannel/>

<int:header-value-router input-channel="routingChannel" header-name="myRoutingHeader">
    <int:mapping value="foo" channel="fooChannel" />
    <int:mapping value="bar" channel="barChannel" />
</int:header-value-router>

sounds like chaining will do it for you

<chain input-chanel="source" output-channel="routing-channel">
 <transform/>
 <!-- any enrichment process or intermediate process can go here -->
</chain>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM