Observer über RMI ?

Status
Nicht offen für weitere Antworten.

AURA

Mitglied
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 :
Code:
   Server.addObserver(this);
Die Klasse die ich übergebe ist Serializable und implementiert die Update-Methode.

Update Methode im Client :
Code:
  public void update(Observable arg0, Object arg1) {
		System.out.println("Update");
  }
Wenn ich nun am Server
Code:
   setChanged();
   notifyObservers();
aufrufe erscheint die Ausgabe "Update" im Server bzw. in der Console des Servers !?!?!?!?

Kann mir das jemand erklären ?

Gruß
Jochen
 
Hier ein altes Beispiel für RMI Callback. Es ist ein einfacher Zähler, der von
allen Clients gleichzeitig verwendet wird.
Code:
import java.rmi.*;

public interface CounterListener extends Remote {

  public void update(Integer counter) throws RemoteException;

}
Code:
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;

}
Code:
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);

  }

}
Code:
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) {}
        }
      }
    );

  }

}
Code:
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
Code:
grant {
  // Mache was du wolle
  permission java.security.AllPermission;
};
build.bat
Code:
@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
Code:
@echo off
java -Djava.security.policy=./policy -cp .;classes; CounterServer
exec-3clients.bat
Code:
@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
Code:
counter
  |-- classes
  |-- src
Die Sourcecodedateien kommen ins counter\src-Verzeichnis, die Batchdatei und policy in counter.

Gruß,
Michael
 
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
 
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

Code:
 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
 
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 :

Code:
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
 
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
Code:
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RemoteObserver extends Remote
{
  public void update(RemoteObservable observable, Object obj) throws RemoteException;
}
Code:
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);
  }
}
Code:
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;
}
Code:
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();
      }
    }
  }
}
Code:
import java.rmi.RemoteException;

public interface Server extends RemoteObservable
{
  public void hello() throws RemoteException;
}
Code:
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();
    }
  }
}
Code:
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.
 
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
 
AURA hat gesagt.:
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
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.
Code:
  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
 
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?
 
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.

Code:
 public void addObserver(RemoteObserver observer, int priority)
 
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:
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben