EJB 3 Advanced Concepts
In this topic we discuss how the container works and the concept of managed services, I also will be discussing dependency injection, JNDI lookups, EJB interceptors and the EJB timer service.
EJB centers on the idea of managed objects, beans are just annotated POJOs themselves. When a client invokes an EJB method using the bean interface it does not work directly on the bean instance, the container makes beans special by acting as a proxy between the client and the actual bean instance. This enables the container to provide EJB services to the client on behalf of the bean instance. For each bean instance the container automatically generates a proxy called an EJB Object. The EJB object has access to all the functionality of the container, including the JNDI registry, security, transaction management, thread pools, session management and pretty much anything else that is necessary to provide EJB services, you can see this in a diagram below
For session beans the client interacts with the EJB object through the business interface, for MDBs the EJB object or message endpoint sits between the message provider and the bean instance.
Services like transaction management, security, dependency injection, etc are meant to be overlaid on the bean through configuration, However there might be times when the bean needs to access the container directly, this is when you can use the EJB context. The javax.ejb.EJBContext interface is the backdoor to the container. The EJBContext interface is below
EJBContext interface | public interface EJBContext { |
EJBContext Methods |
|
getCallerPrincipal isCallerInRole |
These methods are used in security |
getEJBHome getEJBLocalHome |
These methods are used to obtain the remote home and local home interfaces, they are used mainly for backward compatibility |
getRollbackOnly setRollbackOnly |
These are used for container-managed transactions |
getUserTransaction | Used for bean-managed transactions |
getTimerService | Used to get access to the EJB timer service |
lookup | This method is used to get references to objects stored in the JNDI registry, due to dependency injection (DI) this is hardy used |
Both session and MDBs have their own subclass of the javax.ejb.EJBContext as shown below
To access the appropriate Context you can use dependency injection as seen below
SessionContext | @Stateless Note: this adds a number of methods that are session specific |
MessageDrivenContext | @MessageDriven Note: no additional methods but throws exceptions if the following methods are called isCallerRole, getEJBHome or getEJBLocalHome, it makes no sense calling these methods |
The @Resource annotation is the most versatile mechanism for DI in EJB 3, as you are already aware it can be used for injecting JDBC data sources, JMS server resources and EJB contexts. It can also be used for e-mail server resources, environment entries, ORB reference, or even EJB references.
Normally you would setup a data source as follows (using JBoss), then access that data source via the @Resource annotation
web.xml | <resource-ref> <res-ref-name>OracleDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> |
jboss-web.xml | <resource-ref> <res-ref-name>OracleDS</res-ref-name> <jndi-name>java:OracleDS</jndi-name> </resource-ref> |
@Resource annotation | @Stateless public class PlaceBidBean implements PlaceBid { @Resource(name="OracleDS") private javax.sql.Datasource datasource; |
The @Resource annotation definition is
@Resource definition | @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface Resource { public enum AuthenticationType { CONTAINER, APPLICATION } String name default ""; Class type() default Object.class; AuthenticationType authenticationType() default AuthenticationType.CONTAINER; boolean shareable() default true; String mappedName() default ""; description() default ""; } |
@Resource Parameters |
|
AuthenticationType | Defines the type of authentication for accessing the resource, container means that the container's security context is used for the resource. Application means that authentication for the resource must be provided by the application. |
shareable | specifies if the resource can be shared |
description | A description of the resource |
mappedName | A Vendor-specific name that the resource may be mapped to as opposed to the JNDI name |
You can also use setter/getter injection instead of field injection
setter/getter injection | @Stateless public class PlaceBidBean implements PlaceBid { ... private Datasource dataSource; ... @Resource(name="OracleDS") public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public DataSource getDataSource() { return dataSource; } |
The two advantages for using setter/getter injection is
Here a number of examples using the @Resource annotation
JMS Resources | @Resource(name="jms/actionBazaarQueue") private Queue queue; |
EJBContext | @Resource EJBContext context; @Resource SessionContext context; @Resource MessageDrivenContext context; |
Environment entries | @Resource private boolean censorship; # The deployment descriptor would be, make sure the data types are compatible otherwise you will # get a runtime exception <env-entry> <env-entry-name>censorship</env-entry-name> <env-entry-type>java.lang.Boolean</env-entry-type> <env-entry-value>true</env-entry-value> </env-entry> |
E-Mail Resources | @Resource(name="mail/ActionBazaar") private javax.mail.Session mailSession; |
Timer Service | @Resource javax.ejb.TimerService timerService; |
If the superclass defines any dependencies on resources using the @Resource annotation, they are inherited by the subclass.
There are two ways to programmatically perform lookups
EJB Context (inside the container) | @EJB(name="ejb.BidderAccountCreator", beanInterface = BidderAccountCreator.class) @Stateless public class GoldBidderManagerBean implements GoldBidderManager { @Resource SessionContext sessionContext; ... BidderAccountCreator = accountCreator = (BidderAccountCreator) sessionContext.lookup( "ejb/BidderAccountCreator"); ... } |
JNDI initial context (outside the container) | Context context = new InitialContext(); |
Before I talk about interceptors, i want to talk about Aspect-Oriented Programming (AOP), most applications, common application code repeated across modules not necessary for solving core business problems are considered as infrastructure concerns. There are a number of common examples that would be coded in to each module logging, auditing, profiling and statistics. The common term used to describe these cases is crosscutting concerns - concerns that cut across application logic. An AOP system allows the separation of crosscutting concerns into their own modules, these modules are then applied across the relevant cross section of application code. EJB 3 supports AOP-like functionality by providing the ability to intercept business methods and lifecycle callbacks.
Interceptors are objects that are automatically triggered when an EJB method is invoked. EJB 3 interceptors are triggered at the beginning of a method and are around when the method returns, they can inspect the return value or any exceptions thrown by the method. They can be applied to both Session and Message-Drive beans.
Here is an simple interceptor example
Interceptor class | public class ActionBazaarLogger { @AroundInvoke public Object logMethodEntry ( InvocationContext invocationContext ) throws Exception { System.out.println("Entering Method: " + invocationContext.getMethod().getName() ); // Tell the container it can proceed normally return invocationContext.proceed(); } } |
Using the interceptor | @Stateless Public class PlaceBidBean implements PlaceBid { ... // Interceptor will be nvoke when this method is called @Interceptors(ActionBazaarLogger.class) public void addBid(Bid bid) { ... } } |
The @Interceptor annotation can accept multiple interceptor classes, they are called in the order they are specified, you can also attach the interceptor to the whole class which means any method invoke will invoke the interceptor. To attach a interceptor to all EJB modules you need to specify it in a deployment descriptor. An @Interceptor class should have only one method that is designated as the around invoke method.
Attach interceptor to all EJB modules | <assembly-descriptor> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>action.bazaar.buslogic.ActionBazaarLogger</interceptor-class> </interceptor-binding> </assembly-descriptor> |
By default interceptors are called from the larger scope to the smaller scope: Default->Class->Method, there ia a parameter to specify the order called interceptor-order. You can also exclude interceptors by using the annotation @ExcludeDefaultInterceptors or @ExcludeClassInterceptors.
The InvocationContext interface passed in as the single parameter to the method provides a number of features that makes the AOP mechanism extremely flexible. The above example uses the two of the methods included in the InvocationContext interface, the getMethod().getName() and the proceed() method which tells the container that it should proceed to the next interceptor in the execution chain or call the intercepted business method, not calling the method will bring the processing to a halt and avoid the business method from being called, you could use this for a security validation for an example the interceptor method prevents the intercepted business method from being executed if the security validation fails.
The InvocationContext interface definition highlights more useful methods
InvocationContext interface | public interface InvocationContext { Notes: getMethod - returns the method of the bean class for which the interceptor was invoked |
Lifecycle callbacks are a form of interceptor as well, where interceptors are triggered when a business method is invoked a lifecycle is triggered when a bean moves from one lifecycle state to another, you can incorporate lifecycle events into interceptors.
Lifecycle callbacks methods in the interceptors class | public class ActionBazaarResourceLogger { @PreDestroy |
Here is a summary table of both business methods interceptors and lifecycle callbacks
Supported Features | LifecycleCallback methods | Business Method Interceptor |
Invocation | Gets invoked when a certain lifecycle event occurs | Gets invoked when a business method is called by a client |
Location | In a separate Interceptor class or in the bean class | In the class or an interceptor class |
Method Signature | # in a separate interceptor class. # In the bean class |
Object <METHOD> (InterceptorContext) throws Exception |
Annotation | @PreDestroy @PostContruct @PrePassivate @PostActive |
@AroundInvoke |
Exception Handling | May throw runtime exceptions but must not throw check exceptions, may catch and swallow exceptions. No other lifecycle callback methods are called if an exception is thrown | May throw application or runtime exception May catch and swallow runtime exceptions No other business interceptor methods or the business method itself are called if an exception is thrown before calling the proceed method |
Transaction and security context | No security and transaction context. | Share the same secruity and transaction context within which the original buisness method was invoked |
The Unix cron utility is probably the most people recognize, in the Java Quartz is a good open source implementation. The EJB 3 timer service is based on the idea of a time-delayed callback. The container will automatically invoke the method on your behalf when the time interval you specified elapses, you can even call the method at regular intervals. Timers are only used with session beans and MDBs, because of there asynchronous stateless nature. Timers are persistent (can survive crashes and restarts), they are also transactional, which means that if a failure occurs in a timer method it rolls back the transaction.
Here is an example
Timer example | public class PlaceBidBean implements PlaceBid { ... @Resource TimerService timerService; public void addBid(Bid Bid) { .. Code to add bid ... timerService.createTimer(15*60*1000, 15*60*1000, bid); ... } @Timeout public void monitorBid(Timer timer) { Bid bid = (Bid) timer.getInfo(); ... Code to monitor the bid ... } } |
We use resource injection to get a timer service, we then schedule a timer service callback to occur every 15 minutes, by attaching the newly create Bid, at regular intervals the monitorBid method is called by the timer service which is designed with the @Timeout annotation. The monitorBid method retrieves the Bid instance attached as timer information and monitors the bid.
You can also get a timer service via the EJB context
Obtaining timer service | @Resource SessionContext context; TimerService timerService = context.getTimerService(); |
The timerService has four overloaded methods to add timers, the one above should initially trigger in 15 minutes (15*60*1000 milliseconds) and repeat every 15 minutes and added a Bid instance as Timer information. The Timer definition is below, I will leave you to investigate further
Timer definition | public interface javax.ejb.TimerService { public Timer createTimer(long duration, java.io.Serializable info); public Timer createTimer(long initialDuration, long intervalDuration, java.io.Serializable info); public Timer createTimer(java.util.Date expiration, java.io.Serializable info); public Timer createTimer(java.util.Date initialExpiration, long intervalDuration, java.io.Serializable info); public Collection getTimers(); } |
A bean can have at most only one @Timeout method, which can either be specified by annotation or deployment descriptor (timeout-method), the timeout interface is below
timeout interface | public interface javax.ejb.Timer { public void cancel(); public long getTimeRemaining(); public java.util.Date getNextTimeout(); public javax.ejb.TimerHandle getHandle(); public java.io.Serializable getInfo(); } |
The above timer does not offer a fully feature rich scheduling like Quartz or Flux but can be use in some situations, the below is what it can offer and cannot offer