1. Overview
1.概述
Over the years, the Java ecosystem has evolved and grown tremendously. During this time, Enterprise Java Beans and Spring are two technologies that not only have competed but learned from each other symbiotically.
多年来,Java生态系统已经发生了巨大的变化和发展。在这期间,Enterprise Java Beans和Spring是两种技术,它们不仅相互竞争,而且相互学习。
In this tutorial, we’ll take a look at their history and differences. Of course, we’ll see some code examples of EJB and their equivalents in the Spring world.
在本教程中,我们将看一下它们的历史和区别。当然,我们会看到一些EJB及其在Spring世界中的等价物的代码实例。
2. A Brief History of the Technologies
2.技术简史
To start with, let’s take a quick peek at the history of these two technologies and how they’ve steadily developed over the years.
首先,让我们快速浏览一下这两项技术的历史,以及它们多年来如何稳步发展。
2.1. Enterprise Java Beans
2.1.企业级Java Bean
The EJB specification is a subset of the Java EE (or J2EE, now known as Jakarta EE) specification. Its first version came out in 1999, and it was one of the first technologies designed to make server-side enterprise application development easier in Java.
EJB规范是Java EE(或J2EE,现在称为Jakarta EE)规范的一个子集。它的第一个版本于1999年问世,它是最早的技术之一,旨在使服务器端的企业应用开发在Java中更加容易。
It shouldered the Java developers’ burden of concurrency, security, persistence, transaction processing, and more. The specification handed these and other common enterprise concerns over to the implementing application servers’ containers, which handled them seamlessly. However, using EJBs as they were was a bit cumbersome due to the amount of configuration required. Moreover, it was proving to be a performance bottleneck.
它承担了Java开发人员在并发性、安全性、持久性、事务处理等方面的负担。该规范将这些和其他常见的企业关注点移交给了实现应用服务器的容器,这些容器可以无缝地处理它们。然而,由于需要大量的配置,使用EJBs是有点麻烦的。此外,它还被证明是一个性能瓶颈。
But now, with the invention of annotations, and stiff competition from Spring, EJBs in their latest 3.2 version are much simpler to use than their debut version. The Enterprise Java Beans of today borrow heavily from Spring’s dependency injection and use of POJOs.
但现在,随着注解的发明,以及来自Spring的激烈竞争,最新的3.2版本的EJB在使用上要比其首发版本简单得多。今天的企业Java Bean在很大程度上借鉴了Spring的依赖注入和对POJOs的使用。
2.2. Spring
2.2.Spring
While EJBs (and Java EE in general) were struggling to satisfy the Java community, Spring Framework arrived like a breath of fresh air. Its first milestone release came out in the year 2004 and offered an alternative to the EJB model and its heavyweight containers.
当EJB(以及整个Java EE)正在努力满足Java社区的需求时,Spring框架的出现就像一股清新的空气。它的第一个里程碑式的版本在2004年问世,为EJB模型及其重量级容器提供了一个替代方案。
Thanks to Spring, Java enterprise applications could now be run on lighter-weight IOC containers. Moreover, it also offered dependency inversion, AOP, and Hibernate support among myriad other useful features. With tremendous support from the Java community, Spring has now grown exponentially and can be termed as a full Java/JEE application framework.
由于Spring的出现,Java企业应用程序现在可以在更轻量级的IOC容器上运行。此外,它还提供了依赖反转、AOP和Hibernate支持等无数有用的功能。在Java社区的巨大支持下,Spring现在已经飞速发展,可以说是一个完整的Java/JEE应用框架。
In its latest avatar, Spring 5.0 even supports the reactive programming model. Another offshoot, Spring Boot, is a complete game-changer with it’s embedded servers and automatic configurations.
在其最新版本中,Spring 5.0甚至支持反应式编程模型。另一个分支,Spring Boot,由于它的嵌入式服务器和自动配置,完全改变了游戏规则。
3. Prelude to the Feature Comparison
3.特征比较的前奏
Before jumping to the feature comparison with code samples, let’s establish a few basics.
在跳转到带有代码样本的功能比较之前,让我们先建立一些基础知识。
3.1. Basic Difference Between the Two
3.1.两者之间的基本区别
First, the fundamental and apparent difference is that EJB is a specification, whereas Spring is an entire framework.
首先,最根本和明显的区别是:EJB是一种规范,而Spring是整个框架。
The specification is implemented by many application servers such as GlassFish, IBM WebSphere and JBoss/WildFly. This means that our choice to use the EJB model for our application’s backend development is not enough. We also need to choose which application server to use.
该规范由许多应用服务器实现,如GlassFish、IBM WebSphere和JBoss/WildFly。这意味着我们选择使用EJB模型进行应用程序的后端开发是不够的。我们还需要选择使用哪种应用服务器。
Theoretically, Enterprise Java Beans are portable across app servers, though there is always the prerequisite that we shouldn’t use any vendor-specific extensions if interoperability is to be kept as an option.
理论上,Enterprise Java Beans可以跨应用服务器进行移植,尽管总是有一个前提条件,即如果要保持互操作性,我们不应该使用任何供应商特定的扩展。
Second, Spring as technology is closer to Java EE than EJB in terms of its broad portfolio of offerings. While EJBs only specify backend operations, Spring, like Java EE, also has support for UI development, RESTful APIs, and Reactive programming to name a few.
其次,Spring作为技术,在其广泛的产品组合方面比EJB更接近于Java EE。EJB只指定后端操作,而Spring和Java EE一样,也支持UI开发、RESTful APIs和Reactive编程,仅举几例。
3.2. Useful Information
3.2.有用信息
In the sections that follow, we’ll see the comparison of the two technologies with some practical examples. Since EJB features are a subset of the much larger Spring ecosystem, we’ll go by their types and see their corresponding Spring equivalents.
在接下来的章节中,我们将通过一些实际的例子来看这两种技术的比较。由于EJB的特性是更大的Spring生态系统的一个子集,我们将按照它们的类型,看看它们对应的Spring等价物。
To best understand the examples, consider reading up on Java EE Session Beans, Message Driven Beans, Spring Bean, and Spring Bean Annotations first.
为了更好地理解这些示例,请考虑先阅读Java EE会话Bean、消息驱动Bean、Spring Bean以及Spring Bean注释。
We’ll be using OpenJB as our embedded container to run the EJB samples. For running most of the Spring examples, its IOC container will suffice; for Spring JMS, we’ll need an embedded ApacheMQ broker.
我们将使用OpenJB作为我们的嵌入式容器来运行EJB示例。对于运行大多数Spring示例,其IOC容器就足够了;对于Spring JMS,我们需要一个嵌入式ApacheMQ代理。
To test all our samples, we’ll use JUnit.
为了测试我们所有的样本,我们将使用JUnit。
4. Singleton EJB == Spring Component
4.单子EJB == Spring Component
Sometimes we need the container to create only a single instance of a bean. For example, let’s say we need a bean to count the number of visitors to our web application. This bean needs to be created only once during application startup.
有时我们需要容器只创建一个Bean的单一实例。例如,假设我们需要一个Bean来计算我们的Web应用程序的访问者数量。这个Bean只需要在应用程序启动时创建一次。
Let’s see how to achieve this using a Singleton Session EJB and a Spring Component.
让我们看看如何使用Singleton Session EJB和Spring Component实现这一目标。
4.1. Singleton EJB Example
4.1.单子EJB实例
We’ll first need an interface to specify that our EJB has the capability to be handled remotely:
我们首先需要一个接口来指定我们的EJB有能力被远程处理。
@Remote
public interface CounterEJBRemote {
int count();
String getName();
void setName(String name);
}
The next step is to define an implementation class with the annotation javax.ejb.Singleton, and viola! Our singleton is ready:
下一步是用注解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
}
But before we can test the singleton (or any other EJB code sample), we need to initialize the ejbContainer and get the context:
但在我们测试单子(或其他EJB代码样本)之前,我们需要初始化ejbContainer并获得context。
@BeforeClass
public void initializeContext() throws NamingException {
ejbContainer = EJBContainer.createEJBContainer();
context = ejbContainer.getContext();
context.bind("inject", this);
}
Now let’s look at the test:
现在我们来看看这个测试。
@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());
}
A few things to note in the above example:
在上述例子中,有几件事需要注意。
- We are using the JNDI lookup to get counterEJB from the container
- count2 picks up from the point count left the singleton at, and adds up to 20
- secondCounter retains the name we set for firstCounter
The last two points demonstrate the significance of a singleton. Since the same bean instance is used each time it’s looked up, the total count is 20 and the value set for one remains the same for the other.
最后两点说明了单子的意义。由于每次查询都是使用同一个bean实例,所以总计数是20,而且为一个bean设置的值对另一个bean也是一样的。
4.2. Singleton Spring Bean Example
4.2.Singleton Spring Bean示例
The same functionality can be obtained using Spring components.
使用Spring组件也可以获得同样的功能。
We don’t need to implement any interface here. Instead, we’ll add the @Component annotation:
我们不需要在这里实现任何接口。相反,我们将添加@Component注解。
@Component
public class CounterBean {
// same content as in the EJB
}
In fact, components are singletons by default in Spring.
事实上,Spring中的组件默认是单体的。
We also need to configure Spring to scan for components:
我们还需要配置Spring以扫描组件。
@Configuration
@ComponentScan(basePackages = "com.baeldung.ejbspringcomparison.spring")
public class ApplicationConfig {}
Similar to how we initialized the EJB context, we’ll now set the Spring context:
与我们初始化EJB上下文的方式类似,我们现在要设置Spring上下文。
@BeforeClass
public static void init() {
context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
}
Now let’s see our Component in action:
现在让我们看看我们的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());
}
As we can see, the only difference with respect to EJBs is how we are getting the bean from the Spring container’s context, instead of JNDI lookup.
我们可以看到,与EJB的唯一区别是我们如何从Spring容器的上下文中获取Bean,而不是JNDI查询。
5. Stateful EJB == Spring Component with prototype Scope
5.有状态的EJB == Spring Component with prototype Scope
At times, say when we are building a shopping cart, we need our bean to remember its state while going back and forth between method calls.
有时,比如说当我们正在构建一个购物车时,我们需要我们的Bean在方法调用之间来回走动时记住它的状态。
In this case, we need our container to generate a separate bean for each invocation and save the state. Let’s see how this can be achieved with our technologies in question.
在这种情况下,我们需要我们的容器为每个调用生成一个单独的Bean并保存状态。让我们看看如何用我们的相关技术来实现这一点。
5.1. Stateful EJB Example
5.1.有状态的EJB实例
Similar to our singleton EJB sample, we need a javax.ejb.Remote interface and its implementation. Only this time, its annotated with javax.ejb.Stateful:
与我们的单子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
}
Let’s write a simple test to set a name and add items to a bathingCart. We’ll check its size and verify the 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());
}
Now, to demonstrate that the bean really maintains state across instances, let’s add another shoppingCartEJB to this test:
现在,为了证明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());
Here we did not set the name and hence its value was null. Recall from the singleton test, that the name set in one instance was retained in another. This demonstrates that we got separate ShoppingCartEJB instances from the bean pool with different instance states.
这里我们没有设置name,因此它的值是空的。回顾一下单子测试,在一个实例中设置的名称在另一个实例中被保留了。这表明我们从bean pool中得到了不同的ShoppingCartEJB实例,而且实例状态不同。
5.2. Stateful Spring Bean Example
5.2.有状态的Spring Bean示例
To get the same effect with Spring, we need a Component with a prototype scope:
为了获得与Spring相同的效果,我们需要一个带有原型作用域的Component。
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShoppingCartBean {
// same contents as in the EJB
}
That’s it, just the annotations differ – the rest of the code remains the same.
就是这样,只有注释不同–代码的其他部分保持一致。
To test our Stateful bean, we can use the same test as described for EJBs. The only difference is again how we get the bean from the container:
为了测试我们的有状态Bean,我们可以使用与EJB相同的测试。唯一的区别是我们如何从容器中获取Bean。
ShoppingCartBean bathingCart = context.getBean(ShoppingCartBean.class);
6. Stateless EJB != Anything in Spring
6.无状态的EJB !=Spring中的任何东西
Sometimes, for example in a search API, we neither care about the instance state of a bean nor if it is a singleton. We just need the results of our search, which might be coming from any bean instance for all we care about.
有时,例如在搜索API中,我们既不关心Bean的实例状态,也不关心它是否是一个单子。我们只需要我们的搜索结果,我们所关心的可能是来自任何Bean实例的结果。
6.1. Stateless EJB Example
6.1.无状态EJB实例
For such scenarios, EJB has a stateless variant. The container maintains an instance pool of beans, and any of them is returned to the calling method.
对于这种情况,EJB有一个无状态的变体。容器维护着一个bean的实例池,其中的任何一个都会返回给调用方法。
The way we define it is the same as other EJB types, with a remote interface, and implementation with javax.ejb.Stateless annotation:
我们定义它的方式与其他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);
}
}
Let’s add another simple test to see this in action:
让我们再增加一个简单的测试来看看这个动作。
@Test
public void givenStatelessBean_whenSearchForA_thenApple() throws NamingException {
assertEquals("Apple", alphabetFinder.search("A"));
}
In the above example, alphabetFinder is injected as a field in the test class using the annotation javax.ejb.EJB:
在上面的例子中,alphabetFinder使用注解javax.ejb.EJB被注入测试类中的一个字段。
@EJB
private FinderEJBRemote alphabetFinder;
The central idea behind Stateless EJBs is to enhance performance by having an instance pool of similar beans.
无状态EJB的中心思想是通过拥有一个类似Bean的实例池来提高性能。
However, Spring does not subscribe to this philosophy and only offers singletons as stateless.
然而,Spring并不认同这一理念,只提供无状态的单体。
7. Message Driven Beans == Spring JMS
7.消息驱动Bean == Spring JMS
All EJBs discussed so far were session beans. Another kind is the message-driven one. As the name suggests, they are typically used for asynchronous communication between two systems.
到目前为止讨论的所有EJB都是会话Bean。另一种是消息驱动的。顾名思义,它们通常用于两个系统之间的异步通信。
7.1. MDB Example
7.1.MDB实例
To create a message-driven Enterprise Java Bean, we need to implement the javax.jms.MessageListener interface defining its onMessage method, and annotate the class as javax.ejb.MessageDriven:
要创建一个消息驱动的企业Java Bean,我们需要实现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);
}
}
}
Notice that we are also providing a couple of configurations for our MDB:
请注意,我们还为我们的MDB提供了几个配置。
-
-
- destinationType as Queue
- myQueue as the destination queue name, to which our bean is listening
-
In this example, our receiver also produces an acknowledgment, and in that sense is a sender in itself. It sends a message to another queue called ackQueue.
在这个例子中,我们的接收者也产生了一个确认,在这个意义上,它本身就是一个发送者。它向另一个叫做ackQueue的队列发送了一条消息。
Now let’s see this in action with a test:
现在,让我们通过一个测试来看看这一点的作用。
@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());
}
Here we sent a message to myQueue, which was received by our @MessageDriven annotated POJO. This POJO then sent an acknowledgment and our test received the response as a MessageConsumer.
这里我们向myQueue发送了一条消息,被我们的@MessageDriven注解的POJO收到。然后这个POJO发送了一个确认,我们的测试作为一个MessageConsumer收到了这个响应。
7.2. Spring JMS Example
7.2.Spring JMS实例
Well, now it’s time to do the same thing using Spring!
好了,现在是时候用Spring做同样的事情了!
First, we’ll need to add a bit of configuration for this purpose. We need to annotate our ApplicationConfig class from before with @EnableJms and add a few beans to setup JmsListenerContainerFactory and JmsTemplate:
首先,我们需要为此添加一些配置。我们需要用@EnableJms来注释我们之前的ApplicationConfig类,并添加一些bean来设置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;
}
}
Next, we need a Producer – a simple Spring Component – that will send messages to myQueue and receive an acknowledgment from ackQueue:
接下来,我们需要一个Producer–一个简单的SpringComponent–它将向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");
}
}
Then, we have a Receiver Component with a method annotated as @JmsListener to receive messages asynchronously from myQueue:
然后,我们有一个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");
}
}
It also acts as a sender for acknowledging message receipt at ackQueue.
它还充当发件人,在ackQueue确认消息的接收。
As is our practice, let’s verify this with a test:
按照我们的惯例,让我们用一个测试来验证这一点。
@Test
public void givenJMSBean_whenMessageSent_thenAcknowledgementReceived() throws NamingException {
Producer producer = context.getBean(Producer.class);
producer.sendMessageToDefaultDestination("marco");
assertEquals("polo", producer.receiveAck());
}
In this test, we sent marco to myQueue and received polo as an acknowledgment from ackQueue, the same as what we did with the EJB.
在这个测试中,我们向marco发送myQueue,并从ackQueue收到polo作为确认,与我们对EJB所做的相同。
One thing of note here is that Spring JMS can send/receive messages both synchronously and asynchronously.
这里需要注意的一点是,Spring JMS可以同步和异步地发送/接收消息。
8. Conclusion
8.结语
In this tutorial, we saw a one-on-one comparison of Spring and Enterprise Java Beans. We understood their history and basic differences.
在本教程中,我们看到了Spring和Enterprise Java Beans的一对一的比较。我们了解了它们的历史和基本区别。
Then we dealt with simple examples to demonstrate the comparison of Spring Beans and EJBs. Needless to say, it is merely scratching the surface of what the technologies are capable of, and there is a lot more to be explored further.
然后我们处理了一些简单的例子来证明Spring Bean和EJB的比较。不用说,这仅仅是触及了技术的表面,还有很多需要进一步探索的地方。
Furthermore, these might be competing technologies, but that doesn’t mean they can’t co-exist. We can easily integrate EJBs in the Spring framework.
此外,这些可能是相互竞争的技术,但这并不意味着它们不能共存。我们可以轻松地将EJB集成到Spring框架中。
As always, source code is available over on GitHub.
一如既往,源代码可在GitHub上获取。