TimeService i JNDI
Transkrypt
TimeService i JNDI
TimerService i JNDI 1. Usługa TimerServiece, ● interfejsy TimedObject, TimerService, Timer, TimerHandle ● transakcje, ● zastosowanie usługi w komponentach EJB, ● cykl życia EJB, ● problemy. 2. Zasoby JNDI i wstrzykiwanie, ● wypełnianie Enterpise Naming Context, ● odwołania do JNDI, ● rodzaje wsztrzyknięć. 1 Usługa TimerService Aplikacje biznesowe często wykorzystują systemy harmonogramowania, które są odpowiedzialne przykładowo za: ● generowanie raportów, ● cykliczne operacje na danych, ● wykonywanie audytów. Usługa Timer Service kontenera EJB udostępnia interfejs zdarzeń okresowych. Licznik upływającego czasu jest związany z komponentem EJB i po upływie określonego limitu wywołuje na jego rzecz metodę ejbTimeout() lub metodę oznaczoną adnotacją @javax.ejb.Timeout. 2 Interfejs TimedObject Warunkiem korzystania przez komponent EJB z usługi Timer Service jest implementacja interfejsu: package javax.ejb; public interface TimedObject { public void ejbTimeout(Timer timer) ; } Przykład: @Stateless public class ShipMaintenanceBean implements ShipMaintenanceRemote, javax.ejb.TimedObject { public void ejbTimeout(javax.ejb.Timer timer) { // logika biznesowa } ... } 3 Interfejs TimedObject Alternatywnie można skorzystać z adnotacji @javax.ejb.Timeout dla wybranej metody: @Stateless public class ShipMaintenanceBean implements ShipMaintenanceRemote{ @Timeout public void maintenance(javax.ejb.Timer timer) { // logika biznesowa } ... } Metoda oznaczona tą adnotacją musi posiadać sygnaturę identyczną z przykładową metodą maintance(). 4 Interfejs TimedObject Aby skorzystać z systemu harmonogramowania należy zdefiniować kiedy ma nastąpić powiadomienie. Przykład: // Utworzenie obiektu Calendar Calendar time = Calendar.getInstance( ); // aktualny czas time.add(Calendar.DATE, 30); // dodanie 30 dni Date date = time.getTime( ); TimerService timerService = // pobrany np. z EJBContext timerService.createTimer( date, null); TimerService jest interfejsem poprzez który zarządzamy harmonogramem. 5 Interfejs TimerService package javax.ejb; public interface TimerService{ Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info); Timer createTimer(Date expiration, Serializable info); Timer createTimer(long initialDuration, long intervalDuration, Serializable info); Timer createTimer(long duration, Serializable info); Collection getTimers(); } ● intervalDuration – liczba milisekund pomiędzy kolejnymi wywołaniami zdarzenia, ● info – obiekt przekazywany przez referencje Timer wraz z każdym wywołaniem zdarzenia. 6 Wyjątki ● IllegalArgumentException – brak daty (null) lub liczba ujemna określajaca czas, ● IllegalStateException – metoda wywołana gdy instancja była w niewłaściwym stanie. Ogólnie wywołania mogą nastąpić zewsząd poza metodami interfejsu EJBContext (setEntityContext(), setSessionContext(), setMessageDrivenContext() itd.), ● EJBException – błąd systemowy. 7 Odwołanie harmonogramu Aby anulować ustawiony wcześniej licznik czasu należy wywołać metodę cancel(). Poniższy kod zawarty w metodzie clearSchedule(), anuluje wszystkie ustawione wcześniej w ramach komponentu harmonogramy: @Stateless public class ShipMaintenanceBean { implements ShipMaintenanceRemote @Resource javax.ejb.TimerService timerService; public void clearSchedule() { for (Object obj : timerService.getTimers()) { javax.ejb.Timer timer = (javax.ejb.Timer)obj; timer.cancel(); } } 8 Interfejs Timer package javax.ejb; public interface Timer { // Kasuje harmonogram public void cancel(); // Zwraca informacje związaną z harmonogramem w trakcie // jego stworzenia public java.io.Serializable getInfo(); // Zwraca moment następnego zdarzenia public java.util.Date getNextTimeout(); // Zwraca liczbę milisekund do następnego zdarzenia public long getTimeRemaining(); //Get a serializable handle to the timer public TimerHandle getHandle(); } 9 Interfejs TimerHandle Metoda getHandle() zwraca referencje do obiektu implementującego interfejs TimerHandle: package javax.ejb; public interface TimerHandle extends java.io.Serializable { public Timer getTimer(); } Jedyna metoda zwraca instancję Timer określającą aktualny licznik czasowy. Referencja TimerHandle ma charakter lokalny i może być wymieniana pomiędzy komponentami w ramach kontenera EJB. 10 Transakcje Licznik czasowy jest tworzony w ramach bieżącej transakcji. Jeżeli transakcja zostanie wycofana (np. poprzez zgłoszenie wyjątku) licznik nie zostanie utworzony. Z tego powodu metody zwrotne komponentów, wywoływane na skutek wyzerowania odpowiednich liczników czasowych deklaruje się z atrybutem transakcji RequiresNew: @Timeout @TransactionAttribute(TransactionAttributeTypes.REQUIRES_NEW) public void timeoutMethod() {...} 11 Bezstanowe komponenty sesyjne package com.titan.maintenance; import javax.ejb.Remote; import java.util.Date; @Remote public interface ShipMaintenanceRemote{ public void scheduleMaintenance(String ship, String description, Date dateOfTest); } 12 Bezstanowe komponenty sesyjne package com.titan.maintenance; import javax.ejb.*; import java.util.Date; import javax.annotation.Resource; @Stateless public class ShipMaintenanceBean implements ShipMaintenanceRemote{ @Timeout public void maintenance(Timer timer) { System.out.println("Wywołana metoda Timeout"); String scheduled = (String)timer.getInfo(); System.out.println(scheduled); } } 13 Bezstanowe komponenty sesyjne @Resource TimerService timerService; public void scheduleMaintenance(String ship, String description, Date dateOf) { String item = ship + " podlega przeglądowi: " + description; for (Object obj : timerService.getTimers()) { Timer timer = (Timer)obj; String scheduled = (String)timer.getInfo(); if (scheduled.equals(item)) { timer.cancel(); } } timerService.createTimer(dateOf, item); } 14 Bezstanowe komponenty sesyjne Liczniki czasowe bezstanowych komponentów sesyjnych są związane z konkretnymi typami komponentów. Po wyzerowaniu licznika, kontener wybierze jeden spośród składowanych w puli komponentów i wywoła jego metodę zwrotną. Każdy egzemplarz komponentu może obsługiwać żądania dowolnego klienta (w tym kontenera EJB). Komponent może uzyskać dostęp do TimerService zarówno w metodach @PostConstruct i @PreDestroy (choć jest to niezalecane) jak również w metodach biznesowych. 15 Problemy z @PostConstruct 1. Nie wiadomo, czy metoda w ogóle zostanie wywołana. 2. Metoda może być wywoływana wiele razy, public class StatelessTimerBean implements TimedObject { static boolean isTimerSet = false; @Resource TimerService timerService; @Resource SessionContext ctx; @PostConstruct public void init(){ if( isTimerSet == false) { long expirationDate = (Long)ctx.lookup("expirationDate"); timerService.createTimer(expirationDate, null ); isTimerSet = true; } ... } Powyższe rozwiązanie (statyczne flagi) nie pomoże w sytuacji gdy komponenty działają w ramach kilku JVM. Usuwanie istniejących liczników jest i ustawianie nowego jest nieefektywne. 16 Cykl życia Class.newInstance() @PostConstruct metoda biznesowa Nie istnieje @PreDestroy W puli gotowych komponentów ejbTimeout() 17 Komponenty sterowane komunikatami Liczniki czasowe komponentów sterowanych komunikatami działają podobnie jak w bezstanowych komponentach sesyjnych. @MessageDriven public class JmsTimerBean implements MessageListener { @Resource TimerService timerService; public void onMessage(Message message){ MapMessage mapMessage = (MapMessage)message; long date = 0; try { date = mapMessage.getLong("expirationDate"); }catch(JMSException e) { e.printStackTrace(); } this.timerService.createTimer(date, null ); } @Timeout public void timeout( ){ ... } ... } 18 Problemy TimerService Pomimo, ze liczniki czasowe są wartościowym dodatkiem do platformy EJB, posiadają one liczne ograniczenia. Ich funkcjonalność jest znacznie mniejsza niż popularnego narzędzia cron. Standard nie przewiduje możliwości skonfigurowania i ustawienia licznika w momencie uruchomienia aplikacji. Ponadto, w trakcie obsługi zdarzenia, nie istnieje możliwość rozróżnienia liczników jednorazowych od cyklicznych. W związku z tym bardzo prawdopodobna jest zmiana specyfikacji usługi w przyszłych wersjach EJB lub JEE. 19 Zasoby JNDI i wstrzykiwanie Każdy kontener EJB dysponuje własnym rejestrem zwanym ENC (Enterprise Naming Context) implementowanym przez JNDI (Java Naming and Directory Interface). W kontekście ENC można umieszczać referencje do interfejsów EJB, kolejki i tematy obsługiwane przez JMS, źródła danych (zasoby JCA) oraz inne obiektowe zasoby jak również typy proste. Zasoby mogą być umieszczane w kontekście ENC poprzez deklaracje w plikach XML lub adnotacje w kodzie źródłowym. 20 Wypełnianie JNDI ENC <ejb-jar> <enterprise-beans> <session> <ejb-name>TravelAgentBean</ejb-name> <ejb-local-ref> <ejb-ref-name>ejb/ProcessPayment</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <local> com.titan.processpayment.ProcessPaymentLocal </local> <ejb-link>ProcessPaymentBean</ejb-link> </ejb-local-ref> </session> </enterprise-beans> </ejb-jar> Komponent TravelAgentBean będzie dysponował referencją do ProcessPaymentBean poprzez interfejs ProcessPaymentLocal. Komponent będzie dostępny poprzez nazwą ejb/ProcessPayment. 21 Wypełnianie JNDI ENC import javax.ejb.EJB; import javax.ejb.Stateful; @Stateful @EJB(name="ejb/ProcessPayment", beanInterface=ProcessPaymentLocal.class, beanName="ProcessPaymentBean") public class TravelAgentBean implements TravelAgentRemote { ... } 22 Odwołanie do JNDI ENC Dostęp za pomocą jndi.lookup(): @Stateful public class TravelAgentBean implements TravelAgentRemote { } public TicketDO bookPassage(CreditCardDO card, double amount) { ProcessPaymentLocal payment = null; try { InitialContext ctx = new InitialContext(); payment = (ProcessPaymentLocal) ctx.lookup("java:comp/env/ejb/ProcessPayment"); }catch(NamingException ne){ throw new EJBException(ne); } payment.process(card, customer, amount); ... java:comp/env – ścieżka do zasobów komponentu (comp). 23 Odwołanie do JNDI ENC Dostęp za pomocą interfejsu EJBContext: @Stateful public class TravelAgentBean implements TravelAgentRemote { @Resource private javax.ejb.SessionContext ejbContext; public TicketDO bookPassage(CreditCardDO card, double amount) { ProcessPaymentLocal payment = (ProcessPaymentLocal) ejbContext.lookup("ejb/ProcessPayment"); payment.process(card, customer, amount); ... } EJBContext może korzystać ze ścieżki względnej ejb/ProcessPayment. 24 Odwołanie do JNDI ENC Dostęp za pomocą adnotacji (com.titan.TravelAgentBean/payment): @Stateful public class TravelAgentBean implements TravelAgentRemote { @EJB private ProcessPaymentLocal payment; ... } lub też (com.titan.TravelAgentBean/processPayment): @Stateful public class TravelAgentBean implements TravelAgentRemote { private ProcessPaymentLocal payment; @EJB public void setProcessPayment(ProcessPaymentLocal payment){ this.payment = payment; } ... } Drugi sposób jest szczególnie użyteczny w trakcie testów. 25 Odwołanie do JNDI ENC <ejb-jar> <enterprise-beans><session> <ejb-name>TravelAgentBean</ejb-name> <ejb-local-ref> <ejb-ref-name>ProcessPayment</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <local> com.titan.processpayment.ProcessPaymentLocal </local> <ejb-link>ProcessPaymentBean</ejb-link> <injection-target> <injection-target-class> com.titan.travelagent.TravelAgentBean </injection-target-class> <injection-target-name> payment </injection-target-name> </injection-target> </ejb-local-ref> </session></enterprise-beans> </ejb-jar> 26 Rodzaje wstrzyknięć ● komponenty EJB: @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface EJB { String name( ) default ""; // nazwa względem java:comp/env Class beanInterface( ) default Object.class; String beanName( ) default ""; String mappedName( ) default ""; // nazwa w globalnym // drzewie JNDI } ● obiekt EntityManager: @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface PersistenceContext { String name( ) default ""; String unitName( ) default ""; // nazwa jednostki utrwalania // zdefiniowana w pliku persistance.xml PersistenceContextType type( ) default TRANSACTION; PersistenceProperty[] properties( ) default {}; } 27 Rodzaje wstrzyknięć ● obiekt EntityManagerFactory (uzyskiwany za poprzez JNDI): @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface PersistenceUnit { String name( ) default ""; String unitName( ) default ""; // nazwa jednostki utrwalania // zdefiniowana w pliku // persistance.xml } przykład (JNDI): InitialContext jndiContext = new InitialContext( ); EntityManagerFactory titan = (EntityManagerFactory) jndiContext.lookup("java:comp/env/persistence/TitanDB"); 28 Rodzaje wstrzyknięć ● zasoby: @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface Resource { public enum AuthenticationType {CONTAINER, APPLICATION} String name( ) default ""; Class type( ) default Object.class; AuthenticationType authenticationType() default AuthenticationType.CONTAINER; boolean shareable( ) default true; String description( ) default ""; String mappedName( ) default ""; } przykład: @Stateful @Resource(name="jdbc/OracleDB", type=javax.sql.DataSource, mappedName="java:/DefaultDS") public class TravelAgentBean implements TravelAgentRemote { ... } 29 Rodzaje wstrzyknięć ● referencje do usług WebServices: @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface WebServiceRef { String name( ) default ""; String wsdlLocation( ) default ""; Class type( ) default Object.class; Class value( ) default Object.class; String mappedName( ) default ""; } przykład: @Stateful public class TravelAgentBean implements TravelAgentRemote { } @WebServiceRef ProcessorService service; @WebServiceRef(ProcessorService.class) Processor endpoint; ... 30 Podsumowanie Przedstawiono dwie usługi (TimerService i JNDI), które pomagają przy tworzeniu aplikacji biznesowych. Pierwsza pozwala określać liczniki czasowe, potrzebne przy realizacji różnego rodzaju harmonogramów, druga udostępnia środowisko aplikacji poprzez które komponenty mogą wymieniać się zasobami oraz uzyskiwać dostęp do różnorodnych funkcji zapewnianych przez kontener aplikacji. 31