You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/09/11 13:49:45 UTC

[dubbo-spi-extensions] branch master updated: Extend rpc rmi service (#41)

This is an automated email from the ASF dual-hosted git repository.

albumenj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-spi-extensions.git


The following commit(s) were added to refs/heads/master by this push:
     new e6a4522  Extend rpc rmi service  (#41)
e6a4522 is described below

commit e6a452215f9afbfac017c50c6bb288274cff5e1b
Author: clown <32...@users.noreply.github.com>
AuthorDate: Sat Sep 11 21:49:38 2021 +0800

    Extend rpc rmi service  (#41)
    
    * no message
    
    * 添加rmi测试
    
    * fix cli build error
    
    * add new line
    
    * disable testRemoteApplicationName()
    
    * resolve conflicts
    
    * resolve conflicts
    
    * fix ci build
    
    * fix ci build
    
    * add http service
---
 dubbo-rpc-extensions/{ => dubbo-rpc-rmi}/pom.xml   |  39 ++--
 .../rpc/protocol/rmi/RmiRemoteInvocation.java      |  39 ++++
 .../apache/dubbo/rpc/protocol/rmi/RmiProtocol.java | 154 ++++++++++++++
 .../rpc/protocol/rmi/RmiRemoteInvocation.java      |  64 ++++++
 .../dubbo/internal/org.apache.dubbo.rpc.Protocol   |   1 +
 .../apache/dubbo/rpc/protocol/rmi/DemoService.java |  47 +++++
 .../dubbo/rpc/protocol/rmi/DemoServiceImpl.java    |  89 ++++++++
 .../dubbo/rpc/protocol/rmi/RemoteService.java      |  26 +++
 .../dubbo/rpc/protocol/rmi/RemoteServiceImpl.java  |  32 +++
 .../dubbo/rpc/protocol/rmi/RmiProtocolTest.java    | 232 +++++++++++++++++++++
 .../org/apache/dubbo/rpc/protocol/rmi/Type.java    |  21 ++
 dubbo-rpc-extensions/pom.xml                       |   2 +
 12 files changed, 730 insertions(+), 16 deletions(-)

diff --git a/dubbo-rpc-extensions/pom.xml b/dubbo-rpc-extensions/dubbo-rpc-rmi/pom.xml
similarity index 51%
copy from dubbo-rpc-extensions/pom.xml
copy to dubbo-rpc-extensions/dubbo-rpc-rmi/pom.xml
index dbfe401..d338835 100644
--- a/dubbo-rpc-extensions/pom.xml
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/pom.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -15,24 +14,32 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.dubbo.extensions</groupId>
-        <artifactId>extensions-parent</artifactId>
+        <artifactId>dubbo-rpc-extensions</artifactId>
         <version>${revision}</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
-    <packaging>pom</packaging>
-
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>dubbo-rpc-extensions</artifactId>
-
-    <modules>
-        <module>dubbo-rpc-native-thrift</module>
-        <module>dubbo-rpc-http</module>
-        <module>dubbo-rpc-webservice</module>
-    </modules>
+    <artifactId>dubbo-rpc-rmi</artifactId>
+    <packaging>jar</packaging>
+    <description>The rmi rpc module of dubbo project</description>
+    <properties>
+        <skip_maven_deploy>false</skip_maven_deploy>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-cluster</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/com/alibaba/dubbo/rpc/protocol/rmi/RmiRemoteInvocation.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/com/alibaba/dubbo/rpc/protocol/rmi/RmiRemoteInvocation.java
new file mode 100644
index 0000000..22a07b0
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/com/alibaba/dubbo/rpc/protocol/rmi/RmiRemoteInvocation.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.dubbo.rpc.protocol.rmi;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * This class is deprecated, please use {@link org.apache.dubbo.rpc.protocol.rmi.RmiRemoteInvocation}.
+ *
+ * @author chickenlj
+ * @deprecated
+ */
+@Deprecated
+public class RmiRemoteInvocation extends org.apache.dubbo.rpc.protocol.rmi.RmiRemoteInvocation {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * executed on consumer side
+     *
+     * @param methodInvocation
+     */
+    public RmiRemoteInvocation(MethodInvocation methodInvocation) {
+        super(methodInvocation);
+    }
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/org/apache/dubbo/rpc/protocol/rmi/RmiProtocol.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/org/apache/dubbo/rpc/protocol/rmi/RmiProtocol.java
new file mode 100644
index 0000000..f08606b
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/org/apache/dubbo/rpc/protocol/rmi/RmiProtocol.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.protocol.AbstractProxyProtocol;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.apache.dubbo.rpc.support.ProtocolUtils;
+
+import org.springframework.remoting.RemoteAccessException;
+import org.springframework.remoting.rmi.RmiProxyFactoryBean;
+import org.springframework.remoting.rmi.RmiServiceExporter;
+import org.springframework.remoting.support.RemoteInvocation;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.rmi.RemoteException;
+
+import static org.apache.dubbo.common.Version.isRelease263OrHigher;
+import static org.apache.dubbo.common.Version.isRelease270OrHigher;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_VERSION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY;
+import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
+
+/**
+ * RmiProtocol.
+ */
+public class RmiProtocol extends AbstractProxyProtocol{
+
+    public static final int DEFAULT_PORT = 1099;
+
+    public RmiProtocol() {
+        super(RemoteAccessException.class, RemoteException.class);
+    }
+
+    @Override
+    public int getDefaultPort() {
+        return DEFAULT_PORT;
+    }
+
+    @Override
+    protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
+        RmiServiceExporter rmiServiceExporter = createExporter(impl, type, url, false);
+        RmiServiceExporter genericServiceExporter = createExporter(impl, GenericService.class, url, true);
+        return new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    rmiServiceExporter.destroy();
+                    genericServiceExporter.destroy();
+                } catch (Throwable e) {
+                    logger.warn(e.getMessage(), e);
+                }
+            }
+        };
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
+        final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
+        final String generic = url.getParameter(GENERIC_KEY);
+        final boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class);
+        /*
+          RMI needs extra parameter since it uses customized remote invocation object
+
+          The customized RemoteInvocation was firstly introduced in v2.6.3; The package was renamed to 'org.apache.*' since v2.7.0
+          Considering the above two conditions, we need to check before sending customized RemoteInvocation:
+          1. if the provider version is v2.7.0 or higher, send 'org.apache.dubbo.rpc.protocol.rmi.RmiRemoteInvocation'.
+          2. if the provider version is v2.6.3 or higher, send 'com.alibaba.dubbo.rpc.protocol.rmi.RmiRemoteInvocation'.
+          3. if the provider version is lower than v2.6.3, does not use customized RemoteInvocation.
+         */
+        if (isRelease270OrHigher(url.getParameter(RELEASE_KEY))) {
+            rmiProxyFactoryBean.setRemoteInvocationFactory(methodInvocation -> {
+                RemoteInvocation invocation = new RmiRemoteInvocation(methodInvocation);
+                if (isGeneric) {
+                    invocation.addAttribute(GENERIC_KEY, generic);
+                }
+                return invocation;
+            });
+        } else if (isRelease263OrHigher(url.getParameter(DUBBO_VERSION_KEY))) {
+            rmiProxyFactoryBean.setRemoteInvocationFactory(methodInvocation -> {
+                RemoteInvocation invocation = new com.alibaba.dubbo.rpc.protocol.rmi.RmiRemoteInvocation(methodInvocation);
+                if (isGeneric) {
+                    invocation.addAttribute(GENERIC_KEY, generic);
+                }
+                return invocation;
+            });
+        }
+        String serviceUrl = url.toIdentityString();
+        if (isGeneric) {
+            serviceUrl = serviceUrl + "/" + GENERIC_KEY;
+        }
+        rmiProxyFactoryBean.setServiceUrl(serviceUrl);
+        rmiProxyFactoryBean.setServiceInterface(serviceType);
+        rmiProxyFactoryBean.setCacheStub(true);
+        rmiProxyFactoryBean.setLookupStubOnStartup(true);
+        rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
+        rmiProxyFactoryBean.afterPropertiesSet();
+        return (T) rmiProxyFactoryBean.getObject();
+    }
+
+    @Override
+    protected int getErrorCode(Throwable e) {
+        if (e instanceof RemoteAccessException) {
+            e = e.getCause();
+        }
+        if (e != null && e.getCause() != null) {
+            Class<?> cls = e.getCause().getClass();
+            if (SocketTimeoutException.class.equals(cls)) {
+                return RpcException.TIMEOUT_EXCEPTION;
+            } else if (IOException.class.isAssignableFrom(cls)) {
+                return RpcException.NETWORK_EXCEPTION;
+            } else if (ClassNotFoundException.class.isAssignableFrom(cls)) {
+                return RpcException.SERIALIZATION_EXCEPTION;
+            }
+        }
+        return super.getErrorCode(e);
+    }
+
+    private <T> RmiServiceExporter createExporter(T impl, Class<?> type, URL url, boolean isGeneric) {
+        final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
+        rmiServiceExporter.setRegistryPort(url.getPort());
+        if (isGeneric) {
+            rmiServiceExporter.setServiceName(url.getPath() + "/" + GENERIC_KEY);
+        } else {
+            rmiServiceExporter.setServiceName(url.getPath());
+        }
+        rmiServiceExporter.setServiceInterface(type);
+        rmiServiceExporter.setService(impl);
+        try {
+            rmiServiceExporter.afterPropertiesSet();
+        } catch (RemoteException e) {
+            throw new RpcException(e.getMessage(), e);
+        }
+        return rmiServiceExporter;
+    }
+
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/org/apache/dubbo/rpc/protocol/rmi/RmiRemoteInvocation.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/org/apache/dubbo/rpc/protocol/rmi/RmiRemoteInvocation.java
new file mode 100644
index 0000000..729d695
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/java/org/apache/dubbo/rpc/protocol/rmi/RmiRemoteInvocation.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.RpcContext;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.remoting.support.RemoteInvocation;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
+
+public class RmiRemoteInvocation extends RemoteInvocation {
+    private static final long serialVersionUID = 1L;
+    private static final String DUBBO_ATTACHMENTS_ATTR_NAME = "dubbo.attachments";
+
+    /**
+     * executed on consumer side
+     */
+    public RmiRemoteInvocation(MethodInvocation methodInvocation) {
+        super(methodInvocation);
+        addAttribute(DUBBO_ATTACHMENTS_ATTR_NAME, new HashMap<>(RpcContext.getContext().getObjectAttachments()));
+    }
+
+    /**
+     * Need to restore context on provider side (Though context will be overridden by Invocation's attachment
+     * when ContextFilter gets executed, we will restore the attachment when Invocation is constructed, check more
+     * from {@link org.apache.dubbo.rpc.proxy.InvokerInvocationHandler}
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        RpcContext context = RpcContext.getContext();
+        context.setObjectAttachments((Map<String, Object>) getAttribute(DUBBO_ATTACHMENTS_ATTR_NAME));
+        String generic = (String) getAttribute(GENERIC_KEY);
+        if (StringUtils.isNotEmpty(generic)) {
+            context.setAttachment(GENERIC_KEY, generic);
+        }
+        try {
+            return super.invoke(targetObject);
+        } finally {
+            context.setObjectAttachments(null);
+        }
+    }
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
new file mode 100644
index 0000000..a1e30e3
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
@@ -0,0 +1 @@
+rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
\ No newline at end of file
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/DemoService.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/DemoService.java
new file mode 100644
index 0000000..ca0a089
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/DemoService.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+/**
+ * <code>TestService</code>
+ */
+
+public interface DemoService {
+    void sayHello(String name);
+
+    String sayHi(String name);
+
+    String echo(String text);
+
+    long timestamp();
+
+    void throwTimeout();
+
+    String getThreadName();
+
+    int getSize(String[] strs);
+
+    int getSize(Object[] os);
+
+    Object invoke(String service, String method) throws Exception;
+
+    int stringLength(String str);
+
+    Type enumlength(Type... types);
+
+    String getRemoteApplicationName();
+}
\ No newline at end of file
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/DemoServiceImpl.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/DemoServiceImpl.java
new file mode 100644
index 0000000..15fe72e
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/DemoServiceImpl.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+import org.apache.dubbo.rpc.RpcContext;
+
+/**
+ * DemoServiceImpl
+ */
+
+public class DemoServiceImpl implements DemoService {
+    public DemoServiceImpl() {
+        super();
+    }
+
+    public void sayHello(String name) {
+        System.out.println("hello " + name);
+    }
+
+    public String sayHi(String name) {
+        return "Hi, " + name;
+    }
+
+    public String echo(String text) {
+        return text;
+    }
+
+    public long timestamp() {
+        return System.currentTimeMillis();
+    }
+
+    public String getThreadName() {
+        return Thread.currentThread().getName();
+    }
+
+    public int getSize(String[] strs) {
+        if (strs == null)
+            return -1;
+        return strs.length;
+    }
+
+    public int getSize(Object[] os) {
+        if (os == null)
+            return -1;
+        return os.length;
+    }
+
+    public Object invoke(String service, String method) throws Exception {
+        System.out.println("RpcContext.getContext().getRemoteHost()=" + RpcContext.getContext().getRemoteHost());
+        return service + ":" + method;
+    }
+
+    public Type enumlength(Type... types) {
+        if (types.length == 0)
+            return Type.Lower;
+        return types[0];
+    }
+
+    public int stringLength(String str) {
+        return str.length();
+    }
+
+    public void throwTimeout() {
+        try {
+            Thread.sleep(6000);
+        } catch (InterruptedException e) {
+        }
+    }
+
+
+    @Override
+    public String getRemoteApplicationName() {
+        return RpcContext.getContext().getRemoteApplicationName();
+    }
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RemoteService.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RemoteService.java
new file mode 100644
index 0000000..1b1da9a
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RemoteService.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface RemoteService extends Remote {
+    String sayHello(String name) throws RemoteException;
+
+    String getThreadName() throws RemoteException;
+}
\ No newline at end of file
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RemoteServiceImpl.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RemoteServiceImpl.java
new file mode 100644
index 0000000..3de0ab7
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RemoteServiceImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+import org.apache.dubbo.rpc.RpcContext;
+
+import java.rmi.RemoteException;
+
+public class RemoteServiceImpl implements RemoteService {
+    public String getThreadName() throws RemoteException {
+        System.out.println("RpcContext.getContext().getRemoteHost()=" + RpcContext.getContext().getRemoteHost());
+        return Thread.currentThread().getName();
+    }
+
+    public String sayHello(String name) throws RemoteException {
+        return "hello " + name + "@" + RemoteServiceImpl.class.getName();
+    }
+}
\ No newline at end of file
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RmiProtocolTest.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RmiProtocolTest.java
new file mode 100644
index 0000000..dd4d11e
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/RmiProtocolTest.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.service.EchoService;
+import org.apache.dubbo.rpc.service.GenericService;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Disabled(value = "Disable for Github Actions Environment Issue")
+public class RmiProtocolTest {
+    private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+    private ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+
+    /*
+    @Test
+    public void test_getRemoteClass() throws Exception {
+        Class<NonStdRmiInterface> clazz = RmiProtocol.getRemoteClass(NonStdRmiInterface.class);
+        assertEquals(clazz, RmiProtocol.getRemoteClass(NonStdRmiInterface.class));
+    }
+    */
+    @Test
+    public void testRmiProtocolTimeout() throws Exception {
+        int availablePort = NetUtils.getAvailablePort();
+        System.setProperty("sun.rmi.transport.tcp.responseTimeout", "1000");
+        DemoService service = new DemoServiceImpl();
+        Exporter<?> rpcExporter = protocol.export(proxy.getInvoker(service, DemoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService")));
+        service = proxy.getProxy(protocol.refer(DemoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService")));
+        try {
+            try {
+                service.throwTimeout();
+            } catch (RpcException e) {
+                assertTrue(e.isTimeout());
+                assertTrue(e.getMessage().contains("Read timed out"));
+            }
+        } finally {
+            rpcExporter.unexport();
+        }
+    }
+
+    @Test
+    public void testRmiProtocol() throws Exception {
+        {
+            int availablePort = NetUtils.getAvailablePort();
+            DemoService service = new DemoServiceImpl();
+            Exporter<?> rpcExporter = protocol.export(proxy.getInvoker(service, DemoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService")));
+
+            service = proxy.getProxy(protocol.refer(DemoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService")));
+            assertEquals(service.getSize(null), -1);
+            assertEquals(service.getSize(new String[]{"", "", ""}), 3);
+            Object result = service.invoke("rmi://127.0.0.1:" + availablePort + "/TestService", "invoke");
+            assertEquals("rmi://127.0.0.1:" + availablePort + "/TestService:invoke", result);
+
+            rpcExporter.unexport();
+        }
+
+        {
+            int port = NetUtils.getAvailablePort();
+            RemoteService remoteService = new RemoteServiceImpl();
+            Exporter<?> rpcExporter = protocol.export(proxy.getInvoker(remoteService, RemoteService.class, URL.valueOf("rmi://127.0.0.1:" + port + "/remoteService")));
+
+            remoteService = proxy.getProxy(protocol.refer(RemoteService.class, URL.valueOf("rmi://127.0.0.1:" + port + "/remoteService")));
+            remoteService.getThreadName();
+            for (int i = 0; i < 100; i++) {
+                String say = remoteService.sayHello("abcd");
+                assertEquals("hello abcd@" + RemoteServiceImpl.class.getName(), say);
+            }
+            rpcExporter.unexport();
+        }
+    }
+
+    // FIXME RMI protocol doesn't support casting to EchoService yet.
+    @Disabled
+    @Test
+    public void testRmiProtocol_echoService() throws Exception {
+        int availablePort = NetUtils.getAvailablePort();
+        DemoService service = new DemoServiceImpl();
+        Exporter<?> rpcExporter = protocol.export(proxy.getInvoker(service, DemoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService")));
+
+        // cast to EchoService
+        EchoService echo = proxy.getProxy(protocol.refer(EchoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService")));
+        assertEquals(echo.$echo("test"), "test");
+        assertEquals(echo.$echo("abcdefg"), "abcdefg");
+        assertEquals(echo.$echo(1234), 1234);
+
+        rpcExporter.unexport();
+
+        availablePort = NetUtils.getAvailablePort();
+        RemoteService remoteService = new RemoteServiceImpl();
+        rpcExporter = protocol.export(proxy.getInvoker(remoteService, RemoteService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/remoteService")));
+
+        // cast to EchoService
+        echo = proxy.getProxy(protocol.refer(EchoService.class, URL.valueOf("rmi://127.0.0.1:" + availablePort + "/remoteService")));
+        assertEquals(echo.$echo("test"), "test");
+        assertEquals(echo.$echo("abcdefg"), "abcdefg");
+        assertEquals(echo.$echo(1234), 1234);
+
+        rpcExporter.unexport();
+    }
+
+    @Test
+    public void testGenericInvoke() {
+        int availablePort = NetUtils.getAvailablePort();
+        DemoService service = new DemoServiceImpl();
+        URL url = URL.valueOf("rmi://127.0.0.1:" + availablePort + "/" + DemoService.class.getName() + "?release=2.7.0");
+        Exporter<DemoService> exporter = protocol.export(proxy.getInvoker(service, DemoService.class, url));
+        Invoker<GenericService> invoker = protocol.refer(GenericService.class, url);
+        GenericService client = proxy.getProxy(invoker, true);
+        String result = (String) client.$invoke("sayHi", new String[]{"java.lang.String"}, new Object[]{"haha"});
+        Assertions.assertEquals("Hi, haha", result);
+        invoker.destroy();
+        exporter.unexport();
+    }
+
+    @Disabled
+    @Test
+    public void testRemoteApplicationName() throws Exception {
+        int availablePort = NetUtils.getAvailablePort();
+        DemoService service = new DemoServiceImpl();
+        URL url = URL.valueOf("rmi://127.0.0.1:" + availablePort + "/TestService?release=2.7.0").addParameter("application", "consumer");
+        Exporter<?> rpcExporter = protocol.export(proxy.getInvoker(service, DemoService.class, url));
+
+        service = proxy.getProxy(protocol.refer(DemoService.class, url));
+        assertEquals(service.getRemoteApplicationName(), "consumer");
+
+        rpcExporter.unexport();
+    }
+
+    public interface NonStdRmiInterface {
+        void bark();
+    }
+
+	/*@Test
+    public void testRpcInvokerGroup() throws Exception
+	{
+		DemoService service = new DemoServiceImpl();
+		RpcUtils.export("demo://127.0.0.1:9030/org.apache.dubbo.rpc.TestService",DemoService.class,service);
+		RpcUtils.export("dubbo://127.0.0.1:9031/TestService",DemoService.class,service);
+		RpcUtils.export("rmi://127.0.0.1:9032/org.apache.dubbo.rpc.TestService",DemoService.class,service);
+		RpcUtils.export("rmi://127.0.0.1:9033/org.apache.dubbo.rpc.TestService",DemoService.class,service);
+
+		service = RpcUtils.createProxy(DemoService.class,
+				new String[]{
+					"demo://127.0.0.1:9030/org.apache.dubbo.rpc.TestService?weight=20",
+					"dubbo://127.0.0.1:9031/TestService?weight=20",
+					"rmi://127.0.0.1:9032/org.apache.dubbo.rpc.TestService",
+				});
+		assertEquals(service.getSize(null), -1);
+		assertEquals(service.getSize(new String[]{"","",""}), 3);
+
+		// cast to EchoService
+		EchoService echo = RpcUtils.createProxy(EchoService.class,
+				new String[]{
+			"demo://127.0.0.1:9030/org.apache.dubbo.rpc.TestService?weight=20",
+			"dubbo://127.0.0.1:9031/TestService?weight=20",
+			"rmi://127.0.0.1:9032/org.apache.dubbo.rpc.TestService",
+		});
+		assertEquals(echo.$echo("test"), "test");
+		assertEquals(echo.$echo("abcdefg"), "abcdefg");
+		assertEquals(echo.$echo(1234), 1234);
+	}*/
+
+	/*public void testForkInvoke() throws Exception
+	{
+		DemoService service = new DemoServiceImpl();
+		protocol.export(proxy.createInvoker("dubbo://127.0.0.1:9040/TestService", DemoService.class, service);
+		protocol.export(proxy.createInvoker("dubbo://127.0.0.1:9041/TestService", DemoService.class, service);
+		protocol.export(proxy.createInvoker("rmi://127.0.0.1:9042/org.apache.dubbo.rpc.TestService", DemoService.class, service);
+		protocol.export(proxy.createInvoker("rmi://127.0.0.1:9043/org.apache.dubbo.rpc.TestService", DemoService.class, service);
+
+		RpcInvokerGroup group = Proxies.createInvoker(DemoService.class, new String[]{
+			"dubbo://127.0.0.1:9040/TestService",
+			"dubbo://127.0.0.1:9041/TestService",
+			"rmi://127.0.0.1:9042/org.apache.dubbo.rpc.TestService",
+			"rmi://127.0.0.1:9043/org.apache.dubbo.rpc.TestService",
+		});
+		group.getMethodSettings("echo").setFork(true);
+		group.getMethodSettings("echo").setForkInvokeCallback(new ForkInvokeCallback(){
+			public Object merge(RpcInvocation invocation, RpcResult[] results) throws Throwable
+			{
+				System.out.println("merge result begin:");
+				for( RpcResult result : results )
+				{
+					if( result.hasException() )
+						System.out.println("exception:"+result.getException().getMessage());
+					else
+						System.out.println("result:"+result.getResult());
+				}
+				System.out.println("merge result end:");
+				return "aaaa";
+			}
+		});
+
+		service = proxy.createProxy(protocol.refer(DemoService.class, group);
+		service.echo("test");
+
+		// cast to EchoService
+		EchoService echo = proxy.createProxy(protocol.refer(EchoService.class, group);
+		assertEquals(echo.$echo("test"), "test");
+		assertEquals(echo.$echo("abcdefg"), "abcdefg");
+		assertEquals(echo.$echo(1234), 1234);
+	}*/
+
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/Type.java b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/Type.java
new file mode 100644
index 0000000..222d9d1
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-rmi/src/test/java/org/apache/dubbo/rpc/protocol/rmi/Type.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.rmi;
+
+public enum Type {
+    High, Normal, Lower
+}
\ No newline at end of file
diff --git a/dubbo-rpc-extensions/pom.xml b/dubbo-rpc-extensions/pom.xml
index dbfe401..11f796a 100644
--- a/dubbo-rpc-extensions/pom.xml
+++ b/dubbo-rpc-extensions/pom.xml
@@ -34,5 +34,7 @@
         <module>dubbo-rpc-native-thrift</module>
         <module>dubbo-rpc-http</module>
         <module>dubbo-rpc-webservice</module>
+        <module>dubbo-rpc-rmi</module>
     </modules>
+
 </project>