WIADOMOŚCI

Komunikacja zdalna EJB – JBoss 6 – Wildfly

Published on:28 / December / 2015

Problem

Możliwość zdalnego połączenia EJB w różnych wersjach JBoss/Wildfly AS nie jest obsługiwana.

Do wykonania zdalnego połączenia EJB potrzebna jest biblioteka klienta. Biblioteka klienta zawiera klasy, które normalnie stanowią część serwera aplikacji. Gdy klientem jest samodzielna aplikacja (Java SE), jest to właściwe rozwiązanie, ale gdy klientem jest inny serwer aplikacji Java EE, nie jest to możliwe, bo wiele klas jest w konflikcie.

Jedyny wyjątek ma miejsce wtedy, gdy serwery aplikacji mają dokładnie tę samą wersję, bo wtedy biblioteka klienta nie jest potrzebna.

Rozwiązanie

Spróbuj naśladować samodzielnego klienta EJB dla serwera docelowego.

Wykonaj zdalne połączenie EJB w izolacji od classloadera serwera aplikacji i dodaj tylko potrzebne biblioteki klienta z innego serwera.

Droga do rozwiązania

W poszukiwaniu rozwiązania natknęliśmy się na blog, którego autorem jest Carlo de Wolf (RedHat) (http://wolf-71.blogspot.rs/2010/02/et-phone-home.html), gdzie opisany został podobny problem. Najważniejszym elementem była klasa AluniteClassLoader, która nie ma nadrzędnego classloadera i deleguje do danych classloaderów po kolei.

Bardzo istotny był też prawidłowy sposób uwzględnienia naszego własnego interfejsu, aby był dostępny dla classloadera, co pozwoliło uniknąć budzących postrach wyjątków ClassCastExceptions.

Weryfikacja koncepcji

Projekt międzyserwerowej komunikacji krzyżowej:https://github.com/infobip/jboss-wildfly-remoting

  • 1 moduł JAR – serwer krzyżowy cross-server-common, ze zdalnym interfejsem IConnector i klasą JndiHandler
  • 1 moduł EJB – serwer krzyżowy cross-server-ejb-jboss6, z 1 wdrożeniem IConnector, celowanym na JBoss6
  • 1 moduł EJB – serwer krzyżowy cross-server-ejb-wildfly9, z 1 wdrożeniem IConnector, celowanym na Wildfly9
  • 1 moduł EAR – wersja na bazie profilu:
    • profil wildfly9 buduje EAR z serwerem krzyżowym cross-server-common i serwerem krzyżowym cross-server-ejb-wildfly9
    • profil jboss6 buduje EAR z serwerem krzyżowym cross-server-common i serwerem krzyżowym cross-server-ejb-jboss6, a wyklucza z EAR plik konfiguracyjny komponentu jboss-deployment-structure.xml

interfejs IConnector ma dwie metody: hello() i answer(). Każde hello() wywołuje answer() na innej instancji serwera.

Klasa JndiHandler ma 3 metody: jedną do standardowego wyszukania komponentu JBoss6 i dwie do wyszukania komponentu Wildfly9 na podstawie API klienta EJB oraz do wyszukania komponentu w stylu zdalnej komunikacji http w projekcie jboss-remote-naming:

  • lookupJboss6
Properties properties = new Properties(); 
properties.setProperty("java.naming.provider.url", host + ":" + port); 
properties.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); 
properties.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); 
InitialContext ctx = new InitialContext(properties); 
T service = (T) ctx.lookup(jndiName);
  • lookupWildfly9 (EJB client API)
Properties properties = new Properties(); 
properties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); 
InitialContext ctx = new InitialContext(properties); 
Context ejbRootNamingContext = (Context) ctx.lookup("ejb:");
T service = (T) ctx.lookup(ejb:" + jndiName);

W przypadku wybrania wyszukania komponentu klienta EJB klient musi mieć jboss-ejb-client.properties w zmiennej classpath.

  • lookupWildfly9HttpRemoting (jboss-remote-naming)
Properties properties = new Properties(); 
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory"); 
properties.put(Context.PROVIDER_URL, "http-remoting://" + host + ":" + port); 
properties.put("jboss.naming.client.ejb.context", true); 
InitialContext ctx = new InitialContext(properties); 
T service = (T) ctx.lookup(jndiName);

Każdy serwer musi mieć biblioteki klienta z drugiego serwera na lokalnej maszynie, ale nierozmieszczone na serwerze aplikacji.

Uruchommy dwa serwery aplikacji.

Powiedzmy, że uruchomimy JBoss6 AS na standardowym porcie 8080, a Wildfly z port-offset 100.

Komunikacja JBoss6-Wildfly9 w metodzie Hello:

ClassLoader previous = Thread.currentThread().getContextClassLoader(); 
URLClassLoader urlCl = new URLClassLoader(new URL[]{ new URL(new File(jbossHome).toURI().toURL(), "client/jboss-client.jar") }, null); 
ClassLoader cl = new AluniteClassLoader(urlCl, previous);
Thread.currentThread().setContextClassLoader(cl);
String jndiname = "/cross-server-test/cross-server-ejb-wildfly9/ConnectorWildfly9Impl!com.infobip.crossserver.IConnector"; 
IConnector connector = (IConnector)JndiHandler.lookupWildfly9HttpRemoting(jndiname, new String[] {"localhost"}, 8180);
String answer = connector.answer(name);
logger.info("Wildfly is answering: " + answer);
// in the end, we return to previous classloader 
Thread.currentThread().setContextClassLoader(previous);

Komunikacja Wildfly9-Jboss6 w metodzie Hello:

ClassLoader previous = Thread.currentThread().getContextClassLoader(); 
URLClassLoader urlCl = new URLClassLoader(new URL[]{ new URL(new File(jbossHome).toURI().toURL(), "jbossall-client.jar") }, null); 
ClassLoader cl = new AluniteClassLoader(urlCl, previous);
Thread.currentThread().setContextClassLoader(cl);
String jndiname = "ConnectorJBoss6"; 
IConnector connector = (IConnector)JndiHandler.lookupJBoss6(jndiname, new String[] {"localhost"}, JndiHandler.LOCAL_JNDI_PORT);
String answer = connector.answer(name);
logger.info("JBoss6 is answering: " + answer);
// in the end, we return to previous classloader 
Thread.currentThread().setContextClassLoader(previous); 

Czary...

Gdy uruchomimy test Junit w cross-server-wildfly9:

IConnector connector = (IConnector) lookup ("localhost", "8180"); connector.hello("Infobip");

Podobnie, test Junit w cross-server-jboss6:

IConnector connector = (IConnector) lookup("localhost", "8080"); connector.hello("Infobip");

(Autor: Jelena Lazic, inżynier oprogramowania)