Summary This article will discuss with you how to combine the power of annotations and aspects to provide declarative services to enterprises in a compatible way with ejb 3.0 while still providing container independence.
1. Introduction
In our joint pursuit of ways to further improve the production performance of software development, we - as members of the java community - generally turn to j2ee to provide more challenging technical issues in enterprise development such as distributed transaction management, concurrency and objects Distribution solutions. The guiding ideology behind it - these complex enterprise services can be implemented by application server vendors and balanced by commercial developers - is indeed a good idea. j2ee, specifically ejb, has successfully provided a platform on which enterprise java applications are built.
Part of this success is due to the ability to perform declarative programming - a way to develop a program - in which you can declare infrastructure services instead of explicitly encoded with business logic so that the code is spread everywhere. ejb has proven the value of this programming approach - by allowing enterprise problems such as transactions and security to be declared with a publish descriptor and handled by a container.
However, over the past years, more and more developers have realized that ejb brings itself a lot of new challenges in the productivity of a team – each ejb must be accompanied by multiple interfaces, with a publish descriptor Description, accessed via jndi, etc. Unit testing on ejb outside the container also brings additional difficulties. Now ejb no longer focuses on pure object-oriented development.
Please note that in order to read this article, you need to have the following tools:
·java 2 sdk 1.5
·maven 2.0 beta 2
The goal of ejb 3.0 is to make enterprise development easier in the following aspects:
·Implement declarative requests for enterprise services by introducing metadata annotations
·Implement dependency/resource injection via annotation
·Implement the decoupling of enterprise beans and ejb specific interfaces
·Simplification of continuous storage through lightweight object-relational mapping
This is like a spring breeze for ejb developers - they have been working hard to develop, test and maintain ejb. It is now easy to write an enterprise bean using ejb 3.0, just like creating a pojo (traditional java object) with specific annotations to mark it as an ejb and request enterprise services. Here is an example from ejb in ejb 3.0 public draft:
@stateful
public class cartbean implements shoppingcart
{
private float total;
private vector products codes;
public int someshoppingmethod(){...};
...
}
The ejb 3.0 statement essentially states that what developers need is not a heavyweight, "one release satisfying everything" solution, but a lightweight and easy-to-use solution - providing developers with a certain range of enterprise services. . To this end, one of the most important methods provided by ejb 3.0 is to decouple enterprise beans and ejb APIs. And this solution also brings interesting derivatives - ejb can now run not only on different ejb containers, but also inside any application framework - these frameworks must be able to recognize ejb 3.0 (jsr 220) and Normal annotation for declaring enterprise services (jsr 250).
This article does not provide in-depth exploration of declarative programming, ejbs, aspects or annotations. Instead, just analyze the interrelationships between these technologies and discuss how to combine them in a new way to simplify application development.
In this article, you will learn how to write an ejb 3.0 compatible bean and create several simple aspects to make it declarative transaction management, security, and resource injection. I hope you can benefit from this exercise:
·Three practical applications in learning (dependency injection, security and transaction).
·Familiar with ejb 3.0 and the ideas behind it.
Recognize how to implement decoupling of ejb from specific APIs to allow ejb 3.0 compatible services to be implemented at lightweight rather than provided by ejb only.
2. Example application-flight ordering
Throughout the discussion, you will learn about an implementation of a flight ordering system - which uses aspects and annotations to implement dependency injection, security, and transaction management. The app performs only two functions: it allows users to search for flights (Figure 1) and then order a trip (Figure 2). Both operations will be processed securely to allow only identified users to perform them. Additionally, since the "order travel" operation involves ordering two flights (out and return flights), the operation needs to be created as transactional - for example, both orders will either succeed or fail as a unit of work.
Figure 1. Flight Query: First, users look for flights that meet their specified criteria.
Figure 2. Flight Ordering: Next, the user orders an outbound flight and a return flight. Both orders either succeed or fail.
This simple web application contains several servlets, a service appearance and a dao layer (see Figure 3).
Cross-cutting concerns such as resource configuration, security and transaction management will be provided by aspects (implemented with aspectj 1.5 m3) to implement the injection behavior declared in java 5 annotations.
Figure 3. Flight Ordering System Architecture: This flight ordering system consists of three main components - they join together to complete user requests. 3. Resource Injection
The ejb 3.0 draft declaration allows resources to be declared via the @resource annotation (this decision is defined in the draft ordinary annotation declaration) and injected into your ejb by container. Dependency injection is a technique - using this technique, an entity outside an object rather than an entity explicitly created for that object can provide (inject) the dependency of an object. It is sometimes described as a Hollywood principle - which jokes like "don't call us, we'll call you."
Take the travelagencyserviceimpl class as an example - In order to continuously store some data, this class needs to find an implementation of the iflightdao interface. Traditionally, this is achieved via a factory, singleton, service locator or some other customized solution. Among them, one possible solution looks like this:
public class travelagencyserviceimpl implements ittravelagencyservice
{
public iflightdao flightdao;
public travelagencyserviceimpl()
{ flightdao = flightdaofactory.getinstance().getflightdao(); }
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws invalidseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
You have seen that this implementation involves creating a specific factory class - it is likely to read configuration information stored somewhere to understand how the implementation to create iflightdao is to be created. If the service is not explicitly creating its dependencies injected by the container, configuration details and object creation will be proxied to the container. This allows components in an application to be easily connected together - with different configurations and eliminates a lot of old-fashioned singleton and factory code.
An implementation of the class - which relies on an implementation of iflightdao declared with jsr 250 resource annotation - may look like this:
public class travelagencyserviceimpl implements ittravelagencyservice
{
@resource(name = "flightdao")
public iflightdao flightdao;
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws invalidseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
In this case, the container will provide the correct implementation of a resource named "flightdao" to the service class. But what if you want to leverage resource injection now instead of waiting for the ejb 3.0 release? OK, you can take a lightweight container - it is able to provide dependency injections such as spring or pico container. However, I don't understand that there is a lightweight container - it is able to use jsr 250 resource annotations to specify injection requirements (although I'm very much looking forward to some in this regard).
One solution is to use aspects to implement dependency injection. If you use the @resource annotation for this, your implementation will be consistent with the ejb 3.0 way and forward compatible with the ejb 3.0 implementation - and that's not a very difficult thing to implement. The following list shows an aspect created with aspectj - it injects fields annotated with @resource annotation:
@aspect
public class injectionaspect
{
private dependencymanager manager = new dependencymanager();
@before("get(@resource * *.*)")
public void beforefieldaccesses(joinpoint thisjoinpoint)
throws illegalargumentexception, illegalaraccessexception
{
fieldsignature signature = (fieldsignature) thisjoinpoint.getsignature();
resource injectannotation = signature.getfield().getannotation(resource.class);
object dependency = manager.resolve dependency(signature.getfieldtype(),injectannotation.name());
signature.getfield().set(thisjoinpoint.getthis(), dependency);
}
}
All this simple aspect does is query the implementation class from a property file (this logic is encapsulated in dependencymanager object) and inject it into the fields annotated with @resource annotation before accessing the field. Obviously, this implementation is not complete, but it does illustrate how you can provide resource injection in a jsr 250-compatible way without ejb.
4. Safety
In addition to resource injection, jsr 250 and ejb 3.0 also provide a metadata secure representation via annotation. The javax.annotation.security package defines five annotations - runas, rolesallowed, permitall, denyall and rolesreferenced - all of which can be applied to methods to define security requirements. For example, if you want to declare that the bookflight method listed above can only be executed by callers with the "user" role, you can annotate this method with the following security constraints:
public class travelagencyserviceimpl implements ittravelagencyservice
{
@resource(name = "flightdao")
public iflightdao flightdao;
@rolesallowed("user")
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws invalidseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
This annotation will state that the container is responsible for ensuring that only the caller of the specified role can execute this method. So now I will show another simple aspect - it will further strengthen security constraints on the application:
@aspect
Public class securityaspect
{
@around("execution(@javax.annotation.security.rolesallowed * *.*(..))")
public object aroundsecuredmethods(proceedingjoinpoint thisjoinpoint)
throws throwable
{
boolean callerauthorized = false;
rolesallowed rolesallowed = rolesallowedforjoinpoint(thisjoinpoint);
for (string role : rolesallowed.value())
{
if (callerinrole(role))
{ callerauthorized = true; }
}
if (callerauthorized)
{ return thisjoinpoint.proceed(); }
else
{
throw new runtimeexception("caller not authorized to perform specified function");
}
}
private rolesallowed rolesallowedforjoinpoint(proceedingjoinpoint thisjoinpoint)
{
methodsignature methodsignature = (methodsignature) thisjoinpoint.getsignature();
method targetmethod = methodsignature.getmethod();
return targetmethod.getannotation(rolesallowed.class);
}
private boolean callerinrole(string role)
{ ... }
}
This aspect includes the execution of all methods - by verifying that the caller is one of the roles specified in the annotation, annotate with the @rolesallowed annotation and ensuring that the caller is authorized to call the method. Of course you can also use any algorithm you like to authorize the user and retrieve his/her role, such as jaas or a customized solution. In this example program, for convenience, I chose to proxy to the servlet container.
V. Affairs
Transactions become an important part of enterprise development - because they facilitate data integration in a concurrent environment. From a high level, transactions can ensure this through multiple or complete or incomplete operations.
Unlike the annotations for resource injection and security, the annotations for transactions are specific to ejb 3.0 and are not defined in the jsr 250 normal annotations. ejb 3.0 defines two annotations associated with transactions: transactionmanagement and transactionattribute. The transactionmanager annotation specifies whether the transaction is managed by the container or by the bean. In ejb 3, if this annotation is not specified, the transaction managed by the container will be used. The transactionattribute annotation is used to specify the transaction propagation level of the method. Valid values - including mandatory, required, required new, supported, unsupported and never supported - are used to define whether an existing transaction is required or a new transaction is initiated, etc.
Because the bookflight operation consists of two steps - ordering an outbound flight and a return flight, by packaging it into a transaction, you can ensure consistency of the operation. By using ejb 3.0 transaction annotations, this will look like this:
public class travelagencyserviceimpl implements ittravelagencyservice
{
@resource(name = "flightdao")
public iflightdao flightdao;
@rolesallowed("user")
@transactionattribute(transactionattributetype.required)
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws invalidseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
And you can apply a simple aspect to automatically define transaction boundaries:
@aspect
public class transactionaspect
{
@pointcut("execution(@javax.ejb.transactionattribute * *.*(..))")
public void transactionalmethods(){}
@before("transactionalmethods()")
public void beforetransactionalmethods()
{ hibernateutil.begintransaction(); }
@afterreturning("transactionalmethods()")
public void afterreturningtransactionalmethods()
{ hibernateutil.committransaction(); }
@afterthrowing("transactionalmethods()")
public void after throwingtransactionalmethods()
{ hibernateutil.rollbacktransaction(); }
}
This implementation is based on the assumption that hibernate and ubiquitous thread-local pattern are used to manage hibernate sessions and transaction objects; however, any other suitable implementation, such as jta-based implementations, can be used instead.
6. Summary
By using the ejb 3.0 and jsr 250 annotation sets, this article has shown how cross-cutting concerns such as resource management, security, and transactions are implemented as aspects. Of course, there are many other contents that we need to learn further. The first thing to learn is the blueprint provided by the modular cross-cutting concerns by implementing these example aspects using aspectj. Secondly, we have seen some new ideas and concepts behind the ejb 3.0 statement that is now emerging. Finally, we also see in a dramatic way the freedom that must be provided to decouple our business objects from the ejb API. At this point, all you want to make travelagencyserviceimpl a stateless session is to do is add a last note:
@stateful
public class travelagencyserviceimpl implements ittravelagencyservice
{ ... }
Finally, I really hope that this free approach to providing enterprise services will bring competition and innovation in the framework/container industry.