Mittwoch Nov 11, 2009
A sample of Spring AOP usage
In the last couple of years I've come across this problem several times: you've created a business application for a client with various business / domain objects, all is well and then the client decides they need to somehow be able to group various of those objects together and then they need to be able to set global filters to make sure you only see and work with objects belonging to a certain grouping. Now there is two problems here:
- The grouping isn't restricted to one of your domain objects. You need
to be able to throw all your various domain objects into those groups, i.e you
can create a group that consists of people and things and emails and boxes and
…
Unfortunately that isn't a trivial thing to model in your classical rdbms/hibernate persistence application. Relational databases aren't particularly well suited to store relations from one thing (table) to all kinds of other things (tables) while still keeping it easy to query those relationships and also maintaining relational integrity.
Well, I haven't really solved this problem. I recently implemented a simple solution that is good enough for my project albeit not very elegant I think - if someone has solved this in a better way, please let me know or point me to a good resource. Anyway I have solved it by creating link objects for each thing that needs to be grouped. The link objects form a class hierarchy that ist mapped onto a single table. That allows me to quite easily query those relationships at the cost of the link table having to have quite a few nullable columns - which C.J.Date wouldn't approve of I guess - The second problem is that you then depending on some contextual information in the user session of your application you need to filter all listings of your domain objects. You could of course have some if or switch statements in your controller logic but that smells. It also means that you need to touch potentially lots and lots of places in your code if anything with your grouping changes.
<aop:aspectj-autoproxy />
(You need to configure the aop schema obviously)
I have then create an Aspect class like this
…
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
@Aspect
public class ScopeAspect {
final static Logger logger = LoggerFactory.getLogger(ScopeAspect.class);
@Pointcut("within(dao..*)")
public void inDataAccessLayer() {}
@Pointcut("execution(public * scoped*(..))")
public void scoped(){}
@Around(value = "inDataAccessLayer() && scoped() && target(target)")
public Object addScopeIfNeeded(ProceedingJoinPoint joinPoint, GenericDao extends Entity, Long> target) throws Throwable
{
Scope scope = ScopeUtils.getCurrentScope();
if(scope != null) {
logger.debug("rewriting dao method to use scope!");
// gather the argument values and types
Object[] originalArgs = joinPoint.getArgs();
Object[] modifiedArgs = new Object[originalArgs.length + 1];
modifiedArgs[0] = scope;
// prepend the 'scope' arg
System.arraycopy(originalArgs, 0, modifiedArgs, 1, originalArgs.length);
// calculate the new argument type list
Class>[] modifiedArgTypes = new Class[modifiedArgs.length];
modifiedArgTypes[0] = Project.class;
Class>[] originalArgTypes = ((CodeSignature)joinPoint.getSignature()).getParameterTypes();
System.arraycopy(originalArgTypes, 0, modifiedArgTypes, 1, originalArgTypes.length);
// find the method that has the same signature apart from the preceding scope type argument
Method m = target.getClass().getMethod(joinPoint.getSignature().getName(), modifiedArgTypes);
return m.invoke(target, modifiedArgs);
}
else
{
return joinPoint.proceed();
}
}
}
So what does that do? This aspect is now called for all methods whose names
starts with 'scoped' in Classes living in the 'dao' package branch. when such
a method is called, the aspect wraps itself around the method call, determines
whether it needs to do anything (if scope != null) and if so it uses relection
to find a method with the same name and signature apart from am additional
first argument of type 'Scope'.
What I like about this- It really is an aspect of my app and therefore it is nice that this lives in exactly one place.
- It means my I can achive the deired effect by convention rather than configuration: I just need to code multiple methods with the same name in my DAOs
- It also means that the dao methods still fully encapsulate the query specifics and if different entities have different requirements for the queries used to list them by scope that is fine
- Since only certain methods of my DAO's are affected I can still have lots of methods left that won't ever get touched by the aspect and so I can have getAll() or list() or whatever methods that I might need for an admin backend.
- Well if you forget about the aspect and forget to code the partner 'scoped' method in a dao you'll get a NoSuchMethodException
- I doubt this, but it could be, that it affects performance
- You still have to remember using your 'scoped' methods in your controller
- It might actually drive you nuts if you come back to this code in 2 years time, having forgotten about this aop business, and put a breakpoint in the method called inyour controller only to wait endlessly for that method to ever be called …
Posted at 01:53PM Nov 11, 2009 by joerg in Allgemein | Kommentare[0]
Kommentare: