«1. Обзор
В этой статье мы покажем, как интегрировать Spring и удаленные Enterprise Java Beans (EJB).
Для этого мы создадим несколько EJB и необходимые удаленные интерфейсы, а затем запустим их внутри JEE-контейнера. После этого мы запустим наше приложение Spring и, используя удаленные интерфейсы, создадим экземпляры наших компонентов, чтобы они могли выполнять удаленные вызовы.
Если есть какие-либо сомнения относительно того, что такое EJB или как они работают, мы уже публиковали вводную статью по этой теме здесь.
2. Настройка EJB
Нам нужно создать наши удаленные интерфейсы и наши реализации EJB. Чтобы сделать их пригодными для использования, нам также понадобится контейнер для хранения бинов и управления ими.
2.1. Удаленные интерфейсы EJB
Давайте начнем с определения двух очень простых bean-компонентов — одного без состояния и одного с состоянием.
Начнем с их интерфейсов:
@Remote
public interface HelloStatefulWorld {
int howManyTimes();
String getHelloWorld();
}
@Remote
public interface HelloStatelessWorld {
String getHelloWorld();
}
2.2. Реализация EJB
@Stateful(name = "HelloStatefulWorld")
public class HelloStatefulWorldBean implements HelloStatefulWorld {
private int howManyTimes = 0;
public int howManyTimes() {
return howManyTimes;
}
public String getHelloWorld() {
howManyTimes++;
return "Hello Stateful World";
}
}
@Stateless(name = "HelloStatelessWorld")
public class HelloStatelessWorldBean implements HelloStatelessWorld {
public String getHelloWorld() {
return "Hello Stateless World!";
}
}
Теперь давайте реализуем наши удаленные интерфейсы EJB:
Если bean-компоненты с состоянием и без состояния звучат незнакомо, эта вводная статья может оказаться полезной.
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>1.6.1</version>
<configuration>
<container>
<containerId>wildfly10x</containerId>
<zipUrlInstaller>
<url>
http://download.jboss.org/wildfly/10.1.0.Final/wildfly-10.1.0.Final.zip
</url>
</zipUrlInstaller>
</container>
<configuration>
<properties>
<cargo.hostname>127.0.0.1</cargo.hostname>
<cargo.jboss.configuration>standalone-full</cargo.jboss.configuration>
<cargo.jboss.management-http.port>9990</cargo.jboss.management-http.port>
<cargo.servlet.users>testUser:admin1234!</cargo.servlet.users>
</properties>
</configuration>
</configuration>
</plugin>
2.3. Контейнер EJB
Мы можем запускать наш код в любом контейнере JEE, но из соображений практичности мы будем использовать Wildfly и плагин Cargo Maven, который сделает всю тяжелую работу за нас:
mvn clean package cargo:run -Pwildfly-standalone
2.4. Запуск EJB
java:global/ejb-remote-for-spring/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:app/ejb-remote-for-spring/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:module/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:jboss/exported/ejb-remote-for-spring/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:global/ejb-remote-for-spring/HelloStatefulWorld
java:app/ejb-remote-for-spring/HelloStatefulWorld
java:module/HelloStatefulWorld
java:global/ejb-remote-for-spring/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:app/ejb-remote-for-spring/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:module/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:jboss/exported/ejb-remote-for-spring/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:global/ejb-remote-for-spring/HelloStatelessWorld
java:app/ejb-remote-for-spring/HelloStatelessWorld
java:module/HelloStatelessWorld
После их настройки мы можем запустить контейнер непосредственно из командной строки Maven:
Теперь у нас есть работающий экземпляр Wildfly, на котором размещены наши bean-компоненты. Мы можем подтвердить это строками журнала:
3. Установка Spring
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-ejb-client-bom</artifactId>
<version>10.1.0.Final</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.baeldung.spring.ejb</groupId>
<artifactId>ejb-remote-for-spring</artifactId>
<version>1.0.1</version>
<type>ejb</type>
</dependency>
Теперь, когда у нас запущен и работает наш JEE-контейнер и развернуты наши EJB, мы можем запустить наше приложение Spring. Мы будем использовать spring-boot-web, чтобы упростить тестирование вручную, но это не обязательно для удаленного вызова.
3.1. Зависимости Maven
Чтобы иметь возможность подключаться к удаленным EJB, нам понадобится клиентская библиотека Wildfly EJB и наш удаленный интерфейс:
@Bean
public Context context() throws NamingException {
Properties jndiProps = new Properties();
jndiProps.put("java.naming.factory.initial",
"org.jboss.naming.remote.client.InitialContextFactory");
jndiProps.put("jboss.naming.client.ejb.context", true);
jndiProps.put("java.naming.provider.url",
"http-remoting://localhost:8080");
return new InitialContext(jndiProps);
}
Последнюю версию wildfly-ejb-client-bom можно найти здесь. .
3.2. Контекст стратегии именования
Имея эти зависимости в пути к классам, мы можем создать экземпляр javax.naming.Context для поиска наших удаленных компонентов. Мы создадим его как Spring Bean, чтобы мы могли автоматически связывать его, когда нам это нужно:
${appName}/${moduleName}/${distinctName}/${beanName}!${viewClassName}
Свойства необходимы для информирования как удаленного URL-адреса, так и контекста стратегии именования.
3.3. Шаблон JNDI
Прежде чем мы сможем связать наши удаленные bean-компоненты внутри контейнера Spring, нам нужно знать, как получить к ним доступ. Для этого мы будем использовать их привязки JNDI. Давайте посмотрим на стандартный шаблон для этих привязок:
Имейте в виду, что, поскольку мы развернули простую банку вместо уха и не указали имя явно, у нас нет appName и отдельного имени. . Дополнительные подробности можно найти в нашей статье EJB Intro на случай, если что-то покажется странным.
Мы будем использовать этот шаблон для привязки наших удаленных bean-компонентов к нашим Spring.
@Bean
public HelloStatelessWorld helloStatelessWorld(Context context)
throws NamingException {
return (HelloStatelessWorld)
context.lookup(this.getFullName(HelloStatelessWorld.class));
}
@Bean
public HelloStatefulWorld helloStatefulWorld(Context context)
throws NamingException {
return (HelloStatefulWorld)
context.lookup(this.getFullName(HelloStatefulWorld.class));
}
private String getFullName(Class classType) {
String moduleName = "ejb-remote-for-spring/";
String beanName = classType.getSimpleName();
String viewClassName = classType.getName();
return moduleName + beanName + "!" + viewClassName;
}
3.4. Построение наших Spring Beans
Чтобы получить доступ к нашим EJB, мы будем использовать вышеупомянутый JNDI. Помните строки журнала, которые мы использовали для проверки того, были ли развернуты наши корпоративные компоненты?
Теперь мы увидим, что эта информация используется:
@RestController
public class HomeEndpoint {
// ...
@GetMapping("/stateless")
public String getStateless() {
return helloStatelessWorld.getHelloWorld();
}
@GetMapping("/stateful")
public String getStateful() {
return helloStatefulWorld.getHelloWorld()
+ " called " + helloStatefulWorld.howManyTimes() + " times";
}
}
Нам нужно быть очень осторожными с правильной полной привязкой JNDI, иначе контекст не сможет достичь удаленного EJB и создать необходимую базовую инфраструктуру.
EJBCLIENT000013: Successful version handshake completed
Имейте в виду, что поиск метода из Context вызовет исключение NamingException, если он не найдет требуемый компонент.
curl http://localhost:8081/stateless
Hello Stateless World!
4. Интеграция
curl http://localhost:8081/stateful
Hello Stateful World called 1 times
curl http://localhost:8081/stateful
Hello Stateful World called 2 times
Когда все готово, мы можем внедрить наши bean-компоненты в контроллер, чтобы проверить правильность подключения:
Давайте запустим наш сервер Spring и проверим некоторые журналы. Мы увидим следующую строку, указывающую на то, что все в порядке:
Теперь давайте проверим наш bean-компонент без сохранения состояния. Мы можем попробовать некоторые команды curl, чтобы убедиться, что они работают должным образом:
И давайте проверим нашу команду с сохранением состояния: