|
April 2005
[
avasseur
]
16:50, Thursday, 14 April 2005
In this post I present an implementation a subset of the EJB 3 specification (JSR-220) : the EJB interceptors. As of today no EJB 3 preview brings an implementation that seamlessly integrates with AOP, despite the concept similarities. Should we consider EJB interceptor as AOP ? As you may know, the EJB 3 specification (JSR-220) defines Interceptors for EJBs (stateless, statefull and message-driven). If it might be a good idea to spread an answer for the need of addressing cross-cutting concerns in J2EE and EJBs in particular, it might be a bad idea to do it for those of us (more and more numerous) familiar with AOP implementations like AspectJ and AspectWerkz, especially because huge limitations of what the specification allows us to do with those interceptors - at first sight and excluding any vendor specific extension. The most anti-AOP concept in the specification is that the EJB that wants interceptor have to declare it explicitly using a @javax.ejb.Interceptor or @javax.ejb.Interceptors class level annotation - which breaks the obliviousness of aspects. That in favor of making things explicit, which might be good for J2EE users. Further on, the EJB bean itself can have a method that intercept its own business method ie the bean is the aspect (that sounds like marketing isn'it ?). Well. That's a specification and is interesting in the sense that it democratize a technology. So what can we do to democratize AOP as part of the EJB 3 specification ? @javax.ejb.Stateless
@javax.ejb.Interceptor("test.ejb3.MyInterceptor")
public class MyEJBIsTheAspect {
// business method
public int businessSum(int i, int j) {
return i + j;
}
// interceptor method within the bean (the bean is the aspect)
@javax.ejb.AroundInvoke
public Object interceptMySelf(InvocationContext ctx) throws Exception {
System.out.println("--> MyEJBIsTheAspect.interceptMySelf");
System.out.println(" method: " + ctx.getMethod());
for (int i = 0; i < ctx.getParameters().length; i++) {
Object o = ctx.getParameters()[i];
System.out.println(" args["+i+"]: " + o);
}
return ctx.proceed();
}
}State of the art (not that much..) in JBoss and Oracle AS EJB 3 preview After having a look at JBoss EJB 3 and OracleAS EJB 3 previews, I was even more disapointed. Both of them are using a reflective based approach to invoke the interceptors. This means that the performance of the interceptor will be bad, and that a Heisenberg effect will be inevitable and actually fairly big (no wonder that ones will use interceptor to gather performance metrics and thus as soon as you observe the bean, you are observing a different things than what actually happens). Ones may say this is a microscopic view. It is. But when thinking about EJB 2 stories in the past a sound idea would be to make sure we don't waste resources where we can avoid it. And thinking about JBoss history around AOP, that's rather suprising that the EJB 3 interceptors are not cleanly integrated in their AOP framework. I personnaly consider that we have enough technology around to make it far better, and far more consistent with AOP. Given the impact that AOP will continue to have, ones would better figuring out how to do that now with EJB 3 - assuming that EJB 3 succeeds. AspectWerkz extensible container value proposal to EJB 3 interceptors I decided to give it a try with the AspectWerkz extensible container. As Jonas Bonér described it in a TSS article, AspectWerkz can be considered as a generic AOP runtime platform, in which ones can hook in any kind of AOP/AOP like programming model. AspectWerkz aspects are one, AspectJ aspects are another, and we have implementations for Spring AOP and AOP Alliance aspects. An AspectModel answers three essential properties: what is the life cycle of the aspect, how to invoke it and how the programming model exposes the closure with wich the user proceed. It happens that the EJB 3 interceptor can be seen as a very simple AOP programming model in two ways:
public interface javax.ejb.InvocationContext { public Object getBean(); public Method getMethod(); public Object[] getParameters(); public void setParameters(Object[]); public Context getEJBContext(); public java.util.Map getContextData(); public Object proceed() throws Exception; } The value proposal I am bringing here with the AspectWerkz extensible container is the following:
AspectWerkz extensible container details To better understand how things will looks like, you need some background in AspectWerkz: The first key part is "org.codehaus.aspectwerkz.transform.inlining.spi.AspectModel". We will provide two implementations of it. One for interceptor class (the interceptor class is the aspect), and one for bean as interceptor (the bean is the aspect). The second key part is in integrating the EJB interceptor closure "InvocationContext" that defines the "proceed()" method with the more general idea of "Joinpoint.proceed()" of the runtime. In short, this means that the interceptor will be entirely part of the aspect chain, and if there is a transaction aspect to handle EJB transaction boundaries, they will share the very same chain - as ones expects - thus making it easy for the implementation to organize precedence rules (as define in section 3.5.1 of JSR 220 for example). As we already wrote this transaction aspect for EJB 3 in a previous AspectWerkz TSS tutorial, that's rather a simple idea and basic requirement. The runtime will take care of aggregating the models depending on which aspect apply to which join point, each aspect having its specific AspectModel and each AspectModel implementation being responsible for generating what it needs: // a closure to deal with the join points // ie models org.codehaus.aspectwerkz.joinpoint.JoinPoint interface // to fit with the transaction aspect, or any other aspect // and javax.ejb.InvocationContext to fit EJB3 interceptors class jitEJB3Closure implements JoinPoint, InvocationContex { // the closure hosts state, optimization makes it more complex than that // the target of the join point ie the intercepted ejb // which is also third aspect: remember the bean is the aspect ! private MyEJBIsTheAspect target; // first aspect in the chain, statically compiled private TXAspect aspect_0; // second aspect: the interceptor class private MyInterceptor aspect_1; // details skipped that makes those fields initalized as they should. // as defined in javax.ejb.InvocationContext public Method getMethod() {...} // as defined in JoinPoint public Class getTargetClass {...} // other methods as per each aspect model affecting the join point ... // generic proceed() method as defined in JoinPoint and InvocationContext public Object proceed() throws Throwable { // sort of a loop to proceed with the advice chain // case first round: TX aspect // TX aspect // as defined in the "AspectWerkzAspectModel implements AspectModel" aspect_0.manageTX(this) // "this" is considered as the JoinPoint instance ... // case second round: interceptor class // as defined in the yet to be written // "EJBInterceptorModel implements AspectModel" aspect_1.someNameOfYourChoice(this) // "this" is considered as the InvocationContext instance ... // case third round: the bean is the aspect // as defined in the yet to be written // "EJBIsTheAspectModel implements AspectModel" target.interceptMyself(this) // "this" is considered as the InvocationContext instance ... } } That may sound a bit of gory details, but that's actually simple once you get the idea of this closure acting for multiple AspectModel in mind. The code is actually 15O lines for the interceptor class model (EJBInterceptorModel) and 25 lines for the bean is the aspect model (EJBIsTheAspectModel) thanks to some inheritance. It mainly deals with
What about the deployment ? I presented the runtime, but there is an interesting topic as well: the deployment. AspectWerkz pioneered the aop.xml to define which aspects are affecting the system. Obviously, we don't want that for EJB interceptor. We need to use the AspectWerkz API to register what we need in the system. The interesting concept here is that we will transform what the EJB specification defines thanks to annotation (@javax.ejb.Interceptor, @javax.ejb.Interceptors, @javax.ejb.AroundInvoke) in real AOP pointcut that the AOP container understand. That is where a vendor may easily hook in an extension (ie introduce a new annotation for example) to refine the interceptor class life cycle, or to introduce a pointcut. I am defining a class that will expose an API that will hide the AOP registration from the user or from the other parts of our spec. implementation. public class EJBInterceptorDeployer { public static void deploy(String ejbClassName, ClassLoader loader) { // step 1 - interceptor class // read the ejbClass @Interceptor and @Interceptors class level annotation // for each interceptor class name found as value of those annotation // get the @AroundInvoke method information // deploy an aspect // using the EJBInterceptorModel // to the pointcut : "execution(!@javax.ejb.AroundInvoke !static * " + ejbClassName + ".*(..))" // // step 2 - @AroundInvoke method of the bean itself // find the @AroundInvoke method if any // deploy an aspect // using the EJBIsTheAspectModel // to the same pointcut } // method making use of AspectWerkz aspect deployment API } The thing to note about the deployer is that it is not using reflection. It is f.e. using our BackPort175 implementation to read the EJB annotations from the bytecode. If we were not doing so, we would trigger the EJB class loading while our system is not yet defined, and the weaver would not be able to do the job when using load-time weaving approaches. What about running the system ? In the sample application, I am deploying the EJB using the EJBInterceptorDeployer presented in the previous section. In a full blown container I would hook the call to it when the application or EJB deployer would register the EJB in the system. The nice thing is that I can run my EJB and still have the interceptors when running as a standalone application by using AspectWerkz load time weaving, and a simple static block to declare which class is an EJB. The sample application can be run with (for ones familiar with AspectWerkz, there is no aop.xml..) java -javaagent:lib\aspectwerkz-jdk5-2.0.jar test.ejb3.Sample public class Sample { static { EJBInterceptorDeployer.deploy("test.ejb3.MyEJBIsTheAspect", Sample.class.getClassLoader()); } public static void main(String args[]) throws Throwable { // some one would do a lookup or inject for that when in the container MyEJBIsTheAspect myEjb = new MyEJBIsTheAspect(); System.out.println("Calling the EJB"); int i = myEjb.businessSum(1, 2); System.out.println("got : " + i); } } AW EJB3 - Deploying test.ejb3.MyInterceptor for test.ejb3.MyEJBIsTheAspect AW EJB3 - Deploying test.ejb3.MyEJBIsTheAspect for test.ejb3.MyEJBIsTheAspect Calling the EJB --> MyInterceptor.interceptStandalone method: public int test.ejb3.MyEJBIsTheAspect.businessSum(int,int) args[0]: 1 args[1]: 2 --> MyEJBIsTheAspect.interceptMySelf method: public int test.ejb3.MyEJBIsTheAspect.businessSum(int,int) args[0]: 1 args[1]: 2 got : 3 Conclusion Though at first glance I found the interceptor part of the EJB 3 specification fairly odd, and was a bit sad that some important AOP concepts like precedence and aspect life cycle, as well as expressiveness of the pointcut language are completely sacrified, I must admit that it might help ones familiarize with AOP concepts. It took 1h to implement the EJB 3 interceptor spec and have it perfectly integrated with AOP - unlike so far exposed EJB 3 previews by JBoss and Oracle (though those are actual preview, while this article is more a technology focus and positionning). It took 15 minutes more to implement a pointcut extension thru a new @org.codehaus.aspectwerkz.ejb3.AroundInvokeAOP annotation, that allows me to reuse the expressiveness of the pointcuts ie a vendor extension: public class MyInterceptor { @AroundInvoke @AroundInvokeAOP("execution(* *.businessSum(..))") public Object interceptStandalone(InvocationContext ctx) throws Exception { System.out.println("--> MyInterceptor.interceptStandalone"); System.out.println(" method: " + ctx.getMethod()); for (int i = 0; i < ctx.getParameters().length; i++) { Object o = ctx.getParameters()[i]; System.out.println(" args["+i+"]: " + o); } return ctx.proceed(); } } Some more time would allow me to have further features, like f.e. supporting cflow and args pointcut, so that an interceptor may look like an advice as it looks like in AspectWerkz aspects, with static access to intercepted method arguments etc (ie no boxing in an Object[] array). A next iteration would be to integrate with the AspectWerkz hot deployment feature, and this would bring in hot deployment of EJB interceptors at no cost - while still having everything statically compiled for the runtime to perform at its best. This implementation may seems at first glance full of details, and perhaps like a hammer to address a simple part of the spec. I don't think so. I think it address the very crucial point that so far everyone has skept - including JBoss folks (that have a foot in the AOP trench): it integrates seamlessly with the aspects and AOP concepts, and actually allow advanced user to apply more advanced AOP concepts as well ie bridging the gap between a commercial concept and needs (EJB interception) and a sound concept backed by years of research (AOP and cross-cutting). So which vendor will be the first to have its EJB play well with AOP : like applying an interceptor thru a real pointcut, defining programmatic precedence etc, dealing with cflow and hotdeployment of interceptors, and all that with an implementation that can scale ? Want the source code ? This one is part of AspectWerkz CVS. It can be browsed from here. Side note: my feedback on the specication: There are some odd things in the 3.5 section of the JSR-220 that I have spot:
Fortunately, the implementation exposed in this article addresses those issues nicely from a vendor specific extension perspective. Note: These are my own thoughts and not of my employer .. |