EJB 3: From legacy technology to secret weapon

[复制链接]
查看11 | 回复9 | 2017-9-26 13:06:30 | 显示全部楼层 |阅读模式
Four factors that streamline and modernize EJB 3 development
By Adam Bien, JavaWorld.com, 10/28/08
Some say Enterprise JavaBeans earned its reputation for complexity one burned out programmer at a time. But developers today have begun to notice that EJB 3 is an entirely different animal. Convention over configuration, annotations, dependency injection, and aspect orientation make modern EJBs a lean alternative to XML-laden and JAR-heavy frameworks. In this article, Adam Bien explains how EJB 3's new component model and persistence handling help streamline enterprise development -- and even work well for small and midrange applications.
Enterprise JavaBeans (EJB) technology is known for being neither lightweight nor cool. Until the advent of EJB 3.0, this reputation was certainly warranted. Now, though, EJB technology is lean and sound. EJBs are lighter -- even weightless. They no longer require XML configuration, deployment of additional frameworks, or JARs. They integrate well with the Java Persistence API (JPA); scale well on multicore, multi-CPU machines; and are in fact the only vendor-neutral and portable solution for enterprise server-side applications.
Alternative frameworks and solutions, on the other hand, sometimes turn out to be bloated. They can require countless JARs to be configured with several pages of XML and deployed to a Web container that, however "lightweight" it might be, ends up hosting a heavyweight application.
For all of these reasons EJB 3 is worth a second look. Let's consider what has changed about Enterprise JavaBeans.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
EJB then and now
No doubt about it: the EJB 2.1 specification was not concise. It severely violated the DRY (Don't Repeat Yourself) principle. Information about an EJB component was spread across its remote interface, home interface, bean class, and deployment descriptor. Without tools, refactoring was unproductive and tedious -- and IDEs in that era weren't particularly good at refactoring.
Developers incurred considerable overhead building even simple functionality. The bean class had to implement the javax.ejb.SessionBean interface and was forced to implement all the life-cycle methods. ejbActivate and ejbPassivate were invoked only in stateful session beans, but most deployed session beans were stateless.
For example, in Listing 1, the one and only "business" method -- sayHello -- is just a fraction of the overall code for HelloWorldBean.
Listing 1. Legacy EJB 2 session bean
public class HelloWorldBean implements SessionBean {
public void setSessionContext(SessionContext aContext) { }
public void ejbCreate() {}
public void ejbActivate() {} //SFSB only
public void ejbPassivate() {}//SFSB only
public void ejbRemove() {}
public void sayHello() {
System.out.println("Hello!");
}
}
The sayHello method had to be declared in an interface too, as in Listing 2:
Listing 2. EJB 2 remote interface
public interface HelloWorldRemote extends EJBObject {
void sayHello() throws RemoteException;
}
The interface had to throw a checked RemoteException. Even so, the bean did not implement the so-called remote interface. This was EJB 1.x/2.x's main flaw from a maintenance perspective. Type and signature checks were performed during deployment, not at compilation time. Deployment errors were hard to find, which significantly increased round-trip times. You had to provide a deployment descriptor in addition to the code and keep in sync with the code. The XML deployment descriptor was the glue for all the disparate parts, so most of the information already contained in the code was repeated in the descriptor.
Although EJB 2.x session beans were verbose, and the programming harder than necessary, the benefits were huge. Remoting, the single-threaded programming model, state management, transactions, and concurrency control came for "free." These benefits are all less important (and less interesting) for development than for production, however, which is one reason why developers didn't really care.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
EJB 3.0
From a programming-model point of view, EJB 3 is revolutionary, not evolutionary. The EJB 3 specification is similar to EJB 2.1 behind the scenes, but the changes in the component model and persistence are huge.
To operate, EJB 3.0 beans still need the same amount of information as their EJB 2.1 predecessors. But the way they derive and leverage the information has changed dramatically, and even inverted. Instead of configuring the obvious, you must specify only the deviations from the default. The default case contains metadata -- such as class name, method names, attribute names, and suitable conventions -- that is sufficient for about 80 percent of all cases. You can fulfill specific requirements by using annotations for configuration, or even by overriding the defaults with XML.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Convention over configuration, or configuration by exception
The principle of convention over configuration is simple: suitable convention is preferable to fine-grained configuration. (The EJB 3 Expert Group didn't invent convention over configuration, by the way; it borrowed it from Ruby on Rails.) But convention alone is not enough. Essential information, such as the name of the class, is still needed, but it's derived from the class itself using reflection and doesn't need to be configured. This is the main difference between EJB 3 and Java 2 Enterprise Edition (J2EE) and other frameworks from its era: With EJB 3, plain classes with only a few annotations have replaced huge deployment descriptors and XML configuration.
Annotations
A subset of the information stored in EJB 2.1 deployment descriptors is available now as Java 5 annotations. The EJB 3.x and JPA specifications rely heavily on them. Annotations are supplemented by metadata derived via reflection from the bytecode and by suitable defaults. The introduction of annotations has dramatically improved the metadata's usability. Now the compiler checks for the availability of default values and for correct typing, so you needn't rely on the Javadoc and the deployment process to implement correct syntax. No additional tools or specific IDE extensions are necessary for this purpose.
The annotations introduced with Java EE 5 are lean and explicit. You just "mark" an ordinary Java class (a plain old Java object, or POJO) with the @Stateless annotation to make it an EJB, as in Listing 3.
Listing 3. EJB 3 annotated POJO
@Stateless
public class HelloBean {
}
The container recognizes HelloBean as a stateless session bean and applies behavior -- transactions, state, security aspects, and remoting -- that it derives from conventions or that you configure.
I've explicitly configured HelloBean's stateless nature with the @Stateless annotation. But you do not need to configure transactions explicitly. The default @TransactionAttribute(TransactionAttributeType.REQUIRED) annotation is also applied, although it wasn't specified. In the default case, every method in a transaction is executed. If a transaction is already active, it is reused; if no transaction exists yet, it is started for you. You can specify this annotation explicitly, override the default with another setting, or rely on the default.
Even the implementation of the interface is no longer needed. All public methods are automatically exposed to the client (the bean's user). The client acquires an instance of the bean not via a Java Naming and Directory Interface (JNDI) lookup, but using dependency injection.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Dependency injection
Dependency injection (DI) is a fancy term for a simple concept: someone or something (the EJB 3 container in this case) manages dependencies for you. You just declare the need for it and trust the mechanism. Instead of performing lookups, using factories, or even writing custom code, you simply declare the need for a resource.
For example, you no longer need to write custom code like this, as you did for EJB 2.x:
Service service = ServiceFactory.getInstance().createService();
In this custom code, the factory encapsulates the decision as to which implementation of the Service interface will be injected. You had full control over the creation and configuration of the product, which was both an advantage and a burden. Hard-coding the product creation isn't flexible, so the product was often created via reflection. It was typical to externalize the fully qualified name and store it in property or XML files.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 4 is another example of custom code that's no longer necessary:
Listing 4. Manual EJB 1.x/2.x lookup implementation
try{
Context ctx = new InitialContext();
Object proxy = ctx.lookup("service");
ServiceHome home = (ServiceHome)PortableRemoteObject.narrow(proxy,ServiceHome.class);
Service service = home.create();
}catch(Exception ex){ /* omitted */}
This a lookup of an EJB 2.X bean. Although the bean didn't implement the interface directly, a particular bean class was specified in a deployment descriptor (an XML file again), and so was replaceable as well. The amount of configuration was massive and difficult to maintain. Many frameworks from that time were highly configurable in XML. The configuration rarely paid off, because the possibly replaceable classes were actually never replaced.
With EJB 3.x, you just declare the dependency and the need for automatic association with a given instance, as in Listing 5.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 5. Declarative injection in EJB 3.x
@EJB
private Service service;
The container does the work. It scans for the annotation and injects the implementation, before a business method is invoked. The process of acquiring an implementation of an interface and associating it with a field is completely factored out from the application code, making it leaner and less error-prone.
If there's only one implementation of a given bean interface to inject, you don't need to specify it; convention over configuration kicks in again. When you have more than one implementation, the container must differentiate among the possibilities. Pure convention isn't enough; you must provide at least a hint about which class to use.
In Listing 6, for example, the container injects ClientBean, an implementation of the Service interface:
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 6. Injecting the default
@Stateless
public class ClientBean implements Client {
@EJB
private Service service;
public String getHello() {
return this.service.getMessage();
}
}
For demonstration purposes, Listing 7 introduces more than one implementation of the interface so that the convention is explicitly violated. The two implementations return different messages but are adequate for this example.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 7. Different implementations of the same interface: Beyond the convention
@Stateless
public class DefaultService implements Service {
public String getMessage() {
return "Hello from: " + this.getClass().getName();
}
}
@Stateless
public class SpecificService implements Service {
public String getMessage() {
return "Hello from: " + this.getClass().getName();
}
}
The EJB's name is derived from the simple class name (not the fully qualified name). So an attempt is made to deploy two different beans -- DefaultService and SpecificService The deployment fails at an early stage with the following error:
Cannot resolve reference Unresolved Ejb-Ref
com.abien.samples.di.client.ClientBean/service@jndi:
@[email protected]@Session@null because there are 2 ejbs in the
application with interface com.abien.samples.di.service.Service
You can solve the problem either by hard-coding the dependency with annotation attributes, or by using the optional ejb-jar.xml descriptor.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Hard-coding dependencies
Hard-coding the dependency is simple. You need only set the attribute in the @EJB annotation and specify the ejb-name, as in Listing 8.
Listing 8. Configuring the nonobvious, with annotations
public class ClientBean implements Client {
@EJB(beanName="DefaultService")
private Service service;
Using the ejb-jar.xml descriptor
Using the ejb-jar.xml descriptor is more labor-intensive. Instead of changing the code, you must resolve the dependency in the deployment descriptor, as in Listing 9.
Listing 9. Resolving the reference in the XML deployment descriptor



ClientBean



...di.client.ClientBean/service

com.abien.samples.di.service.Service

DefaultService





This approach is more flexible and allows the resolution of the dependency to happen externally, without code modification. The declaration of the dependency remains unchanged; you don't need to provide any additional attributes. It is more suitable for testing purposes or product customization.
You can even override existing annotations with the XML deployment descriptor. This capability is especially convenient for providing a specific configuration for integration testing. A "production ready" configuration that uses annotations can be still overridden with ejb-jar.xml on demand. Then you only need to provide the deployment descriptor in the integration-testing phase and make sure you remove it for production.
You can apply similar principles for injection of resources such as Java Message Service (JMS) Destinations and ConnectionFactories: you can configure the DI either with annotations or in deployment descriptors. (I'll get back to resource injection a little later on.) DI for persistence, though, requires an additional file.
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行