Java Enterprise Edition spotkanie nr 16 Java Message Service i Message-Driven Beans
Alternatywa dla RMI-IIOP asynchroniczność (asynchrony) brak blokowania możliwy jest fire-and-forget rozprężenie (decoupling) klienci nie muszą znać serwera można wyłączyć jeden serwer i podłączyć drugi niezawodność (reliability) serwer nie musi cały czas działać wielu odbiorców i nadawców 2
Style komunikacji przy pomocy komunikatów Publish/Subscribe jak radio/telewizja Point-to-Point producenci/konsumenci jak obsługa zamówień Request-Reply asynchroniczne wywoływanie procedur rzadziej spotykane implementowane przy pomocy poprzednich 3
Gwarantowane dostarczanie wiadomości guaranteed message delivery MOM utrwala wiadomość I ponawia dopóki nie otrzyma potwierdzenia; występuje w odmianach certified message delivery producent zostaje poinformowany o skonsumowaniu wiadomości store and forward producent też utrwala wiadomość I staje się niezależny od MOM trwali konsumenci (durable subscription) otrzymują zaległe wiadomości (z tematu) jak zaczną działać po przerwie 4
Message oriented middleware (MOM) Przykładowe MOM: Niektóre funkcje Tibco Rendezvous, IBM Web-Sphere MQ, BEA Tuxedo/Q, Sun Java System Messaging Server, Microsoft MSMQ, Sonic Software SonicMQ i gwarantowanie dostarczenia wiadomości odporność na błędy równoważenie obciążenia wykrywanie nieaktywnych odbiorców SOAP po JMS FioranoMQ 5
Java Message Service (JMS) podobny pomysł do JDBC i JNDI API do wysyłania i odbierania komunikatów Service Provider Interface (SPI) 6
JMS z klienta 7
Przykład producer import javax.jms.*; import javax.naming.initialcontext; public class Producent { public static void main(string[] args) throws Exception { InitialContext ctx = new InitialContext(...); TopicConnectionFactory factory = (TopicConnectionFactory) ctx.lookup("jms/mojafabrykat"); TopicConnection connection = factory.createtopicconnection(); //pierwszy parametr oznacza, że nie stosujemy transakcji //drugi dotyczy potwierdzeń i nie ma znaczeniu przy producencie TopicSession session = connection.createtopicsession(false, Session.AUTO_ACKNOWLEDGE); Topic topic = (Topic)ctx.lookup("jms/mojT"); TopicPublisher publisher = session.createpublisher(topic); TextMessage msg = session.createtextmessage(); msg.settext("to jest komunikat."); publisher.send(msg); publisher.close(); connection.close(); 8
Przykład subscriber,,, TopicSubscriber subscriber = session.createsubscriber(topic); subscriber.setmessagelistener(new Consumer()); connection.start(); BufferedReader commandline = new BufferedReader(new InputStreamReader(System.in)); while (true) { String s = commandline.readline(); if (s.equalsignorecase("exit")) { connection.close(); break;... class Consumer implements MessageListener { public void onmessage(message message) { try { TextMessage textmessage = (TextMessage) message; String text = textmessage.gettext(); System.out.println(text); catch (JMSException jmse) { jmse.printstacktrace(); 9
Wstrzeliwanie zależności Zamiast pobierać zasoby z JNDI można zastosować DI, ale tylko w obiektach działających na kontenerze @Resource(mappedName = "jms/mojafabrykat") private static TopicConnectionFactory factory; @Resource(mappedName = "jms/mojt") private static Topic topic; 10
Rodzaje interfejsów ConnectionFactory Session QueueConnectionFactory QueueSession TopicConnectionFactory TopicSession Connection MessageProducer QueueConnection QueueSender TopicConnection TopicPublisher Destination MessageConsumer Queue Topic QueueReceiver, QueueBrowser TopicSubscriber 11
Rodzaje wiadomości BytesMessage ObjectMessage TextMessage StreamMessage MapMessage Wiadomości mogą mieć nagłówki. 12
Message-Driven Beans 13
MDB Niedostępny dla klientów Zazwyczaj implementuje javax.jms.messagelistener i odbiera wiadomości JMS dostaje wszystkie wiadomości (chyba, że używamy selectora) trzeba się nagimnastykować żeby odpowiedzieć wyjątki zgłaszane podczas przetwarzania wiadomości nie dotrą do klienta nie ma stanu kontener odpowiada za równoległe przetwarzanie (nie można nic zakładać o kolejności obsługi wiadomości) Od EJB 2.1 konektory Java EE Connector Architecture mogą stanowić źródło komunikatów zyskujemy możliwość asynchronicznej komunikacji z innymi systemami 14
Alternatywy Własne obiekty odpalają Session Beany Trzeba napisać kod, który zarejestruje nasz obiekt jako konsumenta. Trzeba obsługiwać wielowątkowość. Trzeba samemu startować konsumentów. Nie możemy korzystać z usług kontenera. Session Bean jako konsument Bean jest jednowątkowy i jeżeli obsługuje właśnie inne (zwykłe) żądanie, nie będzie mógł obsłużyć wiadomości. Co jak nie ma beanów w chwili nadejścia wiadomości? 15
Cykl życia wymagany jest bezargumentowy konstruktor dla JMS trzeba implementować: javax.jms.messagelistener z metodą onmessage(message) opcjonalnie można implementować javax.ejb.messagedrivenbean setmessagedrivencontext(m essagedrivencontext) kontener inicjalizuje beana kontekstem (alternatywnie można uzyskać z DI) ejbremove() - alternatywnie można adnotować jakąś metodę @PreDestroy 16
Przykład @MessageDriven(activationConfig = { @ActivationConfigProperty ( ropertyname = "destinationtype", propertyvalue = "javax.jms.topic") ) public class MyFirstMDB implements MessageListener { public MyFirstMDB() { System.out.println("MyFirstMDB()"); public void onmessage(message msg) { if (msg instanceof TextMessage) { TextMessage tm = (TextMessage) msg; try { String text = tm.gettext(); System.out.println("Odebrany komunikat : " + text); catch (JMSException e) { e.printstacktrace(); @PreDestroy public void remove() { System.out.println("MyFirstMDB.remove()"); 17
Alternatywa w deskryptorze <?xml version="1.0" encoding="utf-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee "xmlns:xsi="http://www.w3.org/2001/ XMLSchema-instance" version="3.0"xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"> <enterprise-beans> <message-driven> <ejb-name>logbeandd</ejb-name> <ejb-class>examples.messaging.dd.logbean</ejb-class> <messaging-type>javax.jms.messagelistener</messaging-type> <transaction-type>bean</transaction-type> <message-destination-type>javax.jms.topic</message-destination-type> <activation-config> <activation-config-property> <activation-config-property-name>destinationtype</...> <activation-config-property-value>javax.jms.topic</...> </activation-config-property> </activation-config> </message-driven> </enterprise-beans> </ejb-jar> Mapowanie na konkretny temat/kolejkę wskazujemy w deskryptorze kontenera (ze względu na przenośność) 18
Opcjonalna zawartość @MessageDriven oraz <message-driven> @ActivationConfigProperty( propertyname = "destinationtype", propertyvalue = "javax.jms.topic" ) <activation-config-property> <activation-config-property-name> destinationtype </activation-config-property-name> <activation-config-property-value> javax.jms.topic </activation-config-property-value> </activation-config-property> @ActivationConfigProperty( propertyname="messageselector", propertyvalue="jmstype = 'ala' AND ma = 'kota'" ) m.setstringproperty("ma","kota") składnia wzorowana na SQL <activation-config-property> <activation-config-property-name> messageselector </activation-config-property-name> <activation-config-property-value> JMSType='ala' AND ma='kota' </activation-config-property-value> </activation-config-property> @ActivationConfigProperty( propertyname="subscriptiondurability ", propertyvalue="nondurable" ) o tranzakcjach jeszcze będzie <activation-config-property> <activation-config-property-name> subscriptiondurability </activation-config-property-name> <activation-config-property-value> NonDurable </activation-config-property-value> </activation-config-property> 19
Dalsze zagadnienia Transakcje konsument i producent nie należą do tej samej transakcji (wiadomość pojawia się dopiero po zacommitowaniu) Bezpieczeństwo nie ma standardowego sposobu przekazywania security identity Równoważenie obciążenia pośrednik i model pull to kontener jest odbiorcą wiadomości, a nie poszczególne egzemplarze wszystkie kontenery w klastrze są odbiorcami (i dla tematu i dla kolejki) Kolejność przetwarzania komunikatów nie jest gwarantowana przez kontener Metody @PreDestroy są woływana rzadko (kontenery często stosują pule beanów) trzeba sprzątać zanim zgłosi się system exception (np. EJBException) 20
Poison message Poison message to wiadomość nieustannie transmitowana przez JMS destination, bo konsument nieustannie jej nie potwierdza tylko przy CMT np. konsument nie umie przetworzyć wiadomości i zgłasza system exception lub woła MessageDrivenContext.setRollbackOnly() przy wycofywaniu transakcji potwierdzenie nigdy nie zostaje wysłane do JMS destination niektóre implementacje po pewnym czasie przenoszą taką wiadomość do specjalnej kolejki w BMT konsumpcja i potwierdzenie nie jest częścią transakcji, czyli można potwierdzić mimo że transakcja się nie powiedzie kiedy odbywa się potwierdzenie decydujemy ustawiając acknowledgemode na jedną z wartości Auto-ackonwledge (po pomyślnym zakończeniu onmessage()) lub Dups-ok-acknowledge (może spowodować kilkukrotną retransmisję) do sprawdzenia jako ćwiczenie co robi Message.acknowledge() 21
Odpowiadanie Kolejka do odpowiedzi Kolejka tymczasowa (związana z obiektem Connection) JMSReplyTo JMSCorrelationID tymczasowa kolejka stworzona przez bean może przepaść! 22
Kiedy stosować/nie stosować komunikatów Stosować jeżeli wykonujemy kosztowne czynności, których efekty nie muszą być natychmiastowe nie oczekujemy odpowiedzi, więc nie musimy się blokować (wartość zwrotna void) potrzebujemy efektywniejszczego równoważenie obciążenia na zasadzie pull, a nie push potrzebujemy łatwej priorytyzacja potrzebujemy łatwej integracji z systemami zastanymi chcemy utrzymać luźne sprzężenie konieczna jest wysoka niezawodność przy słabym łączu sieciowym potrzebna jest komunikacja wiele do wiele Nie stosować jak oczekujemy wyniku jeżeli nie ma pewności, że operacja się powiedzie kiedy operacja ma być częścią większej transakcji kiedy nie ufamy klientom (można samemu udoskonalić) w małych aplikacjach, gdzie pośrednik może być zbyt zasobożerny jak potrzeba obiektowości i silnych typów system ma być prosty i łatwy w testowaniu i debugowaniu 23