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 |
trouble with spring and maven assembly plugin
I've built a couple of small java tools recently that do some background
processing and get run from cron. These tools are too complex for a
maintainable groovy or python script (at least for my taste) and I therefore
implement them in Java using spring for its wonderful JDBC template, database
connection handling and some other niceties. Now to make for a simple deploy I
pack them up using mavens assembly plugin and here is where the fun starts. If
you do this you will get an error like this:
The reason is that the assembly plugin copies stuff from META-INF dirs of
dependency jars into the META-INF dir of the resulting jar and quite a few of
the spring jars have spring.handlers and spring.schemas files in their
META-INF directory. Now when the assembly plugin packages all these together
only the files of teh last jar it deals with end up in the resulting jar which
causes above error when you try to run the jar.
After some googling I found a couple of hints on the web, e.g. http://forum.springsource.org/showthread.php?p=190246#post190246
and http://forum.springsource.org/showthread.php?p=195655#post195655
and cooked up a fix that works ok for me. I first concatenated the contents of
all spring.handlers files into a new spring.handlers file all spring.schemas
into a new spring.schemas and put the resulting files into my
src/main/resources directory.
I then created the file src/main/assembly/my-jar-with-dependencies.xml with the following contents:
Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace
and added this to the plugins section in my pom.xml
<assembly>
<id>my-jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>true</unpack>
<scope>runtime</scope>
<unpackOptions>
<excludes>
<exclude>**/spring.handlers</exclude>
<exclude>**/spring.schemas</exclude>
</excludes>
</unpackOptions>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
</fileSet>
<fileSet>
<directory>target/classes</directory>
<outputDirectory>META-INF</outputDirectory>
<includes>
<include>spring.handlers</include>
<include>spring.schemas</include>
</includes>
</fileSet>
</fileSets>
</assembly>
If I now run 'mvn assembly:assembly' it created the file
'target/myProject-my-jar-with-dependencies.jar' which I can run without
getting the 'Unable to locate …' message any longer.
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/my-jar-with-dependencies.xml</descriptor>
</descriptors>
<archive>
<manifest>
<mainClass>my.main.Class</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
P.S. There is also a JIRA issue on this: MASSEMBLY-360
Posted at 03:31PM Nov 10, 2009 by joerg in Allgemein | Kommentare[1]