Как интегрировать Spring RMI-сервер с чистым Java RMI-клиентом, который не является Spring Swing GUI? - PullRequest
0 голосов
/ 01 февраля 2019

Я перевожу приложение J2EE EJB на сервисы Spring.Это настольное приложение, которое имеет графический интерфейс Swing и для связи с сервером J2EE использует RMI.Я создал простой весенний сервис с весенней загрузкой, который экспортирует сервис с использованием удаленного взаимодействия, RMIServiceExporter.Клиент - богатый клиент и имеет сложную архитектуру, поэтому я пытаюсь внести в него минимальные изменения, чтобы вызвать службу Spring RMI.

Итак, в целом, у меня есть простой клиент RMI и сервер RMI Spring.Я узнал, что Spring RMI абстрагирует чистый Java RMI, поэтому в моем случае они не взаимодействуют.

Я покажу код ниже, но текущая ошибка это.Обратите внимание, что мой текущий проект использует «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 и plain / pure java rmi, и я прочитал несколько ответов на похожие вопросы в stackoverflow и в Интернете, но я не смогне нашел ничего полезного или подходящего для моего случая, потому что даже самый подходящий ответ говорит только о том, что он не взаимодействует.

Я подумал, что, возможно, мне нужно превратить свой клиент Swi GUI в весенний с помощью весенней загрузки, но я не был уверен в контексте приложения, так как не хочу нарушать существующий клиентский код.Итак, я искал, может быть, есть что-то вроде частичного контекста Spring, так что, возможно, я могу поместить в него только мой клиентский код CommManager.java, а Spring только управляет этим файлом.измените мой RMI-сервер, чтобы заставить Spring создать какой-то простой / чистый Java RMI вместо RMI по умолчанию.Я говорю что-то, потому что я прочитал кое-что о Spring rmi, которое объясняет, что это абстракция над rmi, и мы можем заставить его создать стандартную заглушку RMI.

Пока я ищу решение, я столкнулся с Spring Integration, но яне мог понять это на самом деле, так как это выглядит как другая абстракция, но это также говорит о адаптерах.Так как я видел «адаптер», возможно, он используется для такого рода случаев интеграции / миграции устаревшего кода.Но я не мог идти дальше.

Клиентская сторона:

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;
        }
    }

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;
    }
...

JbossRemotingInvocationFactory.java

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) {
        }
    }
}

CommManager.java

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;
}
public static final String CUSTOMER_SERVICE_REMOTE = getRemoteBean("CustomerServiceBean", CustomerService.class.getName());

public static final string HELLO_WORLD_REMOTE = getRemoteBean("HelloWorldRMI", HelloWorldRMI.class.getName());

...

private static final String APPLICATION_NAME = "XXX";
private static final String MODULE_NAME = "YYYY";

...

protected static String getRemoteBean(String beanName, String interfaceName) {
        return String.format("%s/%s/%s!%s", APPLICATION_NAME, MODULE_NAME, beanName, interfaceName);
    }

Сторона сервера:

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);
    }
}

Итак, моя проблема заключается вкак взаимодействовать чистый / обычный / стандартный Java-клиент RMI, который находится в Swing GUI с Spring RMI-сервер?

Edit # 1:

Кстати, если вы можетепредоставьте дальнейшие объяснения или ссылки о внутренних деталях создания заглушки Spring RMI и почему они не взаимодействуют, я буду счастлив.Спасибо, действительно.

А также, если вы посмотрите на мой метод getRemoteBean из устаревшего кода, как работает эта строка поиска?Я имею в виду, где файл реестра rmi или что-то находится на сервере, или это формат по умолчанию, или я могу настроить его?

Edit # 2: Я также пробовал этот вид поиска вclient:

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");
}

Edit # 3:

Пока я искал, я обнаружил, что кто-то на весеннем форуме предложил принудить 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: //"?

1 Ответ

0 голосов
/ 05 марта 2019

Пока я пытался выяснить, я смог найти решение.Это правда, что Spring RMI и Java RMI не взаимодействуют, но в настоящее время у меня недостаточно знаний, чтобы объяснить его причину.Я не смог найти полное объяснение внутренних особенностей этого несоответствия.

Решение заключается в использовании простого Java RMI в бэкэнде Spring с использованием java.rmi.*(Remote, RemoteException and server.UnicastRemoteObject).

java.rmi.server.UnicastRemoteObject используется для экспорта удаленного объекта с помощью протокола Java Remote Method Protocol (JRMP) и получения заглушки, которая связывается с удаленным объектом.

Редактировать: Я думаю, что этот пост тесно связан с этой проблемой взаимодействия: Активация Java Spring RMI

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 ... "));
...
...