Swing GUI 客户端侦听器未响应远程 RMI 属性的更新

Posted

技术标签:

【中文标题】Swing GUI 客户端侦听器未响应远程 RMI 属性的更新【英文标题】:Swing GUI Client Listeners not responding to update of Remote RMI Property 【发布时间】:2012-01-10 17:23:22 【问题描述】:

当显式调用 JTree 的模型时,我在重绘 JTree 时遇到了一些麻烦(我在向其添加一些新节点后进行的调用)。

最初运行良好的代码在应用程序导出到 RMI 后失败了。

我将 DefaultTreeModel 对象存储在 Controller 类中,它是一个远程对象。

我使用 tree.addModel(controller.getModel()); 将 DefaultTreeModel 对象添加到客户端中的 JTree;

我使用订阅客户端 GUI 上的按钮的 ActionListener 来调用控制器中的方法,该方法执行“添加新节点”操作。

我使用 TreeModelListener 将消息打印到屏幕以证明模型侦听器已触发。

客户端 Swing 侦听器不能通过 RMI 工作吗?

我已经设法重现了这个问题。我包含完整的代码,但预计有人将能够根据经验得出答案。

服务器驱动类:

package server;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import client.controller.TestTreeControllerService;

import server.controller.TestTreeControllerImpl;


public class TestTreeServerStart 

    /**
     * @param args
     */
    public static void main(String[] args) 
        new TestTreeServerStart();

    

    public TestTreeServerStart() 

        try 

            LocateRegistry.createRegistry(1099);

            TestTreeControllerService c = new TestTreeControllerImpl();

             Registry registry = LocateRegistry.getRegistry();
             registry.rebind("TestTreeControllerService", c);

            System.out.println("Started the RMI Server");
        
        catch (RemoteException e) 
            System.out.println(e.getMessage());
        
    


服务器控制器实现类:

package server.controller;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

import client.controller.TestTreeControllerService;



@SuppressWarnings("serial")
public class TestTreeControllerImpl extends UnicastRemoteObject implements TestTreeControllerService 

    /**
     * 
     */
    //private static final long serialVersionUID = -8137864611400855504L;
    private DefaultTreeModel m ;


    public DefaultTreeModel getModel() 

        return m;
    


    public TestTreeControllerImpl() throws RemoteException 

        super();
        m = new DefaultTreeModel(new DefaultMutableTreeNode("Root"));

    

    public void addNodeAction() throws RemoteException 
        DefaultTreeModel m = (DefaultTreeModel) getModel();
        DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("New Node");
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) m.getRoot();
        root.add(newNode);
        //m.insertNodeInto(newNode, (DefaultMutableTreeNode) m.getRoot(), m.getChildCount(m.getRoot()));
        m.nodeStructureChanged(root);

    


客户端驱动类:

package client;
import java.rmi.Naming;
import java.rmi.RemoteException;

import client.controller.TestTreeControllerService;
import client.view.TreeTestClient;



public class TreeTestClientStart 

    /**
     * @param args
     */
    public static void main(String[] args) 


            try 
                TestTreeControllerService c = (TestTreeControllerService) Naming.lookup("rmi://localhost:1099/TestTreeControllerService");
                 new TreeTestClient(c);
            
            catch(RemoteException e) 
                System.out.println("Remote service not found: " + e.getLocalizedMessage());
            
            catch (Exception e) 
                System.out.println("Splat");
            
    


客户端控制器接口:

package client.controller;
import javax.swing.tree.DefaultTreeModel;


public interface TestTreeControllerService extends java.rmi.Remote 


    public DefaultTreeModel getModel() throws java.rmi.RemoteException;

    public void addNodeAction() throws java.rmi.RemoteException;

客户端界面:

package client.view;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;

import client.controller.TestTreeControllerService;
import client.view.action.AddNodeAction;
import client.view.action.RefreshTreeAction;



public class TreeTestClient 

    private JTree t;
    private TestTreeControllerService c;

    public JTree getTree() 
        return t;
    

    public TestTreeControllerService getController() 
        return c;
    

    public void setTree(JTree tIn) 
        t = tIn;
    

    public TreeTestClient(TestTreeControllerService cIn) 

        //Add controller
        try 
            c = cIn;

            //Draw Frame & Panel  - set dimensions
            JFrame f = new JFrame();
            f.setSize(new Dimension(800,600));
            JPanel p = new JPanel();
            p.setSize(new Dimension(800,600));

            //Create a tree and add the Model from the Controller to it
            t = new JTree();
            t.setModel(c.getModel());


            //Try a Tree Model Listener
            t.getModel().addTreeModelListener(new RefreshTreeAction(this));

            //Add listener to a button which adds nodes to the tree when clicked
            JButton addNode = new JButton("Add node");
            addNode.addActionListener(new AddNodeAction(this));

            JScrollPane s = new JScrollPane(t);


            p.add(s);
            p.add(addNode);

            p.setVisible(true);
            f.add(p);
            f.setVisible(true);
    
    catch(Exception e) 
        System.out.println("Splat");
    
    


*客户端“添加节点”动作监听器(在控制器中调用添加动作)*

package client.view.action;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.RemoteException;

import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

import client.view.TreeTestClient;


public class AddNodeAction implements ActionListener 

    private TreeTestClient treeTest;

public AddNodeAction(TreeTestClient treeTestIn) 
    treeTest=treeTestIn;

@Override
public void actionPerformed(ActionEvent arg0) 

    try 
        treeTest.getController().addNodeAction();
     catch (RemoteException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
    



客户端“刷新操作”树侦听器(打印到屏幕以证明侦听器已触发)

package client.view.action;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;

import client.view.TreeTestClient;


public class RefreshTreeAction implements PropertyChangeListener, TreeModelListener 

    private TreeTestClient treeTest;

    public RefreshTreeAction(TreeTestClient treeTestIn) 
        treeTest = treeTestIn;
    
    private void refreshTree() 
        System.out.println("Refresh tree fired");

    
    @Override
    public void treeNodesChanged(TreeModelEvent arg0) 
        refreshTree();

    

    @Override
    public void treeNodesInserted(TreeModelEvent arg0) 
        refreshTree();
    

    @Override
    public void treeNodesRemoved(TreeModelEvent arg0) 
        refreshTree();

    

    @Override
    public void treeStructureChanged(TreeModelEvent arg0) 
            refreshTree();

    
    @Override
    public void propertyChange(PropertyChangeEvent arg0) 
        refreshTree();

    


【问题讨论】:

【参考方案1】:

服务端导出的TreeModel作为客户端自己的副本序列化给客户端。服务器不知道客户端的副本会发生什么,客户端也不知道服务器的副本会发生什么。它们不是同一个对象。

【讨论】:

根据RMI小道:“远程对象本质上是通过引用传递的。远程对象引用是一个存根,它是一个客户端代理,它实现远程对象的完整远程接口集实现”。如果远程对象通过引用传递给存根,那么当它们的状态发生变化时,任何与存根交互的客户端类都可以观察到它? @7SpecialGems TreeModel 不是远程对象;它不是通过引用传递的;它像我上面写的那样被序列化,即按值传递。它们不是同一个对象。【参考方案2】:

通过将以下代码添加到订阅 GUI 上的新按钮的 ActionListener,我能够通过单击按钮检查模型的内容:

                //Loop contents of model attached to Client Tree
                for (int i=0; i<t.getModel().getChildCount(t.getModel().getRoot()); i++) 
                        System.out.println("From Tree: Row #" + i + ": " + t.getModel().getChild(t.getModel().getRoot(), i));
                

                //Loop contents of model object stored in Controller
                try 
                    for (int i=0; i<c.getModel().getChildCount(c.getModel().getRoot()); i++) 
                        System.out.println("From Controller: Row #" + i + ": " + c.getModel().getChild(c.getModel().getRoot(), i));
                    
                 catch (RemoteException e) 
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                

我发现对客户端模型的引用与控制器模型对象中维护的状态不同。 Client 循环中没有输出,但 Controller 的循环给出了正确的状态。

我随后在 GUI 中添加了一个 Swing Timer 以刷新树的模型以匹配构造函数的模型。下面是更新的 GUI 类和 GUI 刷新操作,它们可以工作:

更新的用户界面:

package client.view;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.Timer;

import client.controller.TestTreeControllerService;
import client.view.action.AddNodeAction;
import client.view.action.GUIRefreshAction;
import client.view.action.RefreshTreeAction;



public class TreeTestClient 

    private JTree t;
    private TestTreeControllerService c;

    public JTree getTree() 
        return t;
    

    public TestTreeControllerService getController() 
        return c;
    

    public void setTree(JTree tIn) 
        t = tIn;
    

    public TreeTestClient(TestTreeControllerService cIn) 

        //Add controller
        try 
            c = cIn;

            //Draw Frame & Panel  - set dimensions
            JFrame f = new JFrame();
            f.setSize(new Dimension(800,600));
            JPanel p = new JPanel();
            p.setSize(new Dimension(800,600));

            //Create a tree and add the Model from the Controller to it
            t = new JTree();
            t.setModel(c.getModel());

            //Try a listener that doesn't use the Remote object
            t.addTreeSelectionListener(new RefreshTreeAction(this));

            //Try a property change listener on the TreeModel   
            t.addPropertyChangeListener("treeModel", new RefreshTreeAction(this));

            //Try a Tree Model Listener
            t.getModel().addTreeModelListener(new RefreshTreeAction(this));

            //Add listener to a button which adds nodes to the tree when clicked
            JButton addNode = new JButton("Add node");
            addNode.addActionListener(new AddNodeAction(this));

            JScrollPane s = new JScrollPane(t);

            //Add a GUI redraw timer
            Timer timer = new Timer(1000, new GUIRefreshAction(this));
            timer.setInitialDelay(1);
            timer.start();

            p.add(s);
            p.add(addNode);

            p.setVisible(true);
            f.add(p);
            f.setVisible(true);
    
    catch(Exception e) 
        System.out.println("Splat");
    
    


GUI 刷新监听类

package client.view.action;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.RemoteException;

import client.view.TreeTestClient;

public class GUIRefreshAction implements ActionListener 

    private TreeTestClient client;

    public GUIRefreshAction(TreeTestClient clientIn) 
        client = clientIn;
    
    @Override
    public void actionPerformed(ActionEvent e) 
        //Update the Tree's Model to match latest on Server
        try 
            client.getTree().setModel(client.getController().getModel());
         catch (RemoteException e1) 
            e1.printStackTrace();
        

    


我希望这有助于对 RMI 客户端 Swing GUI 有相同要求的人更新以响应服务器上的更改。

【讨论】:

以上是关于Swing GUI 客户端侦听器未响应远程 RMI 属性的更新的主要内容,如果未能解决你的问题,请参考以下文章

Scala Swing 中的听众和反应

Tkinter GUI 未显示,但功能收到响应

Python GUI 未捕获 zeromq 消息

RMI 服务器如何响应多个 RMI 客户端调用?

RMI远程方法调用

RMI : 确定远程对象的 IP 地址