# Observer über RMI ?



## AURA (17. Nov 2005)

Hallo,

ich hab da ein kleines Problem mit RMI...

Ich hab ein Client/Server Programm geschrieben und RMI funktioniert eigentlich soweit
auch.

Nun wollte ich das Observer-Pattern einsetzten um den Client
beim Server als Observer zu registrieren bzw zu benachrichtigen wegen Update
der GUI.

Hab in der Client-Anwendung stehen :

```
Server.addObserver(this);
```
Die Klasse die ich übergebe ist Serializable und implementiert die Update-Methode.

Update Methode im Client :

```
public void update(Observable arg0, Object arg1) {
		System.out.println("Update");
  }
```
Wenn ich nun am Server 

```
setChanged();
   notifyObservers();
```
aufrufe erscheint die Ausgabe "Update" im Server bzw. in der Console des Servers !?!?!?!?

Kann mir das jemand erklären ?

Gruß
Jochen


----------



## semi (17. Nov 2005)

Hier ein altes Beispiel für RMI Callback. Es ist ein einfacher Zähler, der von 
allen Clients gleichzeitig verwendet wird.

```
import java.rmi.*;

public interface CounterListener extends Remote {

  public void update(Integer counter) throws RemoteException;

}
```


```
import java.rmi.*;

public interface Counter extends Remote {

  public void increase() throws RemoteException;
  public void decrease() throws RemoteException;
  public void addCounterListener(CounterListener obj) throws RemoteException;
  public void removeCounterListener(CounterListener obj) throws RemoteException;

}
```


```
import java.util.*;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class CounterImpl extends UnicastRemoteObject implements Counter {

  private static final long serialVersionUID = 1158553077772104186L;

  transient Integer counter=null;
  transient Vector  counterListener=null;

  public CounterImpl() throws RemoteException {

    counter = new Integer(0);
    counterListener = new Vector();

  }

  public synchronized void increase() throws RemoteException {

    counter = new Integer(counter.intValue()+1);
    System.out.println("increase(): newValue="+counter);
    fireCounterUpdate();

  }

  public synchronized void decrease() throws RemoteException {

    int n = counter.intValue();
    if(n==0) {
      System.out.println("decrease(): Value=0: no update");
      return;
    }
    counter = new Integer(n-1);
    System.out.println("decrease(): newValue="+counter);
    fireCounterUpdate();

  }

  public synchronized void addCounterListener(CounterListener obj) throws RemoteException {

    System.out.println("addCounterListener()");
    counterListener.add(obj);
    obj.update(counter);

  }

  public synchronized void removeCounterListener(CounterListener obj) throws RemoteException {

    System.out.println("removeCounterListener()");
    counterListener.remove(obj);

  }

  protected void fireCounterUpdate() throws RemoteException {

    System.out.println("fireCounterUpdate()");
    for(Enumeration e=counterListener.elements();e.hasMoreElements();)
      ((CounterListener)e.nextElement()).update(counter);

  }

}
```


```
import java.rmi.*;
import java.rmi.registry.*;

public class CounterServer {

  public static void main(String[] args) {

    System.out.println("Starting Counter...");
    System.setSecurityManager(new RMISecurityManager());
    try {
      Registry reg = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
      CounterImpl cimpl = new CounterImpl();
      reg.rebind("Counter", cimpl);
      System.out.println("Counter is running.");
    }
    catch(Exception e) {
      System.out.println(e.getMessage());
      e.printStackTrace();
    }
    Runtime.getRuntime().addShutdownHook(
      new Thread() {
        public void run() {
          try {
            System.out.println("Counter exiting...");
            sleep(2000);
          }
          catch(InterruptedException e) {}
        }
      }
    );

  }

}
```


```
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class CounterClient extends JPanel implements CounterListener, ActionListener {

  private static final long serialVersionUID = -1750045865622109711L;

  private JButton inc     = new JButton("Increase");
  private JButton dec     = new JButton("Decrease");
  private JLabel  label   = new JLabel("", JLabel.CENTER);
  private Counter counter = null;

  public CounterClient(String host) {

    super();
    setLayout(new BorderLayout());
    inc.addActionListener(this);
    dec.addActionListener(this);
    add(dec, BorderLayout.WEST);
    add(inc, BorderLayout.EAST);
    add(label, BorderLayout.CENTER);
    initCounter(host);

  }

  private void initCounter(String host) {

    try {
      UnicastRemoteObject.exportObject(this);
      System.out.println("exported");
      counter = (Counter)Naming.lookup("rmi://"+host+"/Counter");
      System.out.println("counter retrieved");
      counter.addCounterListener(this);
      System.out.println("listener registered");
    }
    catch(Exception e) {
      counter = null;
      System.out.println(e.getMessage());
      e.printStackTrace();
      System.exit(0);
    }

  }

  public void dispose() {

    try {
      counter.removeCounterListener(this);
      counter = null;
    }
    catch(RemoteException ignore) {}

  }

  public synchronized void update(Integer counter) throws RemoteException {

    label.setText(counter.toString());

  }

  public void actionPerformed(ActionEvent event) {

    if(counter!=null) {
      try {
        if(event.getSource() == inc)
          counter.increase();
        else
          counter.decrease();
      }
      catch(RemoteException e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
      }
    }

  }

  public static void main(String[] args) {

    String host = "localhost";
    if(args.length>0)
      host = args[0];
    System.setSecurityManager(new RMISecurityManager());
    final JFrame frame = new JFrame("Counter Client");
    final CounterClient counterClient = new CounterClient(host);
    frame.setContentPane(counterClient);

    Runtime.getRuntime().addShutdownHook(
      new Thread() {
        public void run() {
          counterClient.dispose();
        }
      }
    );

    frame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          frame.setVisible(false);
          System.exit(0);
        }
      }
    );

    frame.pack();
    frame.setVisible(true);

  }

}
```
policy
	
	
	
	





```
grant {
  // Mache was du wolle
  permission java.security.AllPermission;
};
```
build.bat
	
	
	
	





```
@echo off
if not exist .\classes\Counter.class goto rebuild
erase classes\*.class
:rebuild
javac -g:none -O -Xlint -classpath .\classes;.; -d .\classes src\*.java
rmic -classpath .\classes;.; -d .\classes CounterImpl
rmic -classpath .\classes;.; -d .\classes CounterClient
```
exec-server.bat
	
	
	
	





```
@echo off
java -Djava.security.policy=./policy -cp .;classes; CounterServer
```
exec-3clients.bat
	
	
	
	





```
@echo off
start javaw -cp .;classes; -Djava.security.policy=./policy CounterClient
start javaw -cp .;classes; -Djava.security.policy=./policy CounterClient
start javaw -cp .;classes; -Djava.security.policy=./policy CounterClient
```
Erstelle folgende Verzeichnisstruktur
	
	
	
	





```
counter
  |-- classes
  |-- src
```
Die Sourcecodedateien kommen ins counter\src-Verzeichnis, die Batchdatei und policy in counter.

Gruß,
Michael


----------



## AURA (17. Nov 2005)

Danke für die Antwort !

Dachte ich mir schon fast, das ich mir was eigenes bauen muß 

Danke für das Beispiel, das erleichtert einiges ;-)

Gruß

Jochen


----------



## AURA (18. Nov 2005)

Hallo,

hab mir das Beispiel jetzt mal angeschaut und so Nachgebaut.
Allerdings mit Java 5 und automatischer Stub erzeugung zur Laufzeit.

Wenn ich beim Server 


```
protected void fireCounterUpdate() throws RemoteException { 

...
}
```

aufrufe, wird zwar beim Client die Update aufgerufen aber irgendwie im Server ?!?!?!

Also die Update funktion des Clients wird auf dem Server ausgeführt ????

Wie kommt denn das ?

Gruß

Jochen


----------



## AURA (18. Nov 2005)

Habs gefunden !

Habe vergessen im Client

 UnicastRemoteObject.exportObject(this);

aufzurufen 

Nun hab ich aber das Problem, das ich es eigentlich mit Java 5 code machen wollte
und da sind ja durch _rmic_ erzeugte Stubs nicht mehr notwendig.

Beim Server funktioniert das ganze ja so :


```
Server server = new Server();
ServerInterface stub = (ServerInterface) UnicastRemoteObject.exportObject(server, 8080);
Naming.rebind("TEST", stub);
```


Wie soll das jetzt aber auf clientseite aussehen ???

Ich kann ja nicht auch nich den Client _rebinden_

Wie soll das mit Java 5 funktionieren ?

Gruß und Danke

Jochen


----------



## semi (19. Nov 2005)

Moin,

der Observer muss auch ein UnicastRemoteObject sein (siehe RemoteObserverImpl unten). 

RemoteObserverImpl im Beispiel unten delegiert dann den Aufruf zum eigentlichen 
RemoteObserver (Client). Vielleicht ist der Name der Klasse etwas unglücklich gewählt, 
mir fällt aber kein besserer ein. :wink:
Etwas "uncool" ist auch, dass der Client, also der eigentliche Observer, indirekt java.rmi.Remote
implementiert. Ein zusätzliches Interface für die Delegation (in RemoteObserverImpl) wäre vielleicht 
besser.  ???:L 

Es scheint zu funktionieren. Falls jemand eine bessere Alternative kennt, dann bitte 
melden. Ich lerne gerne dazu.

Gruß,
Michael

```
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteObserver extends Remote
{
  public void update(RemoteObservable observable, Object obj) throws RemoteException;
}
```


```
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteObserverImpl extends UnicastRemoteObject implements RemoteObserver
{
  private static final long serialVersionUID = 3878538909769626282L;
  private transient RemoteObserver observer;

  public RemoteObserverImpl(RemoteObserver observer) throws RemoteException
  {
    this.observer = observer;
  }

  public void update(RemoteObservable observable, Object obj) throws RemoteException
  {
    observer.update(observable, obj);
  }
}
```


```
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteObservable extends Remote
{
  public void addObserver(RemoteObserver observer) throws RemoteException;
  public void removeObserver(RemoteObserver observer) throws RemoteException;
}
```


```
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.rmi.RemoteException;

public class RemoteObservableSupport
{
  private List<RemoteObserver> observerList = Collections.synchronizedList(new ArrayList<RemoteObserver>());
  private RemoteObservable observable;

  public RemoteObservableSupport(RemoteObservable observable)
  {
    this.observable = observable;
  }

  public void addObserver(RemoteObserver observer)
  {
    observerList.add(observer);
  }

  public void removeObserver(RemoteObserver observer)
  {
    observerList.remove(observer);
  }

  public void removeAllObserver()
  {
    observerList.clear();
  }

  public void notifyObservers(Object obj)
  {
    for(Iterator<RemoteObserver> iterator = observerList.iterator(); iterator.hasNext();)
    {
      RemoteObserver observer = iterator.next();
      try
      {
        observer.update(observable, obj);
      }
      catch(RemoteException e)
      {
        iterator.remove();
      }
    }
  }
}
```


```
import java.rmi.RemoteException;

public interface Server extends RemoteObservable
{
  public void hello() throws RemoteException;
}
```


```
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class ServerImpl extends UnicastRemoteObject implements Server
{
  private static final long serialVersionUID = -7143787613855707999L;
  private transient RemoteObservableSupport observableSupport = new RemoteObservableSupport(this);

  public ServerImpl() throws RemoteException
  {
  }

  public void hello() throws RemoteException
  {
    observableSupport.notifyObservers("Hello");
  }

  public void addObserver(RemoteObserver observer)
  {
    observableSupport.addObserver(observer);
  }

  public void removeObserver(RemoteObserver observer)
  {
    observableSupport.removeObserver(observer);
  }

  public static void main(String[] args)
  {
    try
    {
      System.setSecurityManager(new RMISecurityManager());
      Registry reg = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
      reg.rebind("HelloServer", new ServerImpl());
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }
}
```


```
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;

public class Client implements RemoteObserver
{
  public void update(RemoteObservable observable, Object obj) throws RemoteException
  {
    System.out.println(obj);
  }

  public static void main(String[] args)
  {
    String host = "localhost";
    if(args.length>0)
      host = args[0];
    try
    {
      System.setSecurityManager(new RMISecurityManager());
      Server server = (Server)Naming.lookup("rmi://"+host+"/HelloServer");
      RemoteObserverImpl observer = new RemoteObserverImpl(new Client());
      server.addObserver(observer);
      server.hello();
      server.removeObserver(observer);
      System.exit(0);
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }
}
```
Edit: Paar kleine Fehler entfernt.


----------



## AURA (19. Nov 2005)

Hallo,

dein Beispiel hat eigentlich sehr gut funktioniert...

ich würde nur gerne mal wissen wie man das in Java 5 mit 
dynamischen Stubs macht...

Serverseitig ist es klar, da übergibt man den Stub beim rebind("Name",Stubobject) mit.

Aber wie mach ich das auf der clientseite...

Ich kann doch auch der Clientseite nicht noch ein rebind machen mit dem Gleichen namen, oder ???

Gruß und Danke

Jochen


----------



## semi (19. Nov 2005)

AURA hat gesagt.:
			
		

> Hallo,
> 
> dein Beispiel hat eigentlich sehr gut funktioniert...
> 
> ...


Bei dem Beispiel von meiner letzten Antwort werden die Stubs automatisch generiert.
Du brauchst rmic nicht mehr aufzurufen. Die Voraussetzung ist einfach nur, dass das 
Callback auch ein UnicastRemoteObject ist. Die Proxies und Stubs werden automatisch 
generiert, da brauchst clientseitig nichts mehr zu tun.

Du kannst es Dir anschauen, wenn Du folgendes in die Methode ServerImpl#addObserver(...)  
einfügst.
	
	
	
	





```
if(java.lang.reflect.Proxy.isProxyClass(observer.getClass()))
      System.out.println(java.lang.reflect.Proxy.getInvocationHandler(observer).getClass().getName());
```
Die Ausgabe ist "java.rmi.server.RemoteObjectInvocationHandler".

Schau dir auch die Ausgabe der Option

-Djava.rmi.server.logCalls=true 

beim Start der Clients und des Servers an. 

Gruß,
Michael


----------



## AURA (19. Nov 2005)

Alles klar, danke dir sehr !!!!

funktioniert echt gut !

Nochmal Danke !

Gruß

Jochen


----------



## Guest (19. Nov 2005)

Hallo,

Wenn man (bei einem Remote-Objekt oder bei einem serialisierten Objekt) der statischen Kalssen-Variable serialVersionUID keinen Wert zuweist, dann gibt der compiler eine Warnung. Ohne diese Variable läuft der Code auch problemlos. Wozu braucht man diese Variable und wie bestimmt man den Wert?


----------



## Guest (19. Nov 2005)

Funktioniert das letzte Beispiel auch mit JRE 1.3 ?


----------



## Zebani (19. Nov 2005)

Hallo,

ein Nachteil von Oberver/Observable-Patterns ist, meiner Meinung nach, dass es unklar ist, welcher Observer zuerst benachrichtigt wird. Die Methode addObserver könnte z.B. um einen zusätzlichen Argument erweitert werden, damit die Prioritäten der Observers festgelegt werden können.


```
public void addObserver(RemoteObserver observer, int priority)
```


----------



## semi (19. Nov 2005)

Anonymous hat gesagt.:
			
		

> Hallo,
> 
> Wenn man (bei einem Remote-Objekt oder bei einem serialisierten Objekt) der statischen Kalssen-Variable serialVersionUID keinen Wert zuweist, dann gibt der compiler eine Warnung. Ohne diese Variable läuft der Code auch problemlos. Wozu braucht man diese Variable und wie bestimmt man den Wert?


Lese bitte die Beschreibung zu java.io.Serializable.


> If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, *it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations*, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members


serialVersionUID kannst du mit dem Programm "serialver" (bei jdk dabei) generieren lassen.
Compiliere zuerst die serialisierbare Klasse, dann rufe "serialver Klassenname" auf.
Die Zeile kopierst du, fügst sie in den Code ein und compilierst das Zeug noch mal.
Das brauchst du nur einmal zu tun.


			
				Anonymous hat gesagt.:
			
		

> Funktioniert das letzte Beispiel auch mit JRE 1.3 ?


Jajn. Du müsstest den ganzen JDK 1.5 Code entfernen/umschreiben und die Stubs mit "rmic" (siehe java\bin Verzeichnis) generieren.

@Zebani
Wenn es unbedingt benötigt wird. Für kleine Sachen ist das Observer-Pattern aber nicht schlecht.
Ich würde eher auf JMS zurückgreifen, statt solche RMI Akrobatik zu veranstalten, aber mich fragt
keiner. :wink:


----------

