RMI to javowy protokół do zdalnych wywołań metod. Pokażę jak fajnie w springu można z niego skorzystać. Zatem potrzeba:
- Serwera RMI gdzie będzie obiekt na którym zostanie wywołana metoda.
- Obiektu proxy (stub) na kliencie, który będzie proxy do zdalnego wywołania.
Spring uprasza pracę dając:
- Po stronie serwera używa exporterów do wystawienia beanów.
- Po stronie klienta używa FactoryBean-ów aby utworzyć odpowiednie proxy komunikujące się z obiektem na serwerze.
- Łapie wyjątki i tłumaczy je na swoje (runtime-owe) - hierarchia RemoteAccessExceptions
Utwórzmy jakiś fajny interfejs z metodą, którą będziemy wołali (rmi-api),
do niego implementację (rmi-publisher):
do niego implementację (rmi-publisher):
public class CoolServiceImpl implements CoolService { public String coolMethod(String name) { return "Hello " + name; } }i tworzę w xml-u:
<bean id="coolService" class="pl.turo.spring.rmi.CoolServiceImpl" />
Następnie wystawmy naszego bean-a na RMI za pomocą RmiServiceExporter:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"> <property name="serviceName" value="coolService"/> <property name="serviceInterface" value="pl.turo.spring.CoolService"/> <property name="service" ref="coolService"/> <property name="registryPort" value="1096"/> </bean>Wymagane są parametry:
- serviceName - nazwa w rejestrze RMI
- serviceInterface interfejs, pod którym wystawiamy obiekt
- service - bean, który wystawiamy
I w ten właśnie sposób wystawiliśmy sobie na serwerze RMI beana coolService.
Uruchomienie serwera polega na stworzeniu fabryki springa:
Przejdźmy do klienta.
Uruchomienie serwera polega na stworzeniu fabryki springa:
ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml");Podsumowując: wystawienie bean-a po RMI polega jedynie na kilku prostych linijkach xml-a.
Przejdźmy do klienta.
Pobranie usługi wymaga jedynie skorzystania z RmiProxyFactoryBean:
<bean id="coolServiceClient" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <property name="serviceInterface" value="pl.turo.spring.rmi.CoolService"/> <property name="serviceUrl" value="rmi://localhost:1096/coolService"/> </bean>gdzie serviceUrl składa się z "rmi://adres_serwera_RMI:port_na_ktorym_wystawilismy_usluge/nazwa_pod_ktora_wystawilismy"
i u mnie to
adres_serwera_RMI -> localhost
port_na_ktorym_wystawilismy_usluge -> 1096
nazwa_pod_ktora_wystawilismy -> coolService.
Bean "coolService" będzie utworzony poprzez fabrykę RmiProxyFactoryBean i będzie implementował pl.turo.spring.rmi.CoolService, nasz fajny serwis będzimy mogli wstrzykiwać do innych beanów i używać:
<bean id="anotherBean" class="pl.turo.spring.rmi.Another"> <property name="service" ref="coolServiceClient"/> </bean>
Zauważmy, że:
- wystawianie obiektu na RMI i lookup na kliencie jest kwestią konfiguracji, zatem łatwo można to testować (inna konfiguracja dla testów)
- "po obu stronach kabla" musimy mieć javę, oraz ten sam interfejs, w tym przypadku był to pl.turo.spring.CoolService
Uruchamiam serwer RMI (czyli ładuję kontekst w nodule rmi-publisher):
INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2ce908: startup date [Fri Jan 20 12:07:07 CET 2012]; root of context hierarchy INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from class path resource [META-INF/spring/app-context.xml] INFO [main] (DefaultListableBeanFactory.java:555) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@476128: defining beans [coolService,org.springframework.remoting.rmi.RmiServiceExporter#0]; root of factory hierarchy INFO [main] (RmiServiceExporter.java:393) - Looking for RMI registry at port '1096' INFO [main] (RmiServiceExporter.java:404) - Could not detect RMI registry - creating new one INFO [main] (RmiServiceExporter.java:276) - Binding service 'coolService' to RMI registry: RegistryImpl[UnicastServerRef [liveRef: [endpoint:[127.0.1.1:1096](local),objID:[0:0:0, 0]]]]Następnie w innej konsoli uruchamiam klienta (rmi-client):
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml"); Another another = context.getBean(Another.class); String janko = another.coolMethod("Janko"); System.out.println(janko); }I na konsoli widzę:
INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@8916a2: startup date [Fri Jan 20 12:07:34 CET 2012]; root of context hierarchy INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from class path resource [META-INF/spring/app-context.xml] INFO [main] (DefaultListableBeanFactory.java:555) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@30d82d: defining beans [coolServiceClient,anotherBean]; root of factory hierarchy Hello JankoCo odczytuję, że śmiga. Kiedyś usłyszałem zdanie: "stringa to i ja umiem przesłać, co z bardziej skomplikowanymi obiektami?". Stwórzmy nieco skomplikowańszą strukturę:
public Father getFather() { List<childern> childrens = new ArrayList<childern>(); childrens.add(new Child(1.1f, "Ala", Sex.F)); childrens.add(new Child(2.1f, "Róża", Sex.F)); childrens.add(new Child(3.1f, "Michał", Sex.M)); childrens.add(new Child(4.1f, "Sławek", Sex.M)); childrens.add(new Child(5.1f, "Tomek", Sex.M)); return new Father(childrens, 23f, "Janko"); }Pozwoliłem sobie jakieś stałe wartości zwracać - chodzi przecież o sprawdzenie jak się zachowa RMI. Ponownie uruchamiam serwer i klienta, wynik na konsoli:
.... Caused by: java.io.NotSerializableException: pl.turo.spring.rmi.Father at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330) at sun.rmi.server.UnicastRef.marshalValue(UnicastRef.java:274) at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:313) at sun.rmi.transport.Transport$1.run(Transport.java:159) at java.security.AccessController.doPrivileged(Native Method) at sun.rmi.transport.Transport.serviceCall(Transport.java:155) at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:662)To było do przewidzenia. Przecież jakoś obiekt musi zostać przesłany. Zatem dopisuję do klas Father i Child implements Serializable, kolejne uruchomienie już jest poprawne:
INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@8916a2: startup date [Fri Jan 20 13:35:54 CET 2012]; root of context hierarchy INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from class path resource [META-INF/spring/app-context.xml] INFO [main] (DefaultListableBeanFactory.java:555) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@30d82d: defining beans [coolServiceClient,anotherBean]; root of factory hierarchy Father{childerns=[Child{age=1.1, name='Ala', sex=F}, Child{age=2.1, name='Róża', sex=F}, Child{age=3.1, name='Michał', sex=M}, Child{age=4.1, name='Sławek', sex=M}, Child{age=5.1, name='Tomek', sex=M}], age=23.0, name='Janko'}Oczywiście dopisałem (szczerze mówiąc to pozwoliłem IntelliJ-owi dla mnie wygenerować) toString() w Father i Child. Na moje oko: śmiga.
Jak zwykle udostępniam źródła.
Brak komentarzy:
Prześlij komentarz