Webservices over MQ (2)
Door: Jethro Bakker, 5 June 2008Een tweede deel over het gebruik van Spring Webservices en JMS transport.
Ons uitgangspunt is dat er meerdere type berichten (SOAP, POX) binnen komen op dezelfde queue. In een grote organisatie is het voordeel hiervan dat je maar 1x een queue hoeft aan te vragen en daar alle berichten op zet. Eventuele nieuwe berichtsoorten kunnen dan vrij gemakkelijk toegevoegd worden. Nadeel is dat we meerdere type berichten moeten kunnen afhandelen. Een uitdaging om dit efficient en met zoveel mogelijk bestaande code te kunnen doen. We willen namelijk zo min mogelijk infrastructuur code schrijven.
Standaard biedt Spring Webservices een WebServiceMessageListener aan die je kunt gebruiken om berichten te consumeren van de queue. Deze listener ondersteunt een type bericht. Je kunt namelijk maar een message factory definieeren op een WebserviceMessageListener.
In eerste instantie had ik daar een oplossing voor bedacht die er als volgt uit ziet:
class MyWebserviceMessageListener extends WebServiceMessageListener {
private PoxMessageFactory poxMessageFactory;
private SaajSoapMessageFactory soapMessageFactory;
public void onMessage(Message message, Session session) {
if ("applicatie1".equals(message.getStringAttribute("afzender"))) {
setMessageFactory(poxMessageFactory);
} else {
setMessageFactory(soapMessageFactory);
}
}
}
Afhankelijk van een attribuut van het JMS bericht wordt besloten welke message factory gebruikt wordt. Bovenstaande code blijkt niet te werken omdat de WebServiceMessageListener initieel een message factory nodig heeft. Maar het gaat me nu even om het idee. Waarom is het niet verstandig dit zo op te lossen?
Een message driven pojo (MDP), zoals de MyWebserviceMessageListener, draait in een container. (DefaultMessageListenerContainer) Op deze container kun je het aantal consumers instellen. Bijvoorbeeld 3 of 10. Je kunt dit doen door de properties: maxConcurrentConsumers en concurrentConsumers te gebruiken. Zoals de namen van de properties al aangeven hebben we hier te maken met concurrency en onze MDP moet daarom thread safe zijn! In de referentie documentatie staat dat ook heel duidelijk:
Please also be aware that in the case where your POJO will be receiving messages on multiple threads, it is important to ensure that your implementation is thread-safe.
De oplossing zoals hier boven beschreven is niet thread safe en kan de prullenbak in omdat we meerdere concurrent consumers gaan gebruiken. De oplossing is niet thread safe omdat meerdere threads gebruik maken van dezelfde MDP. Er kunnen dus twee threads actief zijn en tegelijk een bericht verwerken. De ene thread een SOAP bericht en de andere een POX bericht. Dan gaat een van beide threads de mist in omdat het een verkeerde message factory gebruikt.
We zullen op basis van de bericht inhoud moeten beslissen of het bericht een SOAP bericht is of een standaard XML bericht. Dit doen we door de message factories in de MDP te verwijderen en te vervangen door twee JMSMessageReceivers die we zelf moeten schrijven. Zo’n receiver heeft een dependency op een message factory. In de MDP beslissen we nu naar welke receiver we gaan op basis van de bericht inhoud.
Een JMSMessageReceiver is een:
Convenience base class for JMS server-side transport objects. Contains a WebServiceMessageReceiver, and has methods for handling incoming JMS BytesMessage and TextMessage requests. Also contains a textMessageEncoding property, which determines the encoding used to read from and write to TextMessages. This property defaults to UTF-8.
Voorbeeld:
public class PoxJmsReceiver extends JmsMessageReceiver {
public void invoke(Message message, Session session) throws JMSException {
try {
handleMessage(message, session);
} catch (Exception ex) {
JMSException jmsException = new JMSException(ex.getMessage());
jmsException.setLinkedException(ex);
throw jmsException;
}
}
}
Deze oplossing heeft als nadeel dat het bericht twee keer gelezen wordt. Een keer in de MDP om te checken of het SOAP bericht is en een keer in het Spring Webservices framework. Bij kleine berichten maakt dit niet zoveel uit maar bij grote berichten kan dit geheugen problemen gaan geven. Een mogelijke oplossing voor dit probleem is om de eerste zoveel bytes van het bericht in te lezen in de MDP en op basis daarvan te besluiten of het een SOAP bericht is. Andere ideeen zijn uiteraard welkom.
