piątek, 20 stycznia 2012

Spring remoting cz.1 - RMI


RMI to javowy protokół do zdalnych wywołań metod. Pokażę jak fajnie w springu można z niego skorzystać. Zatem potrzeba:
  1. Serwera RMI gdzie będzie obiekt na którym zostanie wywołana metoda.
  2. Obiektu proxy (stub) na kliencie, który będzie proxy do zdalnego wywołania.
Spring uprasza pracę dając:
  1. Po stronie serwera używa exporterów do wystawienia beanów.
  2. Po stronie klienta używa FactoryBean-ów aby utworzyć odpowiednie proxy komunikujące się z obiektem na serwerze.
  3. Łapie wyjątki i tłumaczy je na swoje (runtime-owe) - hierarchia RemoteAccessExceptions

Wszystko wyjaśni przykład:
Tworzę 3 moduły mavena :
  • rmi-api - zawiera interfejs, który wystawiamy po RMI
  • rmi-publisher - zawiera implementację interfejsu (tutaj stoi serwer RMI)
  • rmi-client - tutaj jest klient wywołujący metodę na zdalnym obiekcie


Utwórzmy jakiś fajny interfejs z metodą, którą będziemy wołali (rmi-api),



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
registryPort możemy ominąć - domyślna wartość to 1099.
I w ten właśnie sposób wystawiliśmy sobie na serwerze RMI beana coolService.

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 Janko
Co 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ę:


Jak widać mamy ojca, który ma listę dzieci. Implementacja interfejsu:

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