Saturday, March 22, 2008

Adventures in classpath scanning...

On a recent project I've run across a number of scenarios where I wanted to generically apply some logic across a large number of classes. Previously, I've done things like using Ant for this or used find and grep to create an input file with a list of matches and then writing code that read that file.

But what I really wanted was something much simpler that could easily be used in Java code without a lot of gymnastics. And Spring's ClassPathBeanDefinitionParser had almost everything I wanted, except that it was written specifically to generate Spring bean definitions and didn't expose the internal features that I wanted to use.

So I looked at the ClassPathBeanDefinitionParser source and wrote ClassPathScanner.java
(NOTE: It uses numerous Spring classes internally, so it requires Spring.)

It's a crude start, but the basic idea is there. And the results have been suprisingly easy to work with. For example, if you want to write a generic JUnit test that runs all of your tests:
public class AllTestsRecursively extends TestCase {
public static TestSuite suite() throws Exception {
TestSuite suite = new TestSuite();
ClassPathScanner scanner = new ClassPathScanner()
.basePackage(AllTestsRecursively.class.getPackage().getName())
.assignableFrom(TestCase.class)
.includeFilter(".*Test");
for (Class testClass : scanner.findClasses()) {
suite.addTestSuite(testClass);
}
return suite;
}
}
Not rocket science, but it makes it pretty easy to write a lot of generic code. For example, you could use it to write tests that assert characteristics that might otherwise require aspects to enforce.

Or you could use it to dynamically generate Spring beans in ways not directly supported by ClassPathBeanDefintionScanner (e.g. you could search all of your services and create CXF bean definitions for all services that have the @WebService annotation).

I was surprised that something like this hadn't already been implemented in one of the Apache commons projects (or maybe it has and I just couldn't find it). But it seems like something that could be genuinely useful in many different scenarios.

No comments: