请注意上面的截图中,RemoteRegistryUnicastMain Application只有一个线程在工作。这个和我们随后讲到的RMI-Service的线程管理方式有关。接下来我们启动RMI Client:
特别注意Client向RMI Registry查询得到remoteServiceInterface实例,这并不是RemoteServiceInterface接口的具体实现,而是RMI Stub代理。其中的endpoint端点指向RMI Service的具体服务提供位置(这个位置由RMI Service上的Skeleton提供代理服务)。
接下来开始持续调用。在这里过程中,我们关闭RMI Registry服务:
显然SingleRegistry停止工作并没有影响RemoteClient发起的调用。
3-2、Remote-Service线程管理在上文中的演示我们看到了RemoteRegistryUnicastMain处理请求时,使用了线程池。这是JDK1.5到JDK1.6+版本中RMI框架的做的一个改进。包括JDK1.5在内,之前的版本都采用新建线程的方式来处理请求;在JDK1.6版本之后,改用了线程池,并且线程池的大小是可以调整的:
sun.rmi.transport.tcp.maxConnectionThreads:连接池的大小,默认为无限制。无限的大小肯定是有问题,按照Linux单进程可打开的最大文件数限制,建议的设置值为65535(生产环境)。如果同一时间连接池中的线程数量达到了最大值,那么后续的Client请求将会报错。测试环境/开发环境是否设置这个值,就没有那么重要了。
sun.rmi.transport.tcp.threadKeepAliveTime:如果当线程池中有闲置的线程资源的话,那么这个闲置线程资源多久被注销(单位毫秒),默认的设置是1分钟。
如果您使用的是linux或者window的命令控制台执行的话,您可以通过类似如下语句进行参数设置:
java -Dsun.rmi.transport.tcp.maxConnectionThreads=2 -Dsun.rmi.transport.tcp.threadKeepAliveTime=1000 testRMI.RemoteRegistryUnicastMain
如果您使用的是eclipse控制台进行执行的话,可以通过Debug configuration -> Arguments对话框进行设置,如下图所示:
3-3、Registry和Naming在这两篇文章所列举的代码中,Registry和Naming都有出现过,在代码的注释中也提醒大家要注意这两者的使用方法。Registry和Naming都可以进行RMI服务的bind/rebind/unbind,都可以用lookup方法查询RMI服务。
Naming实际上是对Registry的封装。使用完整的URL方式对已注册的服务名进行查找。我们通过Naming类中lookup方法的源代码对Naming的工作方式进行说明。以下是Naming类中lookup方法的源代码:
/**
* Returns a reference, a stub, for the remote object associated
* with the specified <code>name</code>.
*
* @param name a name in URL format (without the scheme component)
* @return a reference for a remote object formatted URL
* @since JDK1.1
*/
public static Remote lookup(String name)
throws NotBoundException,
java.net.MalformedURLException,
RemoteException {
ParsedNamingURL parsed = parseURL(name);
Registry registry = getRegistry(parsed);
if (parsed.name == null)
return registry;
return registry.lookup(parsed.name);
}
在这个静态方法中,首先对传入的URL信息进行解析(parseURL(name)私有方法),解析完成后,将返回ParsedNamingURL私有类的一个对象parsed。我们来看看ParsedNamingURL的定义:
/**
* Simple class to enable multiple URL return values.
*/
private static class ParsedNamingURL {
String host;
int port;
String name;
ParsedNamingURL(String host, int port, String name) {
this.host = host;
this.port = port;
this.name = name;
}
}
如果解析过程中,发现URL格式问题,那么将会抛出异常;如果成功解析,则调用Registry的lookup方法。RMI Naming服务所使用的完整URL格式为:
rmi://host:port/name
其中rmi为固定格式 ,host是RMI Registry注册表所在服务地址;port为Registry注册表访问端口,默认端口为1099;name表示RMI Remote Service的注册名,在之前的代码中RMI Remote Service注册名是queryAllUserinfo。
Naming名字服务只提供RMI服务注册信息的查询,您不能使用Naming创建一个注册服务。
3-3、UnicastRemoteObject和Activatable
在上篇文章中初次讲解RMI工作原理的时候,我提到“图中呈现的是JDK1.5版本中,RMI框架其中一种运行方式”。也就是说,除了这种工作模式外RMI还有其他的工作模式?
是的,在JDK1.2版本中,由Ann Wollrath执笔加入了一种新的RMI工作方式。即通过RMI“活化”模式,将Remote Service的真实提供者移植到RMI Registry注册表所在的JVM上。
要使用这种工作模式的Remote Service实现不再继承UnicastRemoteObject类,而需要继承Activatable类(其他的业务代码不需要改变):
package testRMI;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationID;
import java.util.ArrayList;
import java.util.List;
import testRMI.entity.UserInfo;
/**
* RMI Service的一个具体实现。注意这里我们继承的是Activatable父类
* @author yinwenjie
*/
public class RemoteActivationServiceImpl extends Activatable implements RemoteServiceInterface {
/**
* 这个构造函数式必须的,不然客户端会报:<br>
* java.lang.NoSuchMethodException: testRMI.RemoteActivationServiceImpl.<init>(java.rmi.activation.ActivationID, java.rmi.MarshalledObject) 异常<br>
* 在这个方法里面,您可以调用任何一个Activatable父级的构造函数。这里我们调用的是最简单的一个
* @throws RemoteException
* @throws ActivationException
*/
public RemoteActivationServiceImpl(ActivationID id, MarshalledObject<?> data) throws RemoteException, ActivationException {
super("file://E:\\testworkspace\\testBSocket\\target\\classes", data, false , 0);
}
/**
*
*/
private static final long serialVersionUID = 6797720945876437472L;
/* (non-Javadoc)
* @see testRMI.RemoteServiceInterface#queryAllUserinfo()
*/
@Override
public List<UserInfo> queryAllUserinfo() throws RemoteException {
List<UserInfo> users = new ArrayList<UserInfo>();
UserInfo user1 = new UserInfo();
user1.setUserAge(21);
user1.setUserDesc("userDesc1");
user1.setUserName("userName1");
user1.setUserSex(true);
users.add(user1);
UserInfo user2 = new UserInfo();
user2.setUserAge(21);
user2.setUserDesc("userDesc2");
user2.setUserName("userName2");
user2.setUserSex(false);
users.add(user2);
return users;
}
}
向RMI Registry注册表进行注册的方式也需要进行更改:
package testRMI;
import java.rmi.MarshalledObject;
import java.rmi.RMISecurityManager;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupID;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Properties;
import testRMI.entity.UserInfo;
public class RemoteActivationMain {
public static void main(String[] args) throws Exception {
System.setSecurityManager(new RMISecurityManager());
System.setProperty("java.rmi.activation.port","1099");
// 设置各种参数
Properties props = new Properties();
// 最重要的参数是给rmi 激活服务相应的权限
// all.policy文件的配置在后文中说明
props.put("java.security.policy", "E:/testworkspace/testBSocket/src/all.policy");
/*
* 这里对ActivationDesc构造函数中的参数进行一下说明:
* groupID:就是在registerGroup时系统生成的ActivationGroupID对象
* className:这里就是testRMI.RemoteActivationServiceImpl具体的RemoteServiceInterface接口
* location:相当于classpath。这样注册程序才知道如何序列化类.
* 实际上之前我们建立RemoteActivationServiceImpl的构造函数时,实际上已经location写死了
* 这里只是为了讲解desc和RemoteActivationServiceImpl中构造函数的关系,又进行了一次参数指定
*
* MarshalledObject:一种序列化和反序列化方式,参见:
* http://download.oracle.com/techn ... rshalledObject.html
* */
ActivationGroupDesc groupdesc = new ActivationGroupDesc(props, null);
ActivationGroupID groupid = ActivationGroup.getSystem().registerGroup(groupdesc);
UserInfo userinfo = new UserInfo();
ActivationDesc desc = new ActivationDesc(groupid, "testRMI.RemoteActivationServiceImpl", "file://E:\\testworkspace\\testBSocket\\target\\classes", new MarshalledObject<UserInfo>(userinfo));
/*
* 是不是与UnicastRemoteObject的注册方式不一样啊。
* 之所以比较复杂,是因为这个Remote Object将会序列化到RMI Registry所在的JVM进行运行。
* 并适时被“激活”运行
* */
RemoteServiceInterface server = (RemoteServiceInterface)Activatable.register(desc);
/*
* 和执行UnicastRemoteObject注册的情况不同,这里绑定Remote Object成功后,这个JVM将会退出
* 因为之后的RMI调用将和它没有一点关系了。
* */
//Naming.bind("rmi://192.168.61.1:1099/queryAllUserinfo",server);
Registry registry = LocateRegistry.getRegistry("192.168.61.1",1099);
registry.bind("queryAllUserinfo", server);
}
}
还需要做一个进行JVM权限描述文件all.policy:
grant {
permission java.security.AllPermission “”, “”;
};
当然这是比较简单的描述(主要是为了偷懒,哈哈)。您还可以使用JDK安装目录下自带的权限描述文件,当然要进行一些修改。文件存放路径为$JAVA_HOME\jre\lib\security\java.policy,里面有一些权限信息要根据您自己的实际情况进行修改。
之后我们启动rmib——一个支持RMI“活化”模式的注册表程序:
rmid -J-Djava.security.policy=E:\testworkspace\testBSocket\src\all.policy -port 1099 -J-Djava.rmi.server.codebase=file:/E:/testworkspace/testBSocket/target/classes/
如果您使用的是Linux系统的话,命令形式如下:
export CLASSPATH=$CLASSPATH:/root/java/classes/
rmid -J-Djava.security.policy=/usr/jdk1.7.0_71/all.policy -port 1099
客户端的代码和运行方式是不需要更改的。直接运行之前的RMI Client程序就可以看到效果了。这里注意以下几个问题;
使用RMI的“活化”工作模式,原有的RMI Remote Service真实服务提供者就不再需要对RMI Client的调用进行响应了。所以您会看到在RemoteActivationMain完成注册工作后,RemoteActivationMain所在的JVM会退出,而不会像之前的RemoteUnicastMain一样,在完成注册后一直等待。如下图所示: