SpringRMI解析3-RmiServiceExporter逻辑细节
在发布RMI服务的流程中,有几个步骤可能是我们比较关心的。
获取registry
由于底层的封装,获取Registry实例是非常简单的,只需要使用一个函数LocateRegistry.createRegistry(...)创建Registry实例就可以了。但是,Spring中并没有这么做,而是考虑得更多,比如RMI注册主机与发布的服务并不在一台机器上,那么需要使用LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory)去远程获取Registry实例。
protected Registry getRegistry(String registryHost, int registryPort,
RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)
throws RemoteException {
if (registryHost != null) {
// Host explicitly specified: only lookup possible.
if (logger.isInfoEnabled()) {
logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
}
//如果registryHost不为空则尝试获取对应主机的Registry
Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
//远程连接测试
testRegistry(reg);
return reg;
}
else {
//获取本机的Registry
return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
}
}
如果并不是从另外的服务器上获取Registry连接,那么就需要在本地创建RMI的Registry实例了。当然,这里有一个关键的参数alwaysCreateRegistry,如果此参数配置为true,那么在获取Registry实例时会首先测试是否已经建立了对指定端口的连接,如果已经建立则复用已经创建的实例,否则重新创建。
当然,之前也提到过,创建Registry实例时可以使用自定义的连接工厂,而之前的判断也保证了clientSocketFactory与serverSocketFactory要么同时出现,要么同时不出现,所以这里只对clientSocketFactory是否为空进行了判断。
protected Registry getRegistry(
int registryPort, RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)
throws RemoteException {
if (clientSocketFactory != null) {
if (this.alwaysCreateRegistry) {
logger.info("Creating new RMI registry");
//使用clientSocketFactory创建Registry
return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
}
if (logger.isInfoEnabled()) {
logger.info("Looking for RMI registry at port '" + registryPort + "', using custom socket factory");
}
synchronized (LocateRegistry.class) {
try {
// Retrieve existing registry.
//复用测试
Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory);
testRegistry(reg);
return reg;
}
catch (RemoteException ex) {
logger.debug("RMI registry access threw exception", ex);
logger.info("Could not detect RMI registry - creating new one");
// Assume no registry found -> create new one.
return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
}
}
}
else {
return getRegistry(registryPort);
}
}
//如果创建Registry实例时不需要使用自定义的套接字工厂,那么就可以直接使用LocateRegistry.createRegistry(...)方法来创建了,当然复用的检测还是必要的。
protected Registry getRegistry(int registryPort) throws RemoteException {
if (this.alwaysCreateRegistry) {
logger.info("Creating new RMI registry");
return LocateRegistry.createRegistry(registryPort);
}
if (logger.isInfoEnabled()) {
logger.info("Looking for RMI registry at port '" + registryPort + "'");
}
synchronized (LocateRegistry.class) {
try {
// Retrieve existing registry.
//查看对应当前registryPort的Registry是否已经创建,如果创建直接使用
Registry reg = LocateRegistry.getRegistry(registryPort);
//测试是否可用,如果不可用则抛出异常
testRegistry(reg);
return reg;
}
catch (RemoteException ex) {
logger.debug("RMI registry access threw exception", ex);
logger.info("Could not detect RMI registry - creating new one");
// Assume no registry found -> create new one.
//根据端口创建Registry
return LocateRegistry.createRegistry(registryPort);
}
}
}
初始化将要导出的实体对象
当请求某个RMI服务的时候,RMI会根据注册的服务名称,将请求引导至远程对象处理类中,这个处理类便是使用getObjectToExport()进行创建。
protected Remote getObjectToExport() {
// determine remote object
//如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性
if (getService() instanceof Remote &&
(getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) {
// conventional RMI service
return (Remote) getService();
}
else {
// RMI invoker
if (logger.isDebugEnabled()) {
logger.debug("RMI service [" + getService() + "] is an RMI invoker");
}
//对service进行封装
return new RmiInvocationWrapper(getProxyForService(), this);
}
}
请求处理类的初始化主要处理规则为:如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性,那么直接使用service作为处理类;否则,使用RMIInvocationWrapper对service的代理类和当前类也就是RMIServiceExporter进行封装。
经过这样的封装,客户端与服务端便可以达成一致协议,当客户端检测到是RMIInvocationWrapper类型stub的时候便会直接调用其invoke方法,使得调用端与服务端很好地连接在了一起。而RMIInvocationWrapper封装了用于处理请求的代理类,在invoke中便会使用代理类进行进一步处理。当请求RMI服务时会由注册表Registry实例将请求转向之前注册的处理类去处理,也就是之前封装的RMIInvocationWrapper,然后由RMIInvocationWrapper中的invoke方法进行处理,那么为什么不是在invoke方法中直接使用service,而是通过代理再次将service封装呢?这其中的一个关键点是,在创建代理时添加了一个增强拦截器RemoteInvocationTraceInterceptor,目的是为了对方法调用进行打印跟踪,但是如果直接在invoke方法中硬编码这些日志,会使代码看起来很不优雅,而且耦合度很高,使用代理的方式就会解决这样的问题,而且会有很高的可扩展性。
protected Object getProxyForService(){
//验证service
checkService();
//验证serviceInterface
checkServiceInterface();
//使用JDK的方式创建代理
ProxyFactory proxyFactory = new ProxyFactory();
//添加代理接口
proxyFactory.addInterface(getServiceInterface());
if(registerTraceInterceptor == null ? interceptors == null : registerTraceInterceptor.booleanValue())
//加入代理的横切面RemoteInvocationTraceInterceptor并记录Exporter名称
proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
if(interceptors != null)
{
AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
for(int i = 0; i < interceptors.length; i++)
proxyFactory.addAdvisor(adapterRegistry.wrap(interceptors[i]));
}
//设置要代理的目标类
proxyFactory.setTarget(getService());
proxyFactory.setOpaque(true);
//创建代理
return proxyFactory.getProxy(getBeanClassLoader());
}
RMI服务激活调用
由于在之前bean初始化的时候做了服务名称绑定this.registry.bind(this.serviceName,thhis.exportedObjedt),其中的exportedObject其实是被RMIInvocationWrapper进行封装过的,也就是说当其他服务调用serviceName的RMI服务时,Java会为我们封装其内部操作,而直接会将代码转向RMIInvocationWrapper测invoke方法中。
public Object invoke(RemoteInvocation invocation) throws RemoteException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException{
return rmiExporter.invoke(invocation, wrappedObject);
}
而此时this.rmiExporter为之前初始化的RMIServiceExporter,invocation为包含着需要激活的方法参数,而wrappedObject则是之前封装的代理类。
protected Object invoke(RemoteInvocation invocation, Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return super.invoke(invocation, targetObject);
}
protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
if(logger.isTraceEnabled())
logger.trace((new StringBuilder()).append("Executing ").append(invocation).toString());
try {
return getRemoteInvocationExecutor().invoke(invocation, targetObject);
}catch(NoSuchMethodException ex){
if(logger.isDebugEnabled())
logger.warn((new StringBuilder())
.append("Could not find target method for ")
.append(invocation).toString(), ex);
throw ex;
}
catch(IllegalAccessException ex){
if(logger.isDebugEnabled())
logger.warn((new StringBuilder())
.append("Could not access target method for ")
.append(invocation).toString(), ex);
throw ex;
}catch(InvocationTargetException ex){
if(logger.isDebugEnabled())
logger.debug((new StringBuilder()).append("Target method failed for ")
.append(invocation).toString(), ex.getTargetException());
throw ex;
}
}
public Object invoke(RemoteInvocation invocation, Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{
Assert.notNull(invocation, "RemoteInvocation must not be null");
Assert.notNull(targetObject, "Target object must not be null");
//通过反射方式激活方法
return invocation.invoke(targetObject);
}
public Object invoke(Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//根据方法名称获取代理中的方法
Method method = targetObject.getClass().getMethod(methodName, parameterTypes);
//执行代理中方法
return method.invoke(targetObject, arguments);
}
targetObject为之前封装的代理类。
SpringRMI解析3-RmiServiceExporter逻辑细节的更多相关文章
- SpringRMI解析2-RmiServiceExporter逻辑脉络
配置文件是Spring的核心,在配置文件中我们可以看到,定义了两个bean,其中一个是对接口实现类的发布,而另一个则是对RMI服务的发布,使用org.springframework.remoting. ...
- SpringMVC解析5-DispatcherServlet逻辑细节
MultipartContent类型的request处理 对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换req ...
- SpringRMI解析1-使用示例
Java远程方法调用,即JavaRMI(JavaRemote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口.它使客户机上的运行的程序可以调用远程 ...
- SpringRMI解析4-客户端实现
根据客户端配置文件,锁定入口类为RMIProxyFactoryBean,同样根据类的层次结构查找入口函数. <bean id="rmiServiceProxy" class= ...
- yolo源码解析(1):代码逻辑
一. 整体代码逻辑 yolo中源码分为三个部分,\example,\include,以及\src文件夹下都有源代码存在. 结构如下所示 ├── examples │ ├── darknet.c(主程序 ...
- APP注册&登陆 逻辑细节
前言:有多少用户愿意注册登陆,决定了一款产品的最大活跃度. 用户登陆注册系统分为两大类: 自建用户系统:邮箱/手机号/用户名/二维码/人脸识别/指纹 第三方授权用户系统:微信/微博/支付包/豆瓣/Fa ...
- 自己动手实现一个简单的JSON解析器
1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
随机推荐
- Mathematics:Semi-prime H-numbers(POJ 3292)
Semi-prime H-numbers 题目大意,令4n+1的数叫H数,H数素数x的定义是只能被x=1*h(h是H数),其他都叫合数,特别的,当一个数只能被两个H素数乘积得到时,叫H-semi数 ...
- DP:Cow Exhibition(POJ 2184)(二维问题转01背包)
牛的展览会 题目大意:Bessie要选一些牛参加展览,这些牛有两个属性,funness和smartness,现在要你求出怎么选,可以使所有牛的smartness和funness的最大,并且这两 ...
- ssm控制输出sql(二)
望时高科联通log4j # DEBUG,INFO,WARN,ERROR,FATAL LOG_LEVEL=DEBUG ---------这里对应sql的级别 log4j.rootLogger=${LOG ...
- 【linux】进程不要开太多,否则系统会卡死!
今天在跑一个任务,大概像下面这样 python task.py -s input/ input文件夹下有两百多个文件,比如1.txt, 2.txt等等,task.py会顺序读取并做操作. 我想,这不是 ...
- .NET微信公众号开发-3.0查询自定义菜单
一.前言 前面我们已经创建好了我们的自定义菜单.那么我们现在要如何查询我们自定义的菜单.原理都是一样的,而且都是相当简单,只是接口地址文档换掉了. 2.0开始编码 同样我们首先创建好我的查询页面,在这 ...
- 3ds max删除了对象后,还是将原来所有对象输出的原因
原因是场景中除了 几何体 外还有 图形,如下图 将这些图形删除,几何体就都正常输出了.
- elk安装(这个是初级的可以把这个套件安上)
http://udn.yyuap.com/doc/logstash-best-practice-cn/index.html ELK其实并不是一款软件,而是一整套解决方案,是三个开源软件Elastics ...
- 趣味C++
用三段 140 字符以内的代码生成一张 1024×1024 的图片 Kyle McCormick 在 StackExchange 上发起了一个叫做TweetableMathematical A ...
- Oracle数据库锁表及解锁进程
下午代码迁移,更新数据库记录时for update语句无法执行,数据库被锁,KILL掉几个进程搞定. Oracle数据库操作中,我们有时会用到锁表查询以及解锁和kill进程等操作,那么这些操作是怎么实 ...
- JavaWeb学习之Path总结、ServletContext、ServletResponse、ServletRequest(3)
1.Path总结 1.java项目 1 File file = new File(""); file.getAbsolutePath(); * 使用java命令,输出路径是,当前j ...