Rick has posted 25 posts at DZone. You can read more from them at their website. View Full User Profile

CDI AOP Tutorial: Java Standard Method Interception Tutorial - Java EE

05.25.2011
| 43452 views |
  • submit to reddit

Before we added AOP support when we looked up the atm, we looked up the object directly as shown in figure 1, now that we applied AOP when we look up the object we get what is in figure 2. When we look up the atm in the application context, we get the AOP proxy that applies the decoration (advice, method interceptor) to the atm target by wrapping the target and delegating to it after it invokes the series of method interceptors.



Victroy lap


The last code listing works just like you think. If you use simulateLogin, atm.deposit does not throw a SecurityException. If you use simulateNoAccess, it does throw a SecurityException. Now let's weave in a few more "Aspects" to the mix to drive some points home and to show how interception works with multiple interceptors.

I will go quicker this time.



LoggingInterceptor
package org.cdi.advocacy;

import java.util.Arrays;
import java.util.logging.Logger;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;


@Logable @Interceptor
public class LoggingInterceptor {

    @AroundInvoke 
    public Object log(InvocationContext ctx) throws Exception {
    	System.out.println("In LoggingInterceptor");

        Logger logger = Logger.getLogger(ctx.getTarget().getClass().getName());
        logger.info("before call to " + ctx.getMethod() + " with args " + Arrays.toString(ctx.getParameters()));
        Object returnMe = ctx.proceed();
        logger.info("after call to " + ctx.getMethod() + " returned " + returnMe);
        return returnMe;
    }
}

Now we need to define the Logable interceptor binding annotation as follows:
package org.cdi.advocacy;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import javax.interceptor.InterceptorBinding;


@InterceptorBinding 
@Retention(RUNTIME) @Target({TYPE, METHOD})
public @interface Logable {

}

Now to use it we just mark the methods where we want this logging.



AutomatedTellerMachineImpl.java using Logable
package org.cdi.advocacy;

...

@Secure
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {

...

    @Logable
    public void deposit(BigDecimal bd) {
        System.out.println("deposit called");
        transport.communicateWithBank(null);

    }

    public void withdraw(BigDecimal bd) {
        System.out.println("withdraw called");

        transport.communicateWithBank(null);

    }

}

Notice that we use the @Secure at the class level which will applies the security interceptor to every mehtod in the AutomatedTellerMachineImpl. But, we use @Logable only on the deposit method which applies it, you guessed it, only on the deposit method.

Now you have to add this interceptor to the beans.xml:



META-INF/beans.xml
<beans 
...
    <interceptors>
        <class>org.cdi.advocacy.LoggingInterceptor</class>
        <class>org.cdi.advocacy.security.SecurityAdvice</class>
    </interceptors>
</beans>
When we run this again, we get something like this in our console output:
May 15, 2011 6:46:22 PM org.cdi.advocacy.LoggingInterceptor log
INFO: before call to public void org.cdi.advocacy.AutomatedTellerMachineImpl.deposit(java.math.BigDecimal) with args [1.00]
May 15, 2011 6:46:22 PM org.cdi.advocacy.LoggingInterceptor log
INFO: after call to public void org.cdi.advocacy.AutomatedTellerMachineImpl.deposit(java.math.BigDecimal) returned null
Notice that the order of interceptors in the beans.xml file determines the order of execution in the code. (I added a println to each interceptor just to show the ordering.) When we run this, we get the following output.

Output:
In LoggingInterceptor
In SecurityAdvice
If we switch the order in the beans.xml file, we will get a different order in the console output.

META-INF/beans.xml
<beans 
...
    <interceptors>
      <class>org.cdi.advocacy.security.SecurityAdvice</class>
      <class>org.cdi.advocacy.LoggingInterceptor</class>
    </interceptors>
</beans>
In SecurityAdvice
In LoggingInterceptor
This is important as many interceptors can be applied. You have one place to set the order.

Conclusion


AOP is neither a cure all or voodoo magic, but a powerful tool that needs to be in your bag of tricks. The Spring framework has brought AOP to the main stream masses and Spring 2.5/3.x has simplified using AOP. CDI brings AOP and DI into the standard's bodies where it can get further mainstreamed, refined and become part of future Java standards like JCache, Java EE 6 and Java EE 7.

You can use Spring CDI to apply services (called cross-cutting concerns) to objects using AOP's interception model. AOP need not seem like a foreign concept as it is merely a more flexible version of the decorator design pattern. With AOP you can add additional behavior to an existing class without writing a lot of wrapper code. This can be a real time saver when you have a use case where you need to apply a cross cutting concern to a slew of classes.

To reiterate...

CDI is the Java standard for dependency injection and interception (AOP). It is evident from the popularity of DI and AOP that Java needs to address DI and AOP so that it can build other standards on top of it. DI and AOP are the foundation of many Java frameworks. I hope you share my excitement of CDI as a basis for other JSRs, Java frameworks and standards.

CDI is a foundational aspect of Java EE 6. It is or will be shortly supported by Caucho's Resin, IBM's WebSphere, Oracle's Glassfish, Red Hat's JBoss and many more application servers. CDI is similar to core Spring and Guice frameworks. However CDI is a general purpose framework that can be used outside of JEE 6.

CDI simplifies and sanitizes the API for DI and AOP. I find that working with CDI based AOP is easier and covers the most common use cases. CDI is a rethink on how to do dependency injection and AOP (interception really). It simplifies it. It reduces it. It gets rid of legacy, outdated ideas.

CDI is to Spring and Guice what JPA is to Hibernate, and Toplink. CDI will co-exist with Spring and Guice. There are plugins to make them interoperate nicely (more on these shortly).

This is just a brief taste. There is more to come.

Resources




About the Author


This article was written with CDI advocacy in mind by Rick Hightower with some collaboration from others.

Rick Hightower has worked as a CTO, Director of Development and a Developer for the last 20 years. He has been involved with J2EE since its inception. He worked at an EJB container company in 1999. He has been working with Java since 1996, and writing code professionally since 1990. Rick was an early Spring enthusiast. Rick enjoys bouncing back and forth between C, Python, Groovy and Java development. Although not a fan of EJB 3, Rick is a big fan of the potential of CDI and thinks that EJB 3.1 has come a lot closer to the mark. Rick Hightower is CTO of Mammatus and is an expert on Java and Cloud Computing.

There are 18 code listings in this article
Published at DZone with permission of its author, Rick Hightower.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Jonathan Fisher replied on Wed, 2011/05/25 - 1:52pm

A note, only WebSphere v8 supports CDI, which just recently (mid April I think) went "GA" public release.

Reza Rahman replied on Thu, 2011/05/26 - 3:11pm

Wow - that's a pretty fast release for WebSphere :-).

Krzysztof Kowalczyk replied on Fri, 2011/05/27 - 5:55am

I would argue that CDI / EJB interceptor implementation is not an proper AOP. At least some behaviors are counterintuitive. When we have:
class InterceptedBean{
  @MethodReturnOne <-- interceptor, always returns 1
  Integer x(){ return 2L; }
  Integer y(){ return x(); }
}
x() would return 1 when called outside of the bean, but when called inside the bean -> y() will return 2L. So interceptor is not applied on all method invocations. In CDI it's quite easy to create scope-cached functions for instance, what is really handy considering JSF bindings behavior. Unfortunately in practice private or protected functions cannot be cached in that way and public functions cannot be reused in the same bean. That's annoying.

Reza Rahman replied on Fri, 2011/05/27 - 4:45pm in response to: Krzysztof Kowalczyk

The trivial fix would be:

class InterceptedBean{
  @MethodReturnOne <-- interceptor, always returns 1
  Integer x(){ return 2L; }
  @MethodReturnOne <-- interceptor, always returns 1
  Integer y(){ return x(); }
}

The big benefit to using proxying vis-a-vis AspectJ style byte-code injection is class-loader simplicity/performance. CDI interceptors are also way easier than arcane AspectJ point-cut syntax.

Krzysztof Kowalczyk replied on Sat, 2011/05/28 - 12:18pm in response to: Reza Rahman

This is never a fix! In real world there would be some logic before calling x() e.g. random() * x(). If one implement "arround" behavior with compile time bytecode modification or load time subclass creation, the example would work. So it probably means that interceptor implementation is more complex, thus slower - I did not checked that, yet.

Reza Rahman replied on Sat, 2011/05/28 - 8:22pm in response to: Krzysztof Kowalczyk

I think you are overstating the issue a bit.

Even in the Spring world, AspectJ use is very rare. Take a look at this graph. As you can see, AspectJ use is quite insigficant, even in  the Spring world.

If the issue you noted is really that critical, you'd see more AspectJ use, right :-)?

Krzysztof Kowalczyk replied on Sun, 2011/05/29 - 8:21am in response to: Reza Rahman

This has nothing to do with AspectJ, but with implementation of AOP in JEE. AspectJ is a quite sophisticated and mature solution that is not needed by most developers. Simple arround invoke aop implementation is enough for most of users. JEE gives such solution but with bad implementation IMHO. This implementation of aop may cause many hard to find errors in near future. What if in a financial system someone use interceptor and one of the methods is not transactional(or recorded, or logged, or whatever) because one called the intercepted method from other method in the same class? It is possible and really hard to find. So now I have to explain to developers: sorry guys, but your methods will behave differently depending in which class they are called... and they think that aop is hard and strange. I guess that other AOP implementation will behave correctly, including Spring AOP even without AspectJ, but did not checked that. CDI / EJB is a standard, Glassfish is a reference implementation of the standard. As for today they behave in a non-OOP way (in my opinion this behavior is breaking Liskov substitution principle, as one could see an intercepted class as a subclass of that type) and I think it is an error. If it is a design decision, then I'm would like to know it's rationale.

Reza Rahman replied on Mon, 2011/05/30 - 3:07pm

I thought I was being very clear about the rationale, but I'll try again for you:

CDI is based on proxying, exactly like Spring AOP (so it too will have the issue you are concerned about). CDI (and Java EE in general) does not mandate direct byte-code manulation/code-generation like AspectJ does (that is the only way of implementing what you are looking for -- that is interception at the class instance level rather than injection proxy level). This is done primarily because of class-loading/deployment/implemention simplicity as well as performance (manipulating byte code/generating code is a lot more performance intensive rather than simple Java proxying via sub-classing).

CDI/Java EE is crystal clear that interceptors are only applied via injected proxy -- it is not applied when making a self-call or a call on a direct class instance (likely created via "new" instead of injection). Just like anyone using Spring AOP, anyone using CDI interceptors should be aware of this before the fact. If interceptors are really needed during self-call the solution is quite simple -- do a self-injection like this:

class InterceptedBean {
  @Inject
  private InterceptedBean self;
  @MethodReturnOne <-- interceptor, always returns 1
  Integer x(){ return 2L; }
  Integer y(){ return self.x(); }
}

As I said, in practice this case is very rare which is why Spring AOP and CDI interceptors are sufficient for most cases as compared to full-blown AspectJ. For example, I have not come across it once in ten years of real-world enterprise developement and I have seen a self-call example only in the Java EE compatibility test kit (and was surprised when I saw it).

Hope this helps.

Krzysztof Kowalczyk replied on Mon, 2011/05/30 - 5:29pm

Thanks for your opinion, but I still can't see any rationale for such behavior - it still breaks OOP principles, for what reason? (or am I missing something?).

You claim that you never have seen such use case, so you say that you never invoke a method from another method? (joke ;) ) CDI is general purpose solution and should not constrain itself to procedural style code.

You mentioned subclassing. If we used exactly such style of aop, it would behave exactly as I expect it to behave. The virtual dispatch would be fulfilled, it would not change behavior of not managed instances and it is also one of fastest(runtime) solutions . It is semantically equivalent to a class:

//Proxy
class ReturnOne extends InterceptedBean{
 Integer x(){return 1; }
}


This would cause y() to return 1 (because x is a virtual method)! It can be implemented at injection proxy level as subclass, the original class is not changed in any way. So again, this has nothing to do with AspectJ way. Subclassing is actually easiest way to implement aop afaik, but behaves differently than EJB interceptors.
The behavior of EJB interceptor points rather to a "subclass and delegate" implementation style, or proxy with dynamic filters on method invocations handlers, both are slower in theory, but in practice it should not make a big difference. So I cannot agree with your comments about performance issues.

Reza Rahman replied on Tue, 2011/05/31 - 3:49am in response to: Krzysztof Kowalczyk

Spring AOP and CDI interceptors are good enough for the vast majority of real world use cases and both AOP system are clear that they require interception only on injected proxy calls. For the remaining cases, it is possible to use AspectJ with CDI, Java EE or pretty much anything else.

Now, I believe I've said everything that needs to be said on this. I don't have the time or patience to explain to you why the imaginary code you have above makes little sense in a real-world proxy-based container. You'll have to find that out on your own by looking at Weld, OpenWebBeans, CanDI or Spring AOP. The way I see it, I have paid this more attention than most people would.

If you really feel that strongly about it, feel free to bring it to the attention of the CDI 1.1 EG that is just currently underway: http://www.jcp.org/en/jsr/proposalDetails?id=346. The JCP page references a JIRA instance for CDI/Weld that you can also use.

Hope it helps.

Krzysztof Kowalczyk replied on Tue, 2011/05/31 - 3:16am

Thanks for discussion. When I've asked on weld forum if this behavior is correct, there was no answer. I just could not agree on incorrect statements like the one with "proxy via subclassing" behavior.
Btw. I've checked JBoss interceptor implementation. It is using load time bytecode manipulation. So CDI does mandate bytecode manipulation, whitout it one cannot create dynamic proxy for classes, only for interfaces. It also uses "delegate" style proxy. Spring AOP does the same, but it actually call the self-invocation behavior as "issue" and is explicit about this behavior in documentation. Thought you'd like to know.
Regards, Krzysztof Kowalczyk

Reza Rahman replied on Tue, 2011/05/31 - 3:54am

I'll tell you what -- I'll go ahead and enter the issue for you, but I have a good guess right now what the outcome will be...

Krzysztof Kowalczyk replied on Tue, 2011/05/31 - 6:22am

The more I investigate this, the more I'm aware that it is a design decision, just can't find explanation for it. It just occured to me that it might be implemented that way because of remote beans.

Krzysztof Kowalczyk replied on Tue, 2011/05/31 - 6:37am

Ok, I finally have satisfying answer. The delegate style aop is needed (only reasonable way to do it) when we have a remote bean and we want to apply aop on the client side. Having two different behaviors for the same thing would be really bad so local interceptors works the same. Thanks again for the discussion.

Reza Rahman replied on Tue, 2011/05/31 - 1:00pm in response to: Krzysztof Kowalczyk

I think you should look at real world implementations instead of simply thinking in a void of how this should be. There is more to any of this than unfortunately readily meets the eye. I mentioned some of those concerns already -- implementation (both simplicity and flexibility)/class-loading/deployment/code-generation/byte-code manipulation/proxying/performance/overall container architecture/innovation and so on.

However, as I promised, I'll bring up the issue for you anyway.

Krzysztof Kowalczyk replied on Wed, 2011/06/01 - 1:28pm

The issue is already filled:
https://issues.jboss.org/browse/CDI-44
https://issues.jboss.org/browse/CDI-74

A bit similar discussion:
http://lists.jboss.org/pipermail/weld-dev/2010-May/002506.html
http://lists.jboss.org/pipermail/weld-dev/2010-May/002513.html (I like the example)
I did not found those links earlier. The implementation will be changed, some things are not yet specified.
I do check real world implementations, I know differences between some of them, especially considering performance and some issues caused by them (as in the case of subclass and delegate (proxy) pattern of aop), that's why it caught my attention.

Reza Rahman replied on Sat, 2011/06/11 - 7:45pm in response to: Krzysztof Kowalczyk

Good -- I'll follow it up on the EG and reference the above Weld issues.

Carla Brian replied on Tue, 2012/04/03 - 12:53pm

Thank you for posting this one. I am new to this topic. Good thing I stumbled upon in your post. - Paul E. Perito

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.