标签:write err 使用方法 ceo adc 它的 halo create jtable
目录
RMI 中有三个重要的角色:注册中心(Registry)、客户端(Client)、服务端(Server)。
在 RMI 中也要先进行服务注册,客户端从注册中心获取服务。为了屏蔽网络通信的复杂性,RMI 提出了 Stub(客户端存根)和 Skeleton(服务端骨架)两个概念,客户端和服务端的网络信都通过 Stub 和 Skeleton 进行。
总结: 整体还是可以分为三部分,服务注册、服务发现、服务调用。
首先回顾一下 RMI 服务发布的使用方法:
@Test
public void server() {
// 1. 服务创建及发布。注意:HelloServiceImpl extends UnicastRemoteObject
HelloService service = new HelloServiceImpl();
// 2. 创建注册中心:创建本机 1099 端口上的 RMI 注册表
Registry registry = LocateRegistry.createRegistry(1099);
// 3. 服务注册:将服务绑定到注册表中
registry.bind(name, service);
}
总结: RMI 服务发布有三个流程:
服务创建及发布和创建注册中心流程完全相同,至于服务注册则是将 service 注册到一个 map 中,非常简单。所以服务的注册主要围绕服务创建及发布展开。
服务的发布的关键点有以下几个:
无论是 HelloServiceImpl 还是 Registry 都是 Remote 的子类,准确的说是 RemoteObject 的子类。RemoteObject 最重要的属性是 RemoteRef ref
,RemoteRef 的实现类 UnicastRef,UnicastRef 包含属性 LiveRef ref
。LiveRef 类中的 Endpoint、Channel 封装了与网络通信相关的方法。类结构如下:
HelloServiceImpl 的构造器中调用父类 UnicastRemoteObject,最终调用 exportObject((Remote) this, port)
protected UnicastRemoteObject(int port) throws RemoteException {
this.port = port;
exportObject((Remote) this, port);
}
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException {
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
而 Registry createRegistry(int port)
创建注册中心时也会调用 exportObject 方法。
public RegistryImpl(int port) throws RemoteException
LiveRef lref = new LiveRef(id, port);
setup(new UnicastServerRef(lref, RegistryImpl::registryFilter));
}
private void setup(UnicastServerRef uref) throws RemoteException {
ref = uref;
uref.exportObject(this, null, true);
}
总结: Registry 和 HelloServiceImpl 最终都调用 exportObject 方法,那 exportObject 到底是干什么的呢?从字面上看 exportObject 暴露对象,事实上正如其名,exportObject 打开了一个 ServerSocket,监听客户端的请求。
public Remote exportObject(Remote impl, Object data, boolean permanent)
throws RemoteException {
// 1. 创建本地存根,封装网络通信
Class<?> implClass = impl.getClass();
Remote stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
...
// 2. 服务暴露,this 是指 RemoteObject 对象
Target target = new Target(impl, this, stub, ref.getObjID(), permanent);
ref.exportObject(target);
return stub;
}
总结: exportObject 核心的方法有两个:一是生成本地存根的代理对象;二是调用 ref.exportObject(target)
启动 socket 服务。
注意:exportObject 时会先将 impl 和 stub 等信息封装到 Target 对象中,最终注册到 ObjectTable。
在 Util.createProxy() 方法中创建代理对象。
public static Remote createProxy(Class<?> implClass, RemoteRef clientRef,
boolean forceStubUse) throws StubNotFoundException {
Class<?> remoteClass = getRemoteClass(implClass);
// 1. 是否存在以 _Stub 结尾的类。remoteClass + "_Stub"
// forceStubUse 表示当不存在时是否抛出异常
if (forceStubUse ||
!(ignoreStubClasses || !stubClassExists(remoteClass))) {
return createStub(remoteClass, clientRef);
}
// 2. jdk 动态代理
final ClassLoader loader = implClass.getClassLoader();
final Class<?>[] interfaces = getRemoteInterfaces(implClass);
final InvocationHandler handler = new RemoteObjectInvocationHandler(clientRef);
return (Remote) Proxy.newProxyInstance(loader, interfaces, handler);
}
总结: 创建代理对象有两种情况:
UnicastRef#invoke(Remote, Method, Object[], long)
。跟踪 LiveRef#exportObject 方法,最终调用 TCPTransport#exportObject 方法。
public void exportObject(Target target) throws RemoteException {
// 1. 启动网络监听,默认 port=0,即随机启动一个端口
synchronized (this) {
listen();
exportCount++;
}
// 2. 将 Target 注册到 ObjectTable
super.exportObject(target);
}
总结: 最终服务暴露时做了两件事,一是如果 socket 没有启动,启动 socket 监听;二是将 Target 实例注册到 ObjectTable 对象中。
private void listen() throws RemoteException {
TCPEndpoint ep = getEndpoint();
int port = ep.getPort();
if (server == null) {
server = ep.newServerSocket();
Thread t = new NewThreadAction(new AcceptLoop(server),
"TCP Accept-" + port, true));
t.start();
} catch (IOException e) {
throw new ExportException("Listen failed on port: " + port, e);
}
}
}
ObjectTable 用来管理所有发布的服务实例 Target,ObjectTable 提供了根据 ObjectEndpoint 和 Remote 实例两种方式查找 Target 的方法。先看注册:
private static final Map<ObjectEndpoint,Target> objTable = new HashMap<>();
private static final Map<WeakRef,Target> implTable = new HashMap<>();
// Target 注册
static void putTarget(Target target) throws ExportException {
ObjectEndpoint oe = target.getObjectEndpoint();
WeakRef weakImpl = target.getWeakImpl();
synchronized (tableLock) {
if (target.getImpl() != null) {
...
objTable.put(oe, target);
implTable.put(weakImpl, target);
}
}
}
那实例查找也就很简单了,之后就可以根据 Target 对象获取本地存根 stub。
static Target getTarget(ObjectEndpoint oe) {
synchronized (tableLock) {
return objTable.get(oe);
}
}
public static Target getTarget(Remote impl) {
synchronized (tableLock) {
return implTable.get(new WeakRef(impl));
}
}
当服务 HelloService 和 Registry 均已创建并发布后,之后需要将服务绑定到注册中心。这一步就很简单了,代码 registry.bind(name, service)
。
// 服务名称 -> 实例 impl
private Hashtable<String, Remote> bindings = new Hashtable<>(101);
public void bind(String name, Remote obj)
throws RemoteException, AlreadyBoundException, AccessException {
checkAccess("Registry.bind");
synchronized (bindings) {
Remote curr = bindings.get(name);
if (curr != null)
throw new AlreadyBoundException(name);
bindings.put(name, obj);
}
}
总结: service 绑定到注册中心实际就很简单了,将服务名称和实例保存到 map 中即可。查找时可以通过 name 查找到 impl,再通过 impl 在 ObjectTable 中查找到对应的 Target。
服务暴露主要完成两件事:一是服务端生成本地存根 stub,并包装成 Target 对象,最终注册到 ObjectTable 中;二是启动 ServerSocket 绑定端口,监听客户端的请求。 又可以分为普通服务暴露和注册中心暴露,两种服务暴露过程完全相同。
UnicastRef#invoke(Remote, Method, Object[], long)
方法。@Test
public void client() {
String name = HelloService.class.getName();
// 获取注册表
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
// 查找对应的服务
HelloService service = (HelloService) registry.lookup(name);
}
总结: RMI 服务发现核心步骤两步:一是获取注册中心 registry;二是根据注册中心获取服务的代理类 service。registry 和 service 都是通过 Util.createProxy() 方法生成的代理类,不过这两个代理类的生成时机完全不同,registry 是在客户端生成的代理类,service 是在服务端生成的代理类。
public static Registry getRegistry(String host, int port, RMIClientSocketFactory csf) {
LiveRef liveRef = new LiveRef(new ObjID(ObjID.REGISTRY_ID),
new TCPEndpoint(host, port, csf, null), false);
RemoteRef ref = (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);
return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
}
由于默认存在 RegistryImpl_Stub,所以直接返回 RegistryImpl_Stub 的实例。
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
super.ref.invoke(var2);
ObjectInput var6 = var2.getInputStream();
Remote var23 = (Remote)var6.readObject();
super.ref.done(var2);
return var23;
}
总结: LocateRegistry.getRegistry 获取注册中心时,在客户端直接生成代理对象 RegistryImpl_Stub,RegistryImpl_Stub 实际调用 RemoteRef 的 invoke 方法进行网络通信。
和 RegistryImpl_Stub 不同,普通服务是在服务端生成本地存根 Stub。在服务注册的阶段,我们提到服务暴露时会将服务实例及其生成的 Stub 包装成 Target,并最终注册到 ObjectTable 上。那客户端 registry.lookup(name) 是如何最终查找到对应服务的 Stub 中的呢?
首先客户端调用 registry.lookup(name) 时,会通过网络通信最终调用到 RegistryImpl#lookup 方法,查找到对应的 Remote 实例,之后将这个实例返回给客户端。
但是这个 Socket 输出流是被 MarshalOutputStream 包装过的,在输出对应时会将 Remote 替换为 Stub 对象。也就是说客户端直接可以拿到代理后的对象,反序列后进行网络通信,不需要在客户端生成代理对象。代码如下:
protected final Object replaceObject(Object obj) throws IOException {
if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {
Target target = ObjectTable.getTarget((Remote) obj);
if (target != null) {
return target.getStub();
}
}
return obj;
}
总结: registry.lookup(name) 获取服务端生成的代理对象 stub。这个 stub 代理对象调用 UnicastRef#invoke(Remote, Method, Object[], long)
方法进行网络通信。
注意: 如果该服务没有暴露,则 target=null,也就是直接将服务端注册的实例而不是存根 Stub 返回,所以在客户端必须有该类的实现,否则反序列反时会抛出异常。不过,不暴露服务这种情况好像并没有什么意义。
Exception in thread "main" java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: com.binarylei.rmi.helloword.service.HelloServiceImpl (no security manager: RMI class loader disabled)
at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
at com.binarylei.rmi.helloword.ClientTest.main(ClientTest.java:21)
Caused by: java.lang.ClassNotFoundException: com.binarylei.rmi.helloword.service.HelloServiceImpl (no security manager: RMI class loader disabled)
at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:396)
at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:186)
at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:637)
at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:264)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:219)
... 2 more
每天用心记录一点点。内容也许不重要,但习惯很重要!
标签:write err 使用方法 ceo adc 它的 halo create jtable
原文地址:https://www.cnblogs.com/binarylei/p/12115986.html