Negli articoli Spring Integration (parte 1) e Spring Integration (parte 2) abbiamo visto come realizzare in modo semplice un sistema EAI con Spring Integration ed abbiamo introdotto alcune delle sue componenti principali. Proseguiamo analizzando altri componenti di tipo message endpoint.
Enricher
Gli Enricher aggiungono informazioni o contenuto ad un messaggio e possono essere considerati come delle varianti dei Transformer. Sostanzialmente prelevano il messaggio dal canale di input, aggiungono informazioni all’header o al payload del messaggio e lo inviano nel canale di output. Di base gli enricher sono quindi due: Header Enricher e Payload Enricher.
Header Enricher
Di seguito è mostrato un esempio di header enricher in cui il tag <int:header>
consente di inserire nell’header proprietà custom che possono assumere sia un valore costante (header foo
nell’esempio) che riferimenti a bean (header bar
bell’esempio):
1 2 3 4 5 6 |
<int:header-enricher input-channel="inboundChannel" output-channel="internalChannel"> <int:header name="foo" value="123" /> <int:header name="bar" ref="inspector" /> <int:correlation-id value="123"/> <int:priority value="1"/> </int:header-enricher> |
Si noti inoltre che esistono tag specifici per il set di header standard come errorChannel
, correlationId
, priority
, replyChannel
, routing-slip
, etc.
E’ possibile anche determinare dinamicamente il valore che l’header deve assumere specificando un bean ed un metodo da eseguire per il calcolo del valore. In alternativa è anche possibile utilizzare una espressione SpEL:
1 2 3 4 |
<int:header-enricher input-channel="in" output-channel="out"> <int:header name="foo" method="computeValue" ref="myBean"/> <int:header name="bar" expression="payload.toUpperCase() + '_US'"/> </int:header-enricher> |
Payload Enricher
Un payload enricher aggiunge contenuto al payload del messaggio. Supponendo che il payload sia un bean caratterizzato dalle proprietà property1 e property2, il seguente XML ne ridefinisce il valore:
1 2 3 4 |
<int:enricher input-channel="internalChannel" output-channel="outboundChannel"> <int:property name="property1" value="2"/> <int:property name="property2" value="new value"/> </int:enricher> |
Gli enricher degli esempi precedenti possono essere combinati producendo la sequenza di operazioni mostrate nell’immagine seguente, in cui i due enricher sono applicati consecutivamente:
L’invio del messaggio nel canale di input può essere eseguito, ai fini del test, attraverso il seguente codice:
1 2 3 4 5 6 7 8 |
MessageChannel channel = context.getBean("inboundChannel",MessageChannel.class); TestPayload payload = new TestPayload(); payload.setProperty1( 1 ); payload.setProperty2( "string" ); Message<TestPayload> msg = MessageBuilder.withPayload( payload ).build(); channel.send(msg); |
Inspector
sarà invece:
1 2 3 4 5 6 7 8 9 |
-- header -- bar : it.javaboss.Inspector@708f5957 correlationId : 123 id : f54a7b38-b01d-5128-82b9-7b7116a72a42 priority : 1 foo : 123 timestamp : 1507887181936 -- payload -- property1 = 2; property2 = new value |
Custom enricher possono essere implementati attraverso l’utilizzo di un altro componente di Spring Integration, il Service Activator descritto nel seguito. Sostanzialmente il tag <enricher>
dispone dell’attributo request-channel
che indica un canale condiviso col service activator il quale, una volta lavorato il messaggio, lo restituisce all’enricher per poi proseguire sul canale di output.
Service Activator
Si tratta di un endpoint che consente di connettere uno bean ad un calane ed assumere il ruolo di service. I messaggi nel canale vengono prelevati dal service activator, il quale invoca il bean per processarli. Se è prodotto un output il risultato è inviato al canale di output dichiarato o, se non definito, al canale di reply specificato nell’header del messaggio.
1 2 3 4 5 6 |
<beans ...> <bean id="service" class="it.javaboss.Service" /> <int:channel id="inboundChannel" /> <int:channel id="outboundChannel" /> <int:service-activator ref="service" method="method1" input-channel="inboundChannel"/> </beans> |
Il bean che funge da service non deve avere particolari caratteristiche. Il motore semplicemente reperisce il bean indicato nella proprietà ref
del tag service-activator
ed invoca il metodo specificato nell’attributo method
. Non vi sono vincoli alla signature del metodo, quindi tutti e tre i metodi indicati nel codice riportato sotto sono ammessi. La differenza è che il motore passerà l’intero messaggio al metodo 1, il solo payload al 2 e nessun dato al 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Service { public void method1(Message<TestPayload> msg) { System.out.println("method1"); System.out.println( msg.getPayload() ); } public void method2(TestPayload payload) { System.out.println("method2"); System.out.println(payload); } public void method3() { System.out.println("method3"); } } |
Se nel tag service-activator
nessun metodo è indicato il motore invocherà il metodo 2. Alternativamente è possibile definire un bean con un solo metodo pubblico che quindi sarà invocato dal motore com service.
Non è necessario che il metodo restituisca un valore, ma se lo fa è possibile inviare il risultato in un canale di output come payload di un nuovo messaggio. E’ anche possibile istruire il motore rendendo obbligatoria la restituzione di un risultato provocando alternativamente una eccezione.
Gateway
Lo scopo dei gateway è quello di disaccoppiare il codice applicativo dalle API di Spring Integration, nascondendole e fornendo una interfaccia che è utilizzata dalla business logic in modo trasparente. Esistono due tipologie di gateway:
- Synchronous gateway bloccano l’applicazione fino a quando Spring Integration ha completato l’elaborazione della request;
- Asynchronous gateway consentono all’applicazione di continuare l’elaborazione per recuperare l’esito della lavorazione della request in un momento successivo.
L’implementazione di un gateway è molto banale, è sufficiente definire una interfaccia che può poi essere iniettata come componente in un qualsiasi bean ed utilizzato in modo totalmente trasparente per l’applicazione. Il motore, per mezzo di un GatewayProxyFactoryBean
genera un proxy per l’interfaccia che si occuperò della reali interazione con le API di Spring Integration. L’implementazione del gateway è naturalmente guidata dalla configurazione. Consideriamo ad esempio il seguente XML dove è dichiarato un messageChannel
ed uno standard output adapter che visualizza il contenuto del canale sulla consolle.
1 2 3 4 5 |
<beans ...> <int:channel id="messageChannel" /> <int:gateway service-interface="it.javaboss.TestGateway" id="gateway" default-request-channel="messageChannel"/> <int-stream:stdout-channel-adapter id="consumer" channel="messageChannel" append-newline="true" /> </beans> |
it.javaboss.TestGateway
così definita:
1 2 3 |
public interface TestGateway { public void invoke( TestPayload payload ); } |
L’interazione con SI avverrà quindi nello stesso modo con cui l’applicazione interagisce con un qualsiasi altro bean:
1 2 3 4 5 6 7 8 |
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/si-components-gateway.xml"); TestPayload payload = new TestPayload(); payload.setProperty1( 1 ); payload.setProperty2( "i'm a gateway" ); TestGateway gateway = context.getBean("gateway", TestGateway.class); gateway.invoke(payload); |
Codice Sorgente
Il codice sorgente completo degli esempi visti è scaricabile qui javaboss-si-parte3.