拥抱Jini:从Starter Kit 2.0开始(第二部分)
Jini代表着分布式计算技术的深刻革命,其目的是通过对分布式资源的高效处理,将网络逐渐变成一个方便灵活且易于管理的工具,
这样用户或任何其它可计算实体在Jini系统中都能够很方便地发现对其有用的资源,从而完成各种分布式计算。继本文第一部分介绍了Jini的基本概念和实
现原理后,本文第二部分将介绍如何借助Jini Technology Starter Kit v2.0开发基于Jini的软件系统。
怎样开发Jini?
下
面以一个简单的计算器程序为例,讲述如何开发基于Jini的分布式应用。从这个例子可以看出Jini的一些核心思想:如果有人将计算器作为一种服务成功地
加入到Jini网络中,那么所有需要对两个操作数进行加、减、乘、除这类四则运算的硬件设备或者软件组件,就都可以使用这一服务了。或许这个范例本身的功
能还微不足道,但只要稍微类比一下打印服务、购书服务或者订餐服务,就不难体会到Jini的真正强大之处。
服务接口
Java
语言中的接口(Interface)是实现分布计算的关键,一个类通过共同定义的接口(如计算器接口Calculator),能够调用位于另一台计算机上
的服务。一个实现了Calculator接口的类承诺它的行为是一个计算器,并且清楚如何响应客户端提交的请求。在Jini中,接口可以看成是服务器和客
户端之间的服务合同(contract),因此两者都必须清楚接口的具体细节。下面定义的计算器接口Calculator,无论是服务器还是客户端都需要
用到。
清单8:Calculator.java import java.rmi.Remote; import java.rmi.RemoteException; /** * 计算器服务接口 */ public interface Calculator extends Remote { // 加法操作接口 public double add(double firstOperand, double secondOperand) throws RemoteException; // 减法操作接口 public double subtract(double firstOperand, double secondOperand) throws RemoteException; // 乘法操作接口 public double multiply(double firstOperand, double secondOperand) throws RemoteException; // 除法操作接口 public double divide(double firstOperand, double secondOperand) throws RemoteException; } |
需要注意的是,由于Jini在实现远程计算时所使用的底层机制是RMI,因此Calculator接口必须继承java.rmi.Remote,并且接口中的每个方法都必须抛出java.rmi.RemoteException异常。
服务代理
在Jini
这一分布式环境中,对服务的访问总是通过一个由服务自身所提供的对象,即服务代理(Proxy)来完成的。当一个客户需要使用某个Jini服务时,服务代
理将被下载到客户端,这样客户就可以像调用其他普通对象一样来调用它了,从而达到使用和控制Jini服务的目的。从某种意义上说,Jini的作用就是协助
客户端从网络上下载Java字节码,并且安全地执行它们。
这种可下载的服务代理的思想,是使得Jini在不显示
安装任何驱动程序或者软件的情况下,仍然能够使用服务或者设备的关键原因。例如,一台打印机负责在Jini网络中公布自己的服务代理,并且保证该服务代理
拥有控制这台打印机的能力,这样凡是需要使用打印机的应用程序就只用下载服务代理,就完全能够使用这台打印机了,而不必关心服务代理是如何实现的,也不必
了解它是如何与设备进行交互的。
具体到计算器服务来说,它的服务代理必须能够完成对两个操作数的加、减、乘、除运算,其完整的代码如清单9所示:
清单9:CalculatorProxy.java import java.io.Serializable; import java.rmi.RemoteException; import net.jini.core.lookup.ServiceID; import net.jini.lookup.ServiceIDListener; /** * 计算器服务代理 */ public class CalculatorProxy implements Calculator, ServiceIDListener, Serializable { public CalculatorProxy() throws RemoteException { super(); } // 获得服务标识符 public void serviceIDNotify(ServiceID id) { System.out.println("Service ID is: " + id); } // 加法操作 public double add(double firstOperand, double secondOperand) throws RemoteException { return firstOperand + secondOperand; } // 减法操作 public double subtract(double firstOperand, double secondOperand) throws RemoteException { return firstOperand - secondOperand; } // 乘法操作 public double multiply(double firstOperand, double secondOperand) throws RemoteException { return firstOperand * secondOperand; } // 除法操作 public double divide(double firstOperand, double secondOperand) throws RemoteException { return firstOperand / secondOperand; } } |
计
算器的服务代理CalculatorProxy实现了Calculator接口,它给出了加、减、乘、除运算的具体实现。
ServiceIDListener是CalculatorProxy实现的另外一个接口,该接口中的方法serviceIDNotify将在服务成功加
入Jini网络中时被调用,通过它可以获得该服务的唯一标识符,Jini网络中的每个服务都有自己唯一的标识符,查找服务利用它来区分不同的服务。此外,
由于CalculatorProxy对象需要通过网络进行传送,因此CalculatorProxy还必须实现Serializable接口。
服务发布
当服务接口和服务代理都定义好后,为了使其他硬件设备或者软件组件可以找到并使用该服务,首先需要在Jini网络中发布它,这个过程称为加入。通常的做法是给出一个所谓的"封装"程序(Wrapper),由它来负责定位一个查找服务,并通过查找服务来发布自己的服务代理。
封装程序要做的第一件事就是为这个服务配置一个安全管理器。在运行封装程序时,可以将安全策略文件作为命令行参数传入,但如果用户忘记输入安全策略文件,封装程序应该为他创建一个默认的安全管理器java.rmi.RMISecurityManager:
private void initializeSecurityManager() { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } |
接下去封装程序应该在Jini网络中找到一个查找服务,或者说向Jini体系结构请求一个查找服务。定位查找服务比较简单的做法是使用LookupDiscoveryManager类,它可以用来发现附近网络上的查找服务
LookupDiscoveryManager manager = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS, null, null); |
LookupDiscoveryManager能驱动多播和单播发现,它的构造函数接受三个参数
- 一个字符串数组,指明用来驱动多播发现的组;
- 一个LookupLocator数组,指明用来驱动单播发现的特定查找服务;
- 一个实现了DiscoveryListener接口的接收器。
最后,封装程序通过JoinManager将服务代理加入到Jini网络中:
JoinManager joinManager = new JoinManager(proxy, null, (CalculatorProxy) proxy, manager, null); |
清单10是封装程序的完整代码:
清单10:CalculatorService.java import java.io.IOException; import java.rmi.RMISecurityManager; import net.jini.discovery.LookupDiscovery; import net.jini.discovery.LookupDiscoveryManager; import net.jini.lookup.JoinManager; /** * Jini计算器服务 */ public class CalculatorService { public CalculatorService() throws IOException { initializeSecurityManager(); initializeJoinManager(); } // 设置安全管理器 private void initializeSecurityManager() { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } // 设置加入管理器 private void initializeJoinManager() { try { // 要加入到Jini网络中的服务 CalculatorProxy proxy = new CalculatorProxy(); // 定位Jini查找服务 LookupDiscoveryManager manager = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS, null, null); // 将服务加入到Jini网络 JoinManager joinManager = new JoinManager(proxy, null, (CalculatorProxy) proxy, manager, null); } catch (IOException e) { e.printStackTrace(); } } public static void main(String args[]) { try { new CalculatorService(); // 确保Jini服务始终有效 Object keepAlive = new Object(); synchronized (keepAlive) { keepAlive.wait(); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } |

 |

|
服务客户
一旦服务通过某个查找服务成功地加入了Jini网络之后,客户就可以访问它了。对Jini客户来讲,它首先要做的一件事也是配置恰当的安全管理器:
private void initializeSecurityManager() { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } |
接下去同样是定位一个查找服务:
private void initializeServiceRegister() throws MalformedURLException, ClassNotFoundException, IOException { LookupLocator locator = new LookupLocator("jini://" + ConfigUtil.getHostName()); serviceRegistrar = locator.getRegistrar(); } |
Jini客户使用服务模板
ServiceTemplate来描述自己感兴趣的服务,这是向查找服务提交查询请求的一种常用方法。服务模板的作用就像是数据库中的SQL语句,它是查
找服务进行搜索时的依据。当Jini客户提交一个服务模板之后,查找服务就会搜索它所有已经注册的服务,找到一个与之配置的服务。下面的代码创建了一个根
据服务类型进行搜索的模板:
private void initializeServiceTemplate() { Class[] types = new Class[] { Calculator.class }; serviceTemplate = new ServiceTemplate(null, types, null); } |
实际的搜索是通过代表一个查找服务的ServiceRegistrar对象上的lookup方法来完成的,该方法连接查找服务并执行搜索,如果找到一个匹配的服务,查找服务就把该服务的服务代理传递给Jini客户:
private void searchServiceItem() throws RemoteException { serviceItem = (Calculator) serviceRegistrar.lookup(serviceTemplate); } |
Jini客户通过服务代理可以获得该服务提供的所有功能,清单11是客户程序的完整代码:
清单11:CalculatorClient.java import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.net.MalformedURLException; import java.rmi.RMISecurityManager; import java.rmi.RemoteException; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.SwingConstants; import com.sun.jini.config.ConfigUtil; import net.jini.core.discovery.LookupLocator; import net.jini.core.lookup.ServiceRegistrar; import net.jini.core.lookup.ServiceTemplate; /** * Jini计算器客户 */ public class CalculatorClient { private ServiceTemplate serviceTemplate; private ServiceRegistrar serviceRegistrar; private Calculator serviceItem; public CalculatorClient() throws IOException { try { initializeSecurityManager(); initializeServiceRegister(); initializeServiceTemplate(); searchServiceItem(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // 设置安全管理器 private void initializeSecurityManager() { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } // 定位Jini查找服务 private void initializeServiceRegister() throws MalformedURLException, ClassNotFoundException, IOException { LookupLocator locator = new LookupLocator("jini://" + ConfigUtil.getHostName()); serviceRegistrar = locator.getRegistrar(); } // 设置服务搜索模板 private void initializeServiceTemplate() { Class[] types = new Class[] { Calculator.class }; serviceTemplate = new ServiceTemplate(null, types, null); } // 搜索服务 private void searchServiceItem() throws RemoteException { serviceItem = (Calculator) serviceRegistrar.lookup(serviceTemplate); } public static void main(String[] args) { try { CalculatorClient calculator = new CalculatorClient(); CalculatorFrame frame = calculator.new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } catch (IOException e) { e.printStackTrace(); } } class CalculatorFrame extends JFrame { public static final int WIDTH = 350; public static final int HEIGHT = 200; public static final String TITLE = "Jini Calculator"; private JLabel operand1Label; private JLabel operand2Label; private JLabel operatorLabel; private JLabel resultLabel; private JTextField operand1TextField; private JTextField operand2TextField; private JTextField resultTextField; private JComboBox operatorComboBox; private JButton calculateButton; public CalculatorFrame() { initializeUI(); } // 构造用户界面 private void initializeUI() { setTitle(TITLE); setSize(WIDTH, HEIGHT); Container contentPane = getContentPane(); contentPane.setLayout(null); operand1Label = new JLabel("Operand 1", SwingConstants.CENTER); operand1Label.setBounds(20, 20, 100, 20); operand1TextField = new JTextField("", 1); operand1TextField.setBounds(20, 50, 100, 20); operatorLabel = new JLabel("Operator", SwingConstants.CENTER); operatorLabel.setBounds(120, 20, 100, 20); operatorComboBox = new JComboBox(); operatorComboBox.addItem(" + "); operatorComboBox.addItem(" - "); operatorComboBox.addItem(" * "); operatorComboBox.addItem(" / "); operatorComboBox.setBounds(145, 50, 50, 20); operand2Label = new JLabel("Operand 2", SwingConstants.CENTER); operand2Label.setBounds(220, 20, 100, 20); operand2TextField = new JTextField("", 1); operand2TextField.setBounds(220, 50, 100, 20); calculateButton = new JButton("Calculate"); calculateButton.setBounds(20, 100, 100, 20); calculateButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { compute(); } }); resultLabel = new JLabel("Result:", SwingConstants.CENTER); resultLabel.setBounds(120, 100, 100, 20); resultTextField = new JTextField("", 1); resultTextField.setBounds(220, 100, 100, 20); resultTextField.setEditable(false); contentPane.add(operand1Label); contentPane.add(operand1TextField); contentPane.add(operatorLabel); contentPane.add(operatorComboBox); contentPane.add(operand2Label); contentPane.add(operand2TextField); contentPane.add(calculateButton); contentPane.add(resultLabel); contentPane.add(resultTextField); } // 调用Jini服务完成计算 private void compute() { double operand1, operand2, result; if (operand1TextField.getText().length() > 0) { operand1 = Double.parseDouble(operand1TextField.getText()); } else { operand1 = 0.0; } if (operand2TextField.getText().length() > 0) { operand2 = Double.parseDouble(operand2TextField.getText()); } else { operand2 = 0.0; } try { switch (operatorComboBox.getSelectedIndex()) { case 0 : result = serviceItem.add(operand1, operand2); resultTextField.setText(String.valueOf(result)); break; case 1 : result = serviceItem.subtract(operand1, operand2); resultTextField.setText(String.valueOf(result)); break; case 2 : result = serviceItem.multiply(operand1, operand2); resultTextField.setText(String.valueOf(result)); break; case 3 : result = serviceItem.divide(operand1, operand2); resultTextField.setText(String.valueOf(result)); break; } } catch (RemoteException ex) { ex.printStackTrace(); } } } } |

 |

|
编译运行
假设所有源代码都放在d:\jini2_0\calculator\目录下,首先在命令行方面下用javac将它们编译成Java字节码:
D:\jini2_0\calculator> javac -classpath d:\jini2_0\lib\jini-core.jar; d:\jini2_0\lib\jini-ext.jar;d:\jini2_0\lib\sun-util.jar *.java |
由于CalculatorProxy需要在网络中通过RMI进行传输,因此要用rmic命令为它生成相应的存根代码(stub)和框架代码(skelecton):
D:\jini2_0\calculator> rmic -classpath d:\jini2_0\lib\jini-core.jar; d:\jini2_0\lib\jini-ext.jar;. CalculatorProxy |
接着将编译好的Java字节码打包成calculator.jar,并复制到d:\jini2_0\lib\目录下:
D:\jini2_0\calculator> jar cvf calculator.jar *.class D:\jini2_0\calculator> copy calculator.jar d:\jini2_0\lib\ |
如果此时HTTP服务器、RMI激活守护进程和Reggie查找服务都已经正常运行起来了,那么就可以在命令行方式下执行下面的命令来启动Calculator服务:
D:\jini2_0> java -cp lib\jini-core.jar;lib\jini-ext.jar;lib\calculator.jar -Djava.security.policy=config\all.policy -Djava.rmi.server. codebase=http://%computername%:8080/calculator.jar CalculatorService Service ID is: 45f2798b-bf05-428f-8377-0c620e0916b1 |
而要启动Calculator客户端程序,则应该使用下面的命令:
D:\jini2_0> java -cp lib\jini-core.jar;lib\jini-ext.jar; lib\sun-util.jar;lib\calculator.jar -Djava.security.policy=config\all.policy CalculatorClient |
虽然此处为了简化问题,是在同一台计算机上运行Jini的服务端和客户端,但在实际运用时是可以在一台计算机上运行CalculatorService,而在另一台计算机器运行CalculatorClient的。图8是计算器客户端程序运行时的效果:
小结
Jini
在很大程度上改变了人们对网络的看法,它以Java技术为依托,把网络上的各种硬件设备和软件组件联合成单一的、动态的分布式系统,从而使网络更易于操纵
和管理,并具有更高的可配置性。Jini的目标是将所有可以联网的软硬件资源组织成一个完全自发的网络,使网络中不再需要手工的设备配制、驱动安装等专业
性较强的工作。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/374079/viewspace-130159/
,如需转载,请注明出处,否则将追究法律责任。