在上一篇中,简单的介绍了RMI的运行机制,本篇将使用RMI实现一个简单的例子。在Server端实现一个CarFactory,Client端通过远程调用CarFactory的createCar()方法来获得Car对象,Client端的classpath中不需要CarFactory和Car,而是通过配置Server端的codebase来使Client端自动下载这些类,codebase通过class-server提供HTTP下载。Server端和Client端均通过JNDI来分别绑定和获取对象。Server和Client均部署在同一台物理机上,但运行在不同的JVM里面。
为了模拟Server和Client的独立性,分别为Server和Client程序建立两个不同的工程。
Server端工程目录如下:
在Server端首先定义服务接口CarFactory:
package com.thoughtworks.davenkin.rmi.car.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface CarFactory extends Remote
{
public Car createCar(int speed) throws RemoteException;
}
该接口应该继承自Remote接口。然后定义实现接口的CarFactoryImpl类:
package com.thoughtworks.davenkin.rmi.car.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class CarFactoryImpl extends UnicastRemoteObject implements CarFactory
{
protected CarFactoryImpl() throws RemoteException
{
super();
}
@Override
public Car createCar(int speed) throws RemoteException
{
return new Car(speed);
}
}
CarFactoryImpl类实现了工厂方法creatCar(),用来制造Car对象:
package com.thoughtworks.davenkin.rmi.car.server;
import java.io.Serializable;
public class Car implements Serializable
{
private int speed;
public Car(int speed)
{
this.speed = speed;
}
public void run()
{
System.out.println("I am running at a speed of "+ speed + " per hour!");
}
}
由于Car对象需要从Server端传到Client端,故Car应该实现Serializable接口,另外定义了run()方法。CarServer类用于将CarFactoryImpl对象绑定到对应的JNDI名字上:
package com.thoughtworks.davenkin.rmi.car.server;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.util.Hashtable;
public class CarServer
{
private final InitialContext context;
public CarServer() throws NamingException, RemoteException
{
if (System.getSecurityManager() == null)
{
System.setSecurityManager(new RMISecurityManager());
}
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
context = new InitialContext(env);
}
public void createBinding() throws RemoteException, NamingException
{
context.bind("/CarFactory", new CarFactoryImpl());
}
public static void main(String args[]) throws NamingException, RemoteException, InterruptedException
{
CarServer carServer = new CarServer();
carServer.createBinding();
}
}
在src目录下编译所有java类:
javac -d ../build/ com/thoughtworks/davenkin/rmi/car/server/*.java
为了在build目录下能够启动server端程序,在build目录下建立sercuriy.policy文件:
grant {
permission java.security.AllPermission "", "";
};
在启动Server之前需要启动rmiregistry,rmiregistry可以在任意目录下启动。
在启动Server时,我们提供codebase,以使rmiregistry和Client能够自动下载需要的类文件。
我们用class-server来做codebase的http服务器,启动该http服务器:
java examples.classServer.ClassFileServer 2001 /path/to/server/build/dir
其中2001表示监听端口号。
启动Server:
java -cp path/to/server/build/dir -Djava.rmi.server.codebase="http://localhost:2001/" -Djava.security.policy=/path/to/security.policy com.thoughtworks.davenkin.rmi.car.server.CarServer
此时在class-server的终端中可以看到有类文件被下载:
reading: com.thoughtworks.davenkin.rmi.car.server.CarFactory
reading: com.thoughtworks.davenkin.rmi.car.server.Car
到此,Server端开发配置完成,接下来是Client端,建立另一个Client端工程,目录如下:
Client端只需要定义一个CarClient类:
package com.thoughtworks.davenkin.rmi.car.client;
import com.thoughtworks.davenkin.rmi.car.server.CarFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.util.Hashtable;
public class CarClient
{
public static void main(String[] args) throws NamingException, RemoteException
{
if (System.getSecurityManager() == null)
{
System.setSecurityManager(new RMISecurityManager());
}
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
InitialContext context = new InitialContext(env);
CarFactory carFactory = (CarFactory) context.lookup("/CarFactory");
carFactory.createCar(123).run();
}
}
在编译CarClient类时,由于需要用到Server端的CarFactory等类,故应将CarFactory类加入到在classpath中,在Client端的src文件夹下运行:
javac -cp /path/to/server/build/dir -d ../build com/thoughtworks/davenkin/rmi/car/client/CarClient.java
由于Client程序需要下载Server端的类文件,同样在Client端的build目录下创建security.policy文件:
grant {
permission java.security.AllPermission "", "";
};
最后运行CarClient:
java -cp /path/to/client/build/dir -Djava.security.policy=/path/to/security.policy com.thoughtworks.davenkin.rmi.car.client.CarClient
搞了一整天,一直出现一下错误:
Exception in thread "main" java.lang.NoClassDefFoundError: com/thoughtworks/davenkin/rmi/car/server/CarFactory
at com.thoughtworks.davenkin.rmi.car.client.CarClient.main(CarClient.java:24)
Caused by: java.lang.ClassNotFoundException: com.thoughtworks.davenkin.rmi.car.server.CarFactory
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... 1 more
在class-server下显示Car类和CarFactory都被下载了,但是还是说CarFactory找不到,不知道为什么。