1. 概要

何年にもわたって、Javaエコシステムは途方もなく進化し成長してきました。 この間、Enterprise Java BeansとSpringは、競合するだけでなく、互いに共生的に学んだ2つのテクノロジーです。

このチュートリアルでは、 それらの歴史と違いを見ていきます。 もちろん、Springの世界でEJBとそれに相当するもののコード例をいくつか見ていきます。 。

2. テクノロジーの簡単な歴史

まず、これら2つのテクノロジーの歴史と、それらが長年にわたって着実に発展してきた方法を簡単に見てみましょう。

2.1. エンタープライズJavaBeans

EJB仕様は、Java EE(またはJ2EE、現在はJakarta EEとして知られている)仕様のサブセットです。 その最初のバージョンは1999年にリリースされ、Javaでのサーバー側のエンタープライズアプリケーション開発を容易にするために設計された最初のテクノロジーの1つでした。

並行性、セキュリティ、永続性、トランザクション処理などのJava開発者の負担を負いました。 仕様は、これらおよびその他の一般的な企業の懸念事項を、それらをシームレスに処理するアプリケーションサーバーのコンテナーの実装に引き渡しました。 ただし、EJBをそのまま使用することは、必要な構成の量のために少し面倒でした。 さらに、それはパフォーマンスのボトルネックであることが証明されていました。

しかし現在、アノテーションの発明とSpringとの激しい競争により、最新の3.2バージョンのEJBは、デビューバージョンよりもはるかに簡単に使用できます。 今日のエンタープライズJavaBeanは、Springの依存性注入とPOJOの使用から大きく借りています。

2.2. 春

EJB(および一般的にはJava EE)がJavaコミュニティを満足させるのに苦労している間、SpringFrameworkは新鮮な空気のように到着しました。 その最初のマイルストーンリリースは2004年に発表され、EJBモデルとそのヘビーウェイトコンテナに代わるものを提供しました。

Springのおかげで、Javaエンタープライズアプリケーションを軽量のIOCコンテナで実行できるようになりました。 さらに、他の無数の便利な機能の中で、依存性逆転、AOP、およびHibernateのサポートも提供しました。 Javaコミュニティからの多大なサポートにより、Springは飛躍的に成長し、完全なJava/JEEアプリケーションフレームワークと呼ぶことができます。

最新のアバターでは、Spring5.0はリアクティブプログラミングモデルもサポートしています。 もう1つの派生物であるSpring Bootは、組み込みサーバーと自動構成を備えた完全なゲームチェンジャーです。

3. 機能比較の前置き

コードサンプルとの機能比較にジャンプする前に、いくつかの基本を確立しましょう。

3.1. 2つの基本的な違い

まず、基本的で明らかな違いは、 EJBが仕様であるのに対し、Springはフレームワーク全体であるということです。

この仕様は、GlassFish、IBM WebSphere、JBoss/WildFlyなどの多くのアプリケーションサーバーによって実装されています。 これは、アプリケーションのバックエンド開発にEJBモデルを使用するという選択だけでは不十分であることを意味します。 また、使用するアプリケーションサーバーを選択する必要があります。

理論的には、Enterprise Java Beanはアプリサーバー間で移植可能ですが、相互運用性をオプションとして維持する場合は、ベンダー固有の拡張機能を使用しないという前提条件が常にあります。

第2に、テクノロジーとしてのSpringは、幅広い製品ポートフォリオの点で、EJBよりもJavaEEに近い。 EJBはバックエンド操作のみを指定しますが、Springは、Java EEと同様に、UI開発、RESTful API、およびリアクティブプログラミングもサポートしています。

3.2. 有用な情報

次のセクションでは、2つのテクノロジーの比較といくつかの実際的な例を示します。 EJB機能ははるかに大きなSpringエコシステムのサブセットであるため、それらのタイプを調べて、対応するSpringの同等物を確認します。

例を最もよく理解するには、 Java EEセッションBeanメッセージ駆動型Bean Spring Bean 、およびSpringBeanアノテーションを読むことを検討してください。最初。

OpenJB を埋め込みコンテナとして使用して、EJBサンプルを実行します。 Springの例のほとんどを実行するには、そのIOCコンテナで十分です。 Spring JMSの場合、組み込みのApacheMQブローカーが必要です。

すべてのサンプルをテストするには、JUnitを使用します。

4. シングルトンEJB==Springコンポーネント

Beanのインスタンスを1つだけ作成するためにコンテナが必要になる場合があります。 たとえば、Webアプリケーションへの訪問者の数をカウントするためにbeanが必要だとします。 このBeanは、アプリケーションの起動時に1回だけ作成する必要があります

シングルトンセッションEJBSpringコンポーネントを使用してこれを実現する方法を見てみましょう。

4.1. シングルトンEJBの例

最初に、EJBがリモートで処理される機能を備えていることを指定するためのインターフェースが必要です。

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

次のステップは、アノテーションjavax.ejb.Singleton とビオラを使用して実装クラスを定義することです! シングルトンの準備ができました:

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

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

ただし、シングルトン(またはその他のEJBコードサンプル)をテストする前に、 ejbContainer を初期化し、contextを取得する必要があります。

@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 ルックアップを使用して、コンテナーからcounterEJBを取得しています
  • count2 は、 count がシングルトンを離れたポイントからピックアップし、合計で20になります。
  • secondCounter は、firstCounterに設定した名前を保持します

最後の2つのポイントは、シングルトンの重要性を示しています。 ルックアップするたびに同じbeanインスタンスが使用されるため、合計カウントは20であり、一方に設定された値はもう一方にも同じままです。

4.2. シングルトンSpringBeanの例

Springコンポーネントを使用して同じ機能を取得できます。

ここにインターフェースを実装する必要はありません。 代わりに、@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);
}

次に、Componentの動作を見てみましょう。

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

ご覧のとおり、EJBに関する唯一の違いは、JNDIルックアップではなく、Springコンテナーのコンテキストからbeanを取得する方法です。

5. ステートフルEJB==Spring Component with protocol Scope

たとえば、ショッピングカートを作成しているときに、メソッド呼び出し間を行ったり来たりするときにその状態を記憶する必要がある

この場合、呼び出しごとに個別のBeanを生成し、状態を保存するためのコンテナーが必要です。 問題のテクノロジーでこれをどのように達成できるか見てみましょう。

5.1. ステートフルEJBの例

シングルトンEJBサンプルと同様に、javax.ejb.Remoteインターフェースとその実装が必要です。 今回のみ、javax.ejb.Statefulで注釈が付けられます。

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

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

name を設定し、bathingCartにアイテムを追加する簡単なテストを書いてみましょう。 サイズを確認し、名前を確認します。

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

ここで、beanが実際にインスタンス間で状態を維持していることを示すために、このテストに別の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());

ここでは、 name を設定しなかったため、その値はnullでした。 シングルトンテストから、あるインスタンスで設定された名前が別のインスタンスで保持されたことを思い出してください。 これは、インスタンスの状態が異なるBeanプールから個別のShoppingCartEJBインスタンスを取得したことを示しています。

5.2. ステートフルなSpringBeanの例

Springで同じ効果を得るには、プロトタイプスコープを備えたComponentが必要です。

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

それだけです。注釈だけが異なります。残りのコードは同じままです

ステートフルBeanをテストするには、EJBについて説明したのと同じテストを使用できます。 唯一の違いは、コンテナからBeanを取得する方法です。

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

6. ステートレスEJB!=Springのすべて

たとえば、検索APIの場合、 Beanのインスタンス状態や、シングルトンであるかどうかを気にしないことがあります。 必要なのは検索結果だけです。これは、関心のあるすべてのBeanインスタンスからのものである可能性があります。

6.1. ステートレスEJBの例

このようなシナリオの場合、EJBにはステートレスバリアントがあります。 コンテナはBeanのインスタンスプールを維持し、それらのいずれかが呼び出し元のメソッドに返されます。

これを定義する方法は、他のEJBタイプと同じですが、リモートインターフェイスがあり、javax.ejb.Statelessアノテーションを使用して実装します。

@Stateless
public class FinderEJB implements FinderEJBRemote {

    private Map<String, String> alphabet;

    public FinderEJB() {
        alphabet = new HashMap<String, String>();
        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"));        
}

上記の例では、 alphabetFinder は、アノテーションjavax.ejb.EJBを使用してテストクラスのフィールドとして挿入されます。

@EJB
private FinderEJBRemote alphabetFinder;

ステートレスEJBの背後にある中心的な考え方は、類似したBeanのインスタンスプールを持つことでパフォーマンスを向上させることです。

ただし、 Springはこの哲学に同意せず、ステートレスとしてシングルトンのみを提供します。

7. メッセージ駆動型Bean==Spring JMS

これまでに説明したEJBはすべてセッションBeanでした。 もう1つの種類は、メッセージ駆動型です。 名前が示すように、これらは通常2つのシステム間の非同期通信に使用されます

7.1. MDBの例

メッセージ駆動型のEnterpriseJavaBeanを作成するには、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にいくつかの構成も提供していることに注意してください。

      • destinationType as Queue
      • myQueuedestinationキュー名として、Beanがリッスンしている

この例では、受信者も確認応答を生成し、その意味でそれ自体が送信者ですackQueueという別のキューにメッセージを送信します。

次に、これをテストで実際に見てみましょう。

@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. SpringJMSの例

さて、今度はSpringを使用して同じことを行う時が来ました!

まず、この目的のために少し構成を追加する必要があります。 以前のApplicationConfigクラスに@EnableJmsで注釈を付け、JmsListenerContainerFactoryJmsTemplateをセットアップするためにいくつかのBeanを追加する必要があります。

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

次に、 Producer –単純なSpring Component –が必要です。これは、 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");
    }
}

次に、 Receiver Component に、 @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());
}

このテストでは、marcomyQueueに送信し、ackQueueからの確認応答としてpoloを受信しました。 EJB。

ここで注意すべきことの1つは、SpringJMSが同期と非同期の両方でメッセージを送受信できることです

8. 結論

このチュートリアルでは、SpringとEnterpriseJavaBeansの1対1の比較を見ました。 私たちは彼らの歴史と基本的な違いを理解しました。

次に、SpringBeansとEJBの比較を示す簡単な例を扱いました。 言うまでもなく、はテクノロジーの能力のほんの一部に過ぎず、さらに探求すべきことがたくさんあります

さらに、これらは競合する技術かもしれませんが、それはそれらが共存できないという意味ではありません。 簡単にEJBをSpringフレームワークに統合できます。

いつものように、ソースコードはGitHubから入手できます。