This article discusses CDI based AOP in a tutorial
format. CDI is the Java standard
for dependency injection (DI) 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 already the foundation of many
Java frameworks.
CDI is a foundational aspect of Java
EE 6. It is or will be shortly supported by Caucho's Resin Java Application
Server, Java EE WebProfile certified, IBM's WebSphere, Oracle's
Glassfish, Red Hat's JBoss and many more application servers. CDI is similar to core Spring and
Guice frameworks. Like JPA did for ORM, CDI simplifies and sanitizes the API
for DI and AOP. If you have worked with Spring or Guice, you will find CDI easy
to use and easy to learn. If you are new to AOP, then CDI is an easy on ramp
for picking up AOP quickly, as it uses a small subset of what AOP provides. CDI
based AOP is simpler to use and learn.
One can argue that
CDI only implements a small part of AOP—method interception. While this is a
small part of what AOP has to offer, it is also the part that most developers
use.
CDI can be used
standalone and can be embedded into any application.
Here is the source
code for this tutorial, and instructions for use. It is no accident that this tutorial follows many of the same
examples in the Spring 2.5 AOP tutorial written three years ago.
It will be
interesting to compare and contrast the examples in this tutorial with the one
written three years ago for Spring based AOP.
Design goals of
this tutorial
This tutorial is
meant to be a description and explanation of AOP in CDI without the clutter of
EJB 3.1 or JSF. There are already plenty of tutorials that cover EJB 3.1 and
JSF (and CDI).
We believe that CDI has merit on its
own outside of the EJB and JSF space. This tutorial only covers CDI. Repeat
there is no JSF 2 or EJB 3.1 in this tutorial. There are plenty of articles and
tutorials that cover using CDI as part of a larger JEE 6
application. This tutorial is not that. This tutorial series is CDI and only CDI.
This tutorial only
has full, complete code examples with source code you can download and try out
on your own. There are no code snippets where you can't figure out where in the
code you are suppose to be.
So far these
tutorials have been well recieved and we got a lot of feedback. There appears
to be a lot of interest in the CDI standard. Thanks for reading and thanks for
your comments and participation so far.
AOP Basics
For some, AOP seems
like voodoo magic. For others, AOP seems like a cure-all. For now, let's just
say that AOP is a tool that you want in your developer toolbox. It can make
seemingly impossible things easy. Aagin, when we talk about AOP in CDI, we are
really talking about interception which is a small but very useful part of AOP.
For brevity, I am going to refer to interception as AOP.
The first time that
I used AOP was with Spring's transaction management support. I did not realize
I was using AOP. I just knew Spring could apply EJB-style declarative
transaction management to POJOs. It was probably three to six months before I
realized that I was using was Spring's AOP support. The Spring framework truly
brought AOP out of the esoteric closet into the main stream light of day. CDI
brings these concepts into the JSR standards where other Java standards can
build on top of CDI.
You can think of
AOP as a way to apply services (called cross-cutting concerns) to objects. AOP
encompasses more than this, but this is where it gets used mostly in the main
stream.
I've using AOP to
apply caching services, transaction management, resource management, etc. to
any number of objects in an application. I am currently working with a team of
folks on the CDI implementation for the revived JSR-107 JCache. AOP is not a
panacea, but it certainly fits a lot of otherwise difficult use cases.
You can think of AOP as a dynamic
decorator design pattern. The decorator pattern allows additional behavior to
be added to an existing class by wrapping the original class and duplicating
its interface and then delegating to the original. See this article decorator
pattern for more detail about the decorator
design pattern. (Notice in addition to supporting AOP style interception CDI
also supports actual decorators, which are not covered in this article.)
Sample application
revisited
For this introduction to AOP, let's
take a simple example, let's apply security services to our Automated Teller
Machine example from the first the first in this series.
Let's say when a user logs into a
system that a SecurityToken is created that carries the user's credentials and before methods on
objects get invoked, we want to check to see if the user has credentials to
invoke these methods. For review, let's look at the AutomatedTellerMachine interface.
Code Listing: AutomatedTellerMachine interface
view source
print?
01.package org.cdi.advocacy;
02.
03.import java.math.BigDecimal;
04.
05.public interface AutomatedTellerMachine {
06.
07.public abstract void deposit(BigDecimal bd);
08.
09.public abstract void withdraw(BigDecimal bd);
10.
11.}
In a web application, you could write
a ServletFilter, that stored this SecurityToken in HttpSessionand then on every
request retrieved the token from Session and put it into a ThreadLocal variable where it could be accessed from a SecurityService that you could implement.
Perhaps the objects that needed the SecurityService could access it as
follows:
Code Listing: AutomatedTellerMachineImpl implementing security without AOP
Code Listing: AutomatedTellerMachineImpl implementing security without AOP
view source
print?
01.public void deposit(BigDecimal bd) {
02./* If the user is
not logged in, don't let them use this method */
03.if(!securityManager.isLoggedIn()){
04.throw new SecurityViolationException();
05.}
06./* Only proceed if
the current user is allowed. */
07.
08.if (!securityManager.isAllowed("AutomatedTellerMachine", operationName)){
09.throw new SecurityViolationException();
10.}
11....
12.
13.transport.communicateWithBank(...);
14.}
In our ATM example,
the above might work out well, but imagine a system with thousands of classes
that needed security. Now imagine, the way we check to see if a user is
"logged in" changed. If we put this code into every method that
needed security, then we could possibly have to change this a thousand times if
we changed the way we checked to see if a user was logged in.
What we want to do instead is to use
CDI to create a decorated version of theAutomateTellerMachineImpl bean. The decorated
version would add the additional behavior to theAutomateTellerMachineImpl object without changing the actual implementation of theAutomateTellerMachineImpl. In AOP speak,
this concept is called a cross-cutting concern. A cross-cutting concern is a
concern that crosses the boundry of many objects.
CDI does this by creating what is
called an AOP proxy. An AOP proxy is like a dynamic decorator. Underneath the covers
CDI can generate a class at runtime (the AOP proxy) that has the same interface
as our AutomatedTellerMachine. The AOP proxy
wraps our existing atm object and provides additional behavior by delegating to
a list of method interceptors. The method interceptors provide the additional
behavior and are similar to ServletFilters but for methods
instead of requests.
Diagrams of CDI AOP
support
Thus before we added CDI AOP, our atm example was like Figure 1.
Figure 1: Before AOP advice
After we added AOP support, we now get an AOP proxy that applies the securityAdvice to the atm as show in figure 2.
Figure 2: After AOP advice
Let's actually look
at the code for this example.
For this example, we will use a
simplified SecurityToken that gets stored into a ThreadLocalvariable, but one
could imagine one that was populated with data from a database or an LDAP
server or some other source of authentication and authorization.
Here is the SecurityToken,
which gets stored into a ThreadLocal variable,
for this example:
SecurityToken.java Gets stored in ThreadLocal
SecurityToken.java Gets stored in ThreadLocal
view source
print?
01.package org.cdi.advocacy.security;
02.
03./**
04.* @author Richard
Hightower
05.*
06.*/
07.public class SecurityToken {
08.
09.private boolean allowed;
10.private String userName;
11.
12.public SecurityToken() {
13.
14.}
15.
16.
17.
18.public SecurityToken(boolean allowed, String
userName) {
19.super();
20.this.allowed = allowed;
21.this.userName = userName;
22.}
23.
24.
25.
26.public boolean isAllowed(String object, String
methodName) {
27.return allowed;
28.}
29.
30.
31./**
32.* @return Returns
the allowed.
33.*/
34.public boolean isAllowed() {
36.}
37./**
38.* @param allowed
The allowed to set.
39.*/
40.public void setAllowed(boolean allowed) {
41.this.allowed = allowed;
42.}
43./**
44.* @return Returns
the userName.
45.*/
46.public String getUserName() {
47.return userName;
48.}
49./**
50.* @param userName
The userName to set.
51.*/
52.public void setUserName(String userName) {
53.this.userName = userName;
54.}
55.}
The SecurityService stores
the SecurityToken into
the ThreadLocal variable,
and then delegates to it to see if the current user has access to perform the
current operation on the current object as follows:
SecurityService.java Service
SecurityService.java Service
view source
print?
01.package org.cdi.advocacy.security;
02.
03.
04.public class SecurityService {
05.
06.private static ThreadLocal<SecurityToken>
currentToken = newThreadLocal<SecurityToken>();
07.
08.public static void placeSecurityToken(SecurityToken token){
09.currentToken.set(token);
10.}
11.
12.public static void clearSecuirtyToken(){
13.currentToken.set(null);
14.}
15.
16.public boolean isLoggedIn(){
17.SecurityToken token =
currentToken.get();
18.return token!=null;
19.}
20.
21.public boolean isAllowed(String object, String
method){
22.SecurityToken token =
currentToken.get();
23.return token.isAllowed();
24.}
25.
26.public String getCurrentUserName(){
27.SecurityToken token =
currentToken.get();
28.if (token!=null){
29.return token.getUserName();
30.}else {
31.return "Unknown";
32.}
33.}
34.
35.}
The SecurityService will throw a SecurityViolationException if a user is not allowed to access a resource. SecurityViolationException is just a simple exception for this example.
SecurityViolationException.java Exception
view source
print?
01.package com.arcmind.springquickstart.security;
02.
03./**
04.* @author Richard
Hightower
05.*
06.*/
07.public class SecurityViolationException extends RuntimeException {
08.
09./**
10.*
11.*/
12.private static final long serialVersionUID = 1L;
13.
14.}
To remove the security code out of
the AutomatedTellerMachineImpl class and any other
class that needs security, we will write an Aspect in CDI to intercept calls
and perform security checks before the method call. To do this we will create a
method interceptor (known is AOP speak as an advice) and intercept method calls
on the atm object.
Here is the SecurityAdvice class which will intercept calls on the AutomatedTellerMachineImplclass.
SecurityAdvice
view source
print?
01.package org.cdi.advocacy.security;
02.
03.
04.
05.import javax.inject.Inject;
06.import javax.interceptor.AroundInvoke;
07.import javax.interceptor.Interceptor;
08.import javax.interceptor.InvocationContext;
09.
10./**
11.* @author Richard
Hightower
12.*/
13.@Secure @Interceptor
14.public class SecurityAdvice {
15.
16.@Inject
17.private SecurityService securityManager;
18.
19.@AroundInvoke
20.public Object checkSecurity(InvocationContext joinPoint) throwsException {
21.
22.System.out.println("In
SecurityAdvice");
23.
24./* If the user is
not logged in, don't let them use this method */
25.if(!securityManager.isLoggedIn()){
26.throw new SecurityViolationException();
27.}
28.
29./* Get the name of
the method being invoked. */
30.String operationName
= joinPoint.getMethod().getName();
31./* Get the name of
the object being invoked. */
32.String objectName =
joinPoint.getTarget().getClass().getName();
33.
34.
35./*
36.* Invoke the method
or next Interceptor in the list,
37.* if the current
user is allowed.
38.*/
39.if (!securityManager.isAllowed(objectName, operationName)){
40.throw new SecurityViolationException();
41.}
42.
43.return joinPoint.proceed();
44.}
45.}
Notice that we annotate the SecuirtyAdvice class with an @Secure annotation. The @Secureannotation is an @InterceptorBinding. We use it to
denote both the interceptor and the classes it intercepts. More on this later.
No comments:
Post a Comment