如何将SpringRMI服务器与纯JavaRMI客户端(非SpringSwingGUI)集成?

如何将SpringRMI服务器与纯JavaRMI客户端(非SpringSwingGUI)集成?,spring,spring-boot,jakarta-ee,jndi,rmi,Spring,Spring Boot,Jakarta Ee,Jndi,Rmi,我正在将J2EE EJB应用程序迁移到Spring服务。这是一个桌面应用程序,它有一个Swing GUI,可以与使用RMI的J2EE服务器通信。我用spring boot创建了一个简单的spring服务,它使用SpringRemoting,RMIServiceExporter导出服务。该客户机是一个富客户机,具有复杂的体系结构,因此我尝试对其进行最小的更改以调用SpringRMI服务 总之,我有一个普通的RMI客户端和一个spring RMI服务器。我了解到SpringRMI抽象了纯JavaRM

我正在将J2EE EJB应用程序迁移到Spring服务。这是一个桌面应用程序,它有一个Swing GUI,可以与使用RMI的J2EE服务器通信。我用spring boot创建了一个简单的spring服务,它使用SpringRemoting,RMIServiceExporter导出服务。该客户机是一个富客户机,具有复杂的体系结构,因此我尝试对其进行最小的更改以调用SpringRMI服务

总之,我有一个普通的RMI客户端和一个spring RMI服务器。我了解到SpringRMI抽象了纯JavaRMI,所以在我的例子中,它们不进行互操作

我将在下面显示代码,但当前的错误是这样的。请注意,我当前的项目使用“remote://”。因此,在我得到这个错误后,我还尝试了“rmi://”。但是,在这两种情况下都会出现这种错误

javax.naming.CommunicationException: Failed to connect to any server. Servers tried: [rmi://yyy:1099 (No connection provider for URI scheme "rmi" is installed)]
                at org.jboss.naming.remote.client.HaRemoteNamingStore.failOverSequence(HaRemoteNamingStore.java:244)
                at org.jboss.naming.remote.client.HaRemoteNamingStore.namingStore(HaRemoteNamingStore.java:149)
                at org.jboss.naming.remote.client.HaRemoteNamingStore.namingOperation(HaRemoteNamingStore.java:130)
                at org.jboss.naming.remote.client.HaRemoteNamingStore.lookup(HaRemoteNamingStore.java:272)
                at org.jboss.naming.remote.client.RemoteContext.lookupInternal(RemoteContext.java:104)
                at org.jboss.naming.remote.client.RemoteContext.lookup(RemoteContext.java:93)
                at org.jboss.naming.remote.client.RemoteContext.lookup(RemoteContext.java:146)
                at javax.naming.InitialContext.lookup(InitialContext.java:417)
                at com.xxx.ui.common.communication.JbossRemotingInvocationFactory.getRemoteObject(JbossRemotingInvocationFactory.java:63)
                at com.xxx.gui.comm.CommManager.initializeSpringEJBz(CommManager.java:806)
                at com.xxx.gui.comm.CommManager.initializeEJBz(CommManager.java:816)
                at com.xxx.gui.comm.CommManager.initializeAndLogin(CommManager.java:373)
                at com.xxx.gui.comm.CommManager$2.doInBackground(CommManager.java:273)
                at javax.swing.SwingWorker$1.call(SwingWorker.java:295)
                at java.util.concurrent.FutureTask.run(FutureTask.java:266)
                at javax.swing.SwingWorker.run(SwingWorker.java:334)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
                at java.lang.Thread.run(Thread.java:745)
我搜索了spring rmi和纯java rmi的互操作方法,在stackoverflow和web上阅读了一些类似问题的答案,但我找不到任何有用的或符合我的情况的答案,因为即使是最匹配的答案也只说它不能互操作

我想也许我需要通过使用SpringBoot将我的SwingGUI客户机转换为spring,但我不能确定应用程序上下文,因为我不想破坏现有的客户机代码。所以我寻找了一些类似于部分spring上下文的东西,这样我就可以只将CommManager.java客户机代码放入其中,而spring只管理这个文件

然后我想,也许我需要改变我的RMI服务器,迫使spring创建某种普通/纯Java RMI,而不是默认的spring RMI。我之所以这么说,是因为我读了一些关于SpringRMI的文章,解释了它是rmi的抽象,我们可以强迫它创建标准的rmi存根

当我在寻找解决方案时,我遇到了Spring集成,但我无法真正理解它,因为它看起来像另一个抽象,但它也说明了一些关于适配器的事情。因为我见过“适配器”,所以它可能用于这种集成/遗留代码迁移案例。但我不能再往前走了

客户端:

CommManager.java

private boolean initializeEJBz(String userName, String password) throws Exception {
        ...
        ri = RemoteInvocationFactory.getRemoteInvocation(user, pass);
        if (ri != null) {
            return initializeEJBz(ri);
        } else {
            return false;
        }
    }
private boolean initializeEJBz(RemoteInvocation remoteInvocation) throws Exception {
        cs = remoteInvocation.getRemoteObject(CustomerService.class, JNDINames.CUSTOMER_SERVICE_REMOTE);
       ...

        // here is the integration point. try to get RMI service exported.
        myService = remoteInvocation.getRemoteObject(HelloWorldRMI.class, JNDINames.HELLO_WORLD_REMOTE);


        return true;
}
RemoteInvocationFactory.java

package com.xxx.ui.common.communication;

import javax.naming.NamingException;

public final class RemoteInvocationFactory {
    private static final CommunicationProperties cp = new CommunicationProperties();

    public static synchronized RemoteInvocation getRemoteInvocation(
            byte[] userName, byte[] password) throws NamingException {
        String url = System.getProperty("rmi://xxx.com:1099");
        if (url != null) {
            return new JbossRemotingInvocationFactory(userName, password, url);
        }
        return null;
    }
...
package com.xxx.ui.common.communication;

...
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
...
import java.util.Hashtable;
import java.util.concurrent.TimeUnit;

public class JbossRemotingInvocationFactory implements RemoteInvocation {
    private final byte[] userName, password;
    private final String providerURL;
    private volatile InitialContext initialContext;
    private final SecretKey secretKey;
    private static final String SSL_ENABLED = "jboss.naming.client.connect.options.org.xnio.Options.SSL_ENABLED";
    private static final String SSL_STARTTLS = "jboss.naming.client.connect.options.org.xnio.Options.SSL_STARTTLS";
    private static final String TIMEOUT = "jboss.naming.client.connect.timeout";

    private long timeoutValue;
    private final boolean startSsl;


    @SuppressWarnings("unchecked")
    public JbossRemotingInvocationFactory(byte[] userName, byte[] password, String providerURL) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            secretKey = keyGenerator.generateKey();
            this.providerURL = providerURL;
            startSsl = Boolean.valueOf(System.getProperty(SSL_ENABLED));
            String property = System.getProperty("myproject.connect.timeout");
            if (property != null) {
                try {
                    timeoutValue = TimeUnit.MILLISECONDS.convert(Long.parseLong(property), TimeUnit.SECONDS);
                } catch (Exception e) {
                    timeoutValue = TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS);
                }
            }
            Hashtable jndiProperties = new Hashtable();
            this.userName = encrypt(userName);
            addOptions(jndiProperties);
            jndiProperties.put(Context.SECURITY_CREDENTIALS, new String(password, UTF_8));
            initialContext = new InitialContext(jndiProperties);
            this.password = encrypt(password);
        } catch (NamingException | NoSuchAlgorithmException ne) {
            throw new RuntimeException(ne);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getRemoteObject(Class<T> object, String jndiName) throws NamingException {
        if (initialContext != null) {
            T value = (T) initialContext.lookup(jndiName);
            initialContext.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
            initialContext.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
            return value;
        } else {
            throw new IllegalStateException();
        }
    }

    @Override
    public <T> T getRemoteObject(Class<T> object) throws NamingException {
        throw new IllegalAccessError();
    }

    ...


    private void addOptions(Hashtable jndiProperties) {
        jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
        jndiProperties.put("jboss.naming.client.ejb.context", "true");
        jndiProperties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
        jndiProperties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false");
        jndiProperties.put(SSL_STARTTLS, "false");
        jndiProperties.put(TIMEOUT, Long.toString(timeoutValue));
        if (startSsl) {
            jndiProperties.put("jboss.naming.client.remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "true");
            jndiProperties.put(SSL_ENABLED, "true");
        }
        jndiProperties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_DISALLOWED_MECHANISMS", "JBOSS-LOCAL-USER");
        jndiProperties.put(Context.PROVIDER_URL, providerURL);
        jndiProperties.put(Context.SECURITY_PRINCIPAL, new String(decrypt(userName), UTF_8));
    }

    @Override
    public void reconnect() {
        try {
            Hashtable jndiProperties = new Hashtable();
            addOptions(jndiProperties);
            jndiProperties.put(Context.SECURITY_CREDENTIALS, new String(decrypt(password), UTF_8));
            initialContext = new InitialContext(jndiProperties);
        } catch (NamingException ignore) {
        }
    }
}
JbossRemotingInvocationFactory.java

package com.xxx.ui.common.communication;

import javax.naming.NamingException;

public final class RemoteInvocationFactory {
    private static final CommunicationProperties cp = new CommunicationProperties();

    public static synchronized RemoteInvocation getRemoteInvocation(
            byte[] userName, byte[] password) throws NamingException {
        String url = System.getProperty("rmi://xxx.com:1099");
        if (url != null) {
            return new JbossRemotingInvocationFactory(userName, password, url);
        }
        return null;
    }
...
package com.xxx.ui.common.communication;

...
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
...
import java.util.Hashtable;
import java.util.concurrent.TimeUnit;

public class JbossRemotingInvocationFactory implements RemoteInvocation {
    private final byte[] userName, password;
    private final String providerURL;
    private volatile InitialContext initialContext;
    private final SecretKey secretKey;
    private static final String SSL_ENABLED = "jboss.naming.client.connect.options.org.xnio.Options.SSL_ENABLED";
    private static final String SSL_STARTTLS = "jboss.naming.client.connect.options.org.xnio.Options.SSL_STARTTLS";
    private static final String TIMEOUT = "jboss.naming.client.connect.timeout";

    private long timeoutValue;
    private final boolean startSsl;


    @SuppressWarnings("unchecked")
    public JbossRemotingInvocationFactory(byte[] userName, byte[] password, String providerURL) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            secretKey = keyGenerator.generateKey();
            this.providerURL = providerURL;
            startSsl = Boolean.valueOf(System.getProperty(SSL_ENABLED));
            String property = System.getProperty("myproject.connect.timeout");
            if (property != null) {
                try {
                    timeoutValue = TimeUnit.MILLISECONDS.convert(Long.parseLong(property), TimeUnit.SECONDS);
                } catch (Exception e) {
                    timeoutValue = TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS);
                }
            }
            Hashtable jndiProperties = new Hashtable();
            this.userName = encrypt(userName);
            addOptions(jndiProperties);
            jndiProperties.put(Context.SECURITY_CREDENTIALS, new String(password, UTF_8));
            initialContext = new InitialContext(jndiProperties);
            this.password = encrypt(password);
        } catch (NamingException | NoSuchAlgorithmException ne) {
            throw new RuntimeException(ne);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getRemoteObject(Class<T> object, String jndiName) throws NamingException {
        if (initialContext != null) {
            T value = (T) initialContext.lookup(jndiName);
            initialContext.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
            initialContext.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
            return value;
        } else {
            throw new IllegalStateException();
        }
    }

    @Override
    public <T> T getRemoteObject(Class<T> object) throws NamingException {
        throw new IllegalAccessError();
    }

    ...


    private void addOptions(Hashtable jndiProperties) {
        jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
        jndiProperties.put("jboss.naming.client.ejb.context", "true");
        jndiProperties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
        jndiProperties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false");
        jndiProperties.put(SSL_STARTTLS, "false");
        jndiProperties.put(TIMEOUT, Long.toString(timeoutValue));
        if (startSsl) {
            jndiProperties.put("jboss.naming.client.remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "true");
            jndiProperties.put(SSL_ENABLED, "true");
        }
        jndiProperties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_DISALLOWED_MECHANISMS", "JBOSS-LOCAL-USER");
        jndiProperties.put(Context.PROVIDER_URL, providerURL);
        jndiProperties.put(Context.SECURITY_PRINCIPAL, new String(decrypt(userName), UTF_8));
    }

    @Override
    public void reconnect() {
        try {
            Hashtable jndiProperties = new Hashtable();
            addOptions(jndiProperties);
            jndiProperties.put(Context.SECURITY_CREDENTIALS, new String(decrypt(password), UTF_8));
            initialContext = new InitialContext(jndiProperties);
        } catch (NamingException ignore) {
        }
    }
}
服务器端:

HelloWorldRMI.java:

package com.example.springrmiserver.service;

public interface HelloWorldRMI {
    public String sayHelloRmi(String msg);
}
HelloWorldRMIImpl:

package com.example.springrmiserver.service;

import java.util.Date;

public class HelloWorldRMIimpl implements HelloWorldRMI {

    @Override
    public String sayHelloRmi(String msg) {
        System.out.println("================Server Side ========================");
        System.out.println("Inside Rmi IMPL - Incoming msg : " + msg);
        return "Hello " + msg + " :: Response time - > " + new Date();
    }
}
Config.java:

package com.example.springrmiserver;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.remoting.support.RemoteExporter;

import com.example.springrmiserver.service.HelloWorldRMI;
import com.example.springrmiserver.service.HelloWorldRMIimpl;

@Configuration
public class Config {

    @Bean
    RemoteExporter registerRMIExporter() {

        RmiServiceExporter exporter = new RmiServiceExporter();
        exporter.setServiceName("helloworldrmi");
        //exporter.setRegistryPort(1190);
        exporter.setServiceInterface(HelloWorldRMI.class);
        exporter.setService(new HelloWorldRMIimpl());

        return exporter;
    }


}
SpringServerApplication.java:

package com.example.springrmiserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Collections;

@SpringBootApplication
public class SpringRmiServerApplication {

    public static void main(String[] args)
    {
        //SpringApplication.run(SpringRmiServerApplication.class, args);
        SpringApplication app = new SpringApplication(SpringRmiServerApplication.class);
        app.setDefaultProperties(Collections.singletonMap("server.port", "8084"));
        app.run(args);
    }
}
所以,我的问题是如何在swing GUI中与spring rmi服务器交互纯/纯/标准java rmi客户端

编辑#1:

顺便说一句,如果你能提供关于SpringRMI存根创建的内部细节的进一步解释或链接,以及为什么它们不能互操作,我会很高兴的。谢谢

另外,如果您查看我的getRemoteBean方法,它来自遗留代码,那么这个查找字符串是如何工作的?我的意思是rmi注册表文件或其他东西驻留在服务器上的什么位置,或者这是默认格式,或者我可以自定义它吗

编辑#2: 我也在客户端尝试过这种查找:

private void initializeSpringEJBz(RemoteInvocation remoteInvocation) throws Exception {
    HelloWorldRMI helloWorldService = (HelloWorldRMI) Naming.lookup("rmi://xxx:1099/helloworldrmi");
    System.out.println("Output" + helloWorldService.sayHelloRmi("hello "));
    //hw = remoteInvocation.getRemoteObject(HelloWorldRMI.class, "helloworldrmi");
}
编辑#3:

当我搜索时,我发现spring论坛中有人建议,要强制spring创建纯java rmi存根,我们必须在服务器端进行一些更改,因此我尝试了以下方法:

import java.rmi.server.RemoteObject;

public interface HelloWorldRMI extends **Remote** {
   public String sayHelloRmi(String msg) throws **RemoteException**;
   ...
}

...

public class HelloWorldRMIimpl extends **RemoteObject** implements HelloWorldRMI {
...
}
上面的代码是否位于解决问题的正确路径上


除此之外,第一个问题是连接设置,您可以在问题的开头看到。为什么我会犯这个错误?“rmi://”和“remote://”之间的区别是什么?

当我试图找出答案时,我能够找到解决方案。SpringRMI和JavaRMI确实没有互操作性,但目前我没有足够的知识来解释其原因。我还找不到任何关于这种不匹配内部的完整解释

该解决方案通过使用
Java.RMI.*(Remote、RemoteException和server.UnicastRemoteObject)
在Spring后端使用普通的JavaRMI

UnicastRemoteObject用于使用java远程方法协议(JRMP)导出远程对象,并获取与远程对象通信的存根

编辑: 我认为这篇文章与互操作性问题密切相关:

Spring不支持RMI激活。Spring包括一个用于调用远程对象的RmiServiceExporter,它比标准RMI有很多改进,例如不需要服务扩展java.RMI.remote

解决方案:

这是服务器导出的接口:

package com.xxx.ejb.interf;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface HelloWorldRMI extends Remote {
    public String sayHelloRmi(String msg) throws RemoteException;
}
这是导出类的实现:

package com.xxx.proxyserver.service;

import com.xxx.ejb.interf.HelloWorldRMI;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;

public class HelloWorldRMIimpl extends UnicastRemoteObject implements HelloWorldRMI {

    public HelloWorldRMIimpl() throws RemoteException{
        super();
    }

    @Override
    public String sayHelloRmi(String msg) {
        System.out.println("================Server Side ========================");
        System.out.println("Inside Rmi IMPL - Incoming msg : " + msg);
        return "Hello " + msg + " :: Response time - > " + new Date();
    }
}
RMI注册中心是:

package com.xxx.proxyserver;

import com.xxx.proxyserver.service.CustomerServiceImpl;
import com.xxx.proxyserver.service.HelloWorldRMIimpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Collections;

@SpringBootApplication
public class ProxyServerApplication {

    public static void main(String[] args) throws Exception
    {
        Registry registry = LocateRegistry.createRegistry(1200); // this line of code automatic creates a new RMI-Registry. Existing one can be also reused.
        System.out.println("Registry created !");

        registry.rebind("just_an_alias",new HelloWorldRMIimpl());
        registry.rebind("path/to/service_as_registry_key/CustomerService", new CustomerServiceImpl());

        SpringApplication app = new SpringApplication(ProxyServerApplication.class);
        app.setDefaultProperties(Collections.singletonMap("server.port", "8084")); // Service port
        app.run(args);
    }
}
客户:

...
   HelloWorldRMI helloWorldService = (HelloWorldRMI)Naming.lookup("rmi://st-spotfixapp1:1200/just_an_alias");
   System.out.println("Output" + helloWorldService.sayHelloRmi("hello from client ... "));
...

你不能。您必须在客户端使用Spring。感谢您的回复@user207421,但为了充分披露,我尝试在问题中提供详细信息,询问实现这一点的可能性。因此,在我提出的问题中,是否可以使我的客户机部分基于spring,即只有CommManager使用spring。正如您所说,如果只有让我的客户机使用spring才有可能,那么我如何才能部分实现这一点?在这种情况下,可能更适合问另一个问题,但我只是尝试在客户端中使用spring唯一的rmi功能。它要么使用spring rmi,要么不使用。我不明白“部分”是怎么回事。对不起,我说不清楚。我的意思是,在这种情况下,是否可以将spring的应用程序上下文设置为仅由CommManager使用,而不是整个GUI客户端(即main方法)使用?因此,我可以使用Spring只通过注入RMI相关bean来管理CommManager。