Рубрики
Без рубрики

Весенняя фасоль против EJB – Сравнение функций

Узнайте об истории и различиях между Spring Bean и Enterprise Java Bean.

Автор оригинала: Sampada Wagde.

1. Обзор

За эти годы экосистема Java значительно эволюционировала и выросла. В течение этого времени корпоративные Java-бобы и Spring-это две технологии, которые не только конкурировали, но и учились друг у друга симбиотически.

В этом уроке мы рассмотрим их историю и различия. Конечно, мы увидим некоторые примеры кода EJB и их эквивалентов в весеннем мире .

2. Краткая история технологий

Для начала давайте быстро взглянем на историю этих двух технологий и на то, как они неуклонно развивались на протяжении многих лет.

2.1. Корпоративные Java-компоненты

Спецификация EJB является подмножеством спецификации Java EE (или J2EE, теперь известной как Jakarta EE ) . Его первая версия вышла в 1999 году, и это была одна из первых технологий, разработанных для облегчения разработки корпоративных приложений на стороне сервера на Java.

Он взял на себя бремя разработчиков Java, связанных с параллелизмом, безопасностью, сохраняемостью, обработкой транзакций и многим другим. Спецификация передала эти и другие общие проблемы предприятия контейнерам серверов приложений, которые легко справлялись с ними. Однако использование EJBS в том виде, в каком они были, было немного громоздким из-за объема требуемой конфигурации. Более того, это оказалось узким местом в производительности.

Но теперь, с изобретением аннотаций и жесткой конкуренцией со стороны Spring, EJBS в их последней версии 3.2 намного проще в использовании, чем их дебютная версия. Современные корпоративные Java-компоненты в значительной степени заимствуются из внедрения зависимостей Spring и использования POJOS.

2.2. Весна

В то время как EJBs (и Java EE в целом) изо всех сил пытались удовлетворить сообщество Java, SpringFramework появился как глоток свежего воздуха. Его первый веховый релиз вышел в 2004 году и предложил альтернативу модели EJB и ее тяжеловесным контейнерам.

Благодаря Spring корпоративные приложения Java теперь можно запускать на более легких контейнерах IOC|/. Кроме того, он также предлагал инверсию зависимостей, поддержку AOP и гибернации среди множества других полезных функций. Благодаря огромной поддержке со стороны сообщества Java Spring в настоящее время растет экспоненциально и может быть названа полноценной платформой приложений Java/JEE.

В своем последнем аватаре Spring 5.0 даже поддерживает модель реактивного программирования. Еще одно ответвление, Spring Boot , полностью меняет правила игры со встроенными серверами и автоматическими конфигурациями.

3. Прелюдия к сравнению функций

Прежде чем перейти к сравнению функций с образцами кода, давайте установим несколько основных принципов.

3.1. Основное различие Между Этими Двумя

Во-первых, фундаментальное и очевидное различие заключается в том, что EJB-это спецификация, в то время как Spring-это целая структура .

Спецификация реализована многими серверами приложений, такими как GlassFish, IBM WebSphere и JBoss/WildFly. Это означает, что наш выбор использовать модель EJB для разработки бэкенда нашего приложения недостаточен. Нам также нужно выбрать, какой сервер приложений использовать.

Теоретически, корпоративные Java-компоненты переносимы через серверы приложений, хотя всегда есть предварительное условие, что мы не должны использовать какие-либо расширения, зависящие от поставщика, если мы хотим сохранить совместимость в качестве опции.

Во-вторых, Spring as technology ближе к Java EE, чем EJB, с точки зрения ее широкого портфеля предложений . В то время как EJBS определяют только внутренние операции, Spring, как и Java EE, также поддерживает разработку пользовательского интерфейса, RESTful API и реактивное программирование.

3.2. Полезная информация

В следующих разделах мы увидим сравнение этих двух технологий с некоторыми практическими примерами. Поскольку функции EJB являются подмножеством гораздо более крупной экосистемы Spring, мы рассмотрим их типы и увидим соответствующие эквиваленты Spring.

Чтобы лучше понять примеры, подумайте о том , чтобы сначала прочитать о компонентах сеанса Java EE , компонентах , управляемых сообщениями, компонентах Spring и аннотациях Spring Bean.

Мы будем использовать Openejb в качестве встроенного контейнера для запуска образцов EJB. Для запуска большинства примеров Spring достаточно контейнера IOC; для Spring JMS нам понадобится встроенный брокер Apache MQ.

Чтобы протестировать все наши образцы, мы будем использовать JUnit.

4. Одноэлементный компонент EJB

Иногда нам нужен контейнер для создания только одного экземпляра компонента. Например, предположим, что нам нужен компонент для подсчета количества посетителей нашего веб-приложения. Этот компонент должен быть создан только один раз во время запуска приложения .

Давайте посмотрим, как добиться этого с помощью одноэлементного сеанса EJB и компонента Spring .

4.1. Пример одноэлементного EJB

Сначала нам понадобится интерфейс, чтобы указать, что наш EJB может обрабатываться удаленно:

@Remote
public interface CounterEJBRemote {    
    int count();
    String getName();
    void setName(String name);
}

Следующим шагом является определение класса реализации с аннотацией javax.ejb.Singleton и viola! Наш синглтон готов:

@Singleton
public class CounterEJB implements CounterEJBRemote {
    private int count = 1;
    private String name;

    public int count() {
        return count++;
    }
    
    // getter and setter for name
}

Но прежде чем мы сможем протестировать синглтон (или любой другой пример кода EJB), нам нужно инициализировать контейнер ejb и получить контекст :

@BeforeClass
public void initializeContext() throws NamingException {
    ejbContainer = EJBContainer.createEJBContainer();
    context = ejbContainer.getContext();
    context.bind("inject", this);
}

Теперь давайте посмотрим на тест:

@Test
public void givenSingletonBean_whenCounterInvoked_thenCountIsIncremented() throws NamingException {

    int count = 0;
    CounterEJBRemote firstCounter = (CounterEJBRemote) context.lookup("java:global/ejb-beans/CounterEJB");
    firstCounter.setName("first");
        
    for (int i = 0; i < 10; i++) {
        count = firstCounter.count();
    }
        
    assertEquals(10, count);
    assertEquals("first", firstCounter.getName());

    CounterEJBRemote secondCounter = (CounterEJBRemote) context.lookup("java:global/ejb-beans/CounterEJB");

    int count2 = 0;
    for (int i = 0; i < 10; i++) {
        count2 = secondCounter.count();
    }

    assertEquals(20, count2);
    assertEquals("first", secondCounter.getName());
}

Несколько вещей, которые следует отметить в приведенном выше примере:

  • Мы используем поиск JNDI , чтобы получить счетчик EJB из контейнера
  • count 2 берет начало с точки count , в которой оставил синглтон, и складывает до 20
  • второй счетчик сохраняет имя, которое мы установили для первого счетчика

Последние два пункта демонстрируют значимость синглтона. Поскольку один и тот же экземпляр компонента используется каждый раз, когда он просматривается, общее количество равно 20, а значение, установленное для одного, остается неизменным для другого.

4.2. Пример одноэлементного весеннего боба

Такую же функциональность можно получить с помощью пружинных компонентов.

Нам не нужно реализовывать здесь какой-либо интерфейс. Вместо этого мы добавим аннотацию @Component :

@Component
public class CounterBean {
    // same content as in the EJB
}

На самом деле компоненты по умолчанию являются синглетами в Spring .

Нам также необходимо настроить Spring для сканирования компонентов:

@Configuration
@ComponentScan(basePackages = "com.baeldung.ejbspringcomparison.spring")
public class ApplicationConfig {}

Аналогично тому, как мы инициализировали контекст EJB, теперь мы установим контекст Spring:

@BeforeClass
public static void init() {
    context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
}

Теперь давайте посмотрим на наш Компонент в действии:

@Test
public void whenCounterInvoked_thenCountIsIncremented() throws NamingException {    
    CounterBean firstCounter = context.getBean(CounterBean.class);
    firstCounter.setName("first");
    int count = 0;
    for (int i = 0; i < 10; i++) {
        count = firstCounter.count();
    }

    assertEquals(10, count);
    assertEquals("first", firstCounter.getName());

    CounterBean secondCounter = context.getBean(CounterBean.class);
    int count2 = 0;
    for (int i = 0; i < 10; i++) {
        count2 = secondCounter.count();
    }

    assertEquals(20, count2);
    assertEquals("first", secondCounter.getName());
}

Как мы видим, единственное различие в отношении EJBs заключается в том, как мы получаем боб из контекста контейнера Spring, а не из поиска JNDI.

5. Компонент EJB с отслеживанием состояния с областью действия прототипа

Иногда, скажем, когда мы создаем корзину покупок, нам нужно, чтобы наш боб помнил свое состояние, переходя туда и обратно между вызовами методов .

В этом случае нам нужно, чтобы наш контейнер генерировал отдельный компонент для каждого вызова и сохранял состояние. Давайте посмотрим, как этого можно достичь с помощью наших технологий, о которых идет речь.

5.1. Пример EJB с отслеживанием состояния

Как и в нашем примере одноэлементного EJB, нам нужен интерфейс javax.ejb.Remote и его реализация. Только на этот раз он аннотирован javax.ejb.Stateful :

@Stateful
public class ShoppingCartEJB implements ShoppingCartEJBRemote {
    private String name;
    private List shoppingCart;

    public void addItem(String item) {
        shoppingCart.add(item);
    }
    // constructor, getters and setters
}

Давайте напишем простой тест, чтобы установить имя и добавить товары в корзину для купания . Мы проверим его размер и проверим название:

@Test
public void givenStatefulBean_whenBathingCartWithThreeItemsAdded_thenItemsSizeIsThree()
  throws NamingException {
    ShoppingCartEJBRemote bathingCart = (ShoppingCartEJBRemote) context.lookup(
      "java:global/ejb-beans/ShoppingCartEJB");

    bathingCart.setName("bathingCart");
    bathingCart.addItem("soap");
    bathingCart.addItem("shampoo");
    bathingCart.addItem("oil");

    assertEquals(3, bathingCart.getItems().size());
    assertEquals("bathingCart", bathingCart.getName());
}

Теперь, чтобы продемонстрировать, что компонент действительно поддерживает состояние в разных экземплярах, давайте добавим в этот тест еще один shoppingCartEJB:

ShoppingCartEJBRemote fruitCart = 
  (ShoppingCartEJBRemote) context.lookup("java:global/ejb-beans/ShoppingCartEJB");

fruitCart.addItem("apples");
fruitCart.addItem("oranges");

assertEquals(2, fruitCart.getItems().size());
assertNull(fruitCart.getName());

Здесь мы не установили имя , и, следовательно, его значение было равно нулю. Вспомните из одноэлементного теста, что имя, заданное в одном экземпляре, было сохранено в другом. Это демонстрирует, что мы получили отдельные экземпляры ShoppingCartEJB из пула компонентов с разными состояниями экземпляров.

5.2. Пример Spring Bean с отслеживанием состояния

Чтобы получить тот же эффект с Spring, нам нужен Компонент с областью действия прототипа :

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShoppingCartBean {
   // same contents as in the EJB
}

Вот и все, только аннотации отличаются – остальная часть кода остается прежней .

Чтобы проверить наш компонент с отслеживанием состояния, мы можем использовать тот же тест, что и описанный для EJBs. Единственная разница опять же в том, как мы получаем боб из контейнера:

ShoppingCartBean bathingCart = context.getBean(ShoppingCartBean.class);

6. Безгосударственный EJB весной

Иногда, например, в API поиска, мы не заботимся ни о состоянии экземпляра компонента, ни о том, является ли он одноэлементным . Нам просто нужны результаты нашего поиска, которые могут исходить из любого экземпляра bean для всего, что нас волнует.

6.1. Пример EJB без гражданства

Для таких сценариев EJB имеет вариант без состояния. Контейнер поддерживает пул экземпляров компонентов, и любой из них возвращается вызывающему методу .

Способ, которым мы определяем его, такой же, как и другие типы EJB, с удаленным интерфейсом и реализацией с javax.ejb.Stateless аннотацией:

@Stateless
public class FinderEJB implements FinderEJBRemote {

    private Map alphabet;

    public FinderEJB() {
        alphabet = new HashMap();
        alphabet.put("A", "Apple");
        // add more values in map here
    }

    public String search(String keyword) {
        return alphabet.get(keyword);
    }
}

Давайте добавим еще один простой тест, чтобы увидеть это в действии:

@Test
public void givenStatelessBean_whenSearchForA_thenApple() throws NamingException {
    assertEquals("Apple", alphabetFinder.search("A"));        
}

В приведенном выше примере alphabet Finder вводится в качестве поля в тестовом классе с помощью аннотации javax.ejb.EJB :

@EJB
private FinderEJBRemote alphabetFinder;

Основная идея EJBS без состояния заключается в повышении производительности за счет наличия пула экземпляров аналогичных компонентов.

Однако Spring не подписывается на эту философию и предлагает только синглтоны как безгосударственные .

7. Управляемые сообщениями Бобы JMS

Все EJB, обсуждавшиеся до сих пор, были сессионными бобами. Другой вид-это тот, который управляется сообщениями. Как следует из названия, они обычно используются для асинхронной связи между двумя системами .

7.1. Пример МБР

Чтобы создать управляемый сообщениями корпоративный JavaBean, нам нужно реализовать интерфейс javax.jms.MessageListener , определяющий его метод onMessage , и аннотировать класс как javax.ejb.MessageDriven :

@MessageDriven(activationConfig = { 
  @ActivationConfigProperty(propertyName = "destination", propertyValue = "myQueue"), 
  @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") 
})
public class RecieverMDB implements MessageListener {

    @Resource
    private ConnectionFactory connectionFactory;

    @Resource(name = "ackQueue")
    private Queue ackQueue;

    public void onMessage(Message message) {
        try {
            TextMessage textMessage = (TextMessage) message;
            String producerPing = textMessage.getText();

            if (producerPing.equals("marco")) {
                acknowledge("polo");
            }
        } catch (JMSException e) {
            throw new IllegalStateException(e);
        }
    }
}

Обратите внимание, что мы также предоставляем несколько конфигураций для нашего MDB:

      • тип назначения как Очередь
      • моя очередь в качестве назначения имени очереди, которую прослушивает наш боб

В этом примере наш получатель также выдает подтверждение и в этом смысле сам по себе является отправителем . Он отправляет сообщение в другую очередь с именем ack Queue .

Теперь давайте посмотрим на это в действии с помощью теста:

@Test
public void givenMDB_whenMessageSent_thenAcknowledgementReceived()
  throws InterruptedException, JMSException, NamingException {
    Connection connection = connectionFactory.createConnection();
    connection.start();
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    MessageProducer producer = session.createProducer(myQueue);
    producer.send(session.createTextMessage("marco"));
    MessageConsumer response = session.createConsumer(ackQueue);

    assertEquals("polo", ((TextMessage) response.receive(1000)).getText());
}

Здесь мы отправили сообщение в myQueue , которое было получено нашим @MessageDriven аннотированным POJO . Затем этот POJO отправил подтверждение, и наш тест получил ответ в виде MessageConsumer .

7.2. Пример Spring JMS

Что ж, теперь пришло время сделать то же самое с помощью Весны!

Во-первых, нам нужно будет добавить немного конфигурации для этой цели. Нам нужно аннотировать ваш Application Config класс с помощью @EnableJms и добавить несколько компонентов для настройки JmsListenerContainerFactory и JmsTemplate :

@EnableJms
public class ApplicationConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        return factory;
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        return new ActiveMQConnectionFactory("tcp://localhost:61616");
    }

    @Bean
    public JmsTemplate jmsTemplate() {
        JmsTemplate template = new JmsTemplate(connectionFactory());
        template.setConnectionFactory(connectionFactory());
        return template;
    }
}

Далее нам нужен Производитель – простой пружинный Компонент – который будет отправлять сообщения в myQueue и получать подтверждение от ackQueue :

@Component
public class Producer {
    @Autowired
    private JmsTemplate jmsTemplate;

    public void sendMessageToDefaultDestination(final String message) {
        jmsTemplate.convertAndSend("myQueue", message);
    }

    public String receiveAck() {
        return (String) jmsTemplate.receiveAndConvert("ackQueue");
    }
}

Затем у нас есть Приемник Компонент с методом, аннотированным как @JmsListener для асинхронного приема сообщений от myQueue :

@Component
public class Receiver {
    @Autowired
    private JmsTemplate jmsTemplate;

    @JmsListener(destination = "myQueue")
    public void receiveMessage(String msg) {
        sendAck();
    }

    private void sendAck() {
        jmsTemplate.convertAndSend("ackQueue", "polo");
    }
}

Он также действует в качестве отправителя для подтверждения получения сообщения в ackQueue .

Как и в нашей практике, давайте проверим это с помощью теста:

@Test
public void givenJMSBean_whenMessageSent_thenAcknowledgementReceived() throws NamingException {
    Producer producer = context.getBean(Producer.class);
    producer.sendMessageToDefaultDestination("marco");

    assertEquals("polo", producer.receiveAck());
}

В этом тесте мы отправили marco в мою очередь и получили polio в качестве подтверждения от ackQueue , то же самое , что мы сделали с EJB.

Здесь следует отметить, что Spring JMS может отправлять/получать сообщения как синхронно, так и асинхронно .

8. Заключение

В этом уроке мы увидели индивидуальное сравнение компонентов Spring и Enterprise Java . Мы понимали их историю и основные различия.

Затем мы рассмотрели простые примеры, чтобы продемонстрировать сравнение яровых бобов и EJBs. Излишне говорить, что это просто царапает поверхность того, на что способны технологии, и есть еще много чего, что предстоит изучить дальше .

Кроме того, это могут быть конкурирующие технологии, но это не значит, что они не могут сосуществовать. Мы можем легко интегрировать EJBs в Spring framework .

Как всегда, исходный код доступен на GitHub .