Thursday, 29 September 2011

Looking into the Magic of Dependency Injection Using Annotations - Part 1

One of the things that every Spring and EJB3 developer does on a daily basis is to use annotations to inject instance variables into your objects, which, it seems, has become something we do without thinking about.

You know the type of thing I’m talking about: you write a couple of classes annotating one with @Component and the other with @Autowired and when you run your killer app hey presto everything’s happily glued together.

@Component
public class FakaSource {

 
public String execute() {

   
System.out.println("Inside - execute of FakaSource");
   
return "A Result";
 
}
}

@Configuration
public class AutoWiredAppConfig {

 
@Autowired
 
private FakaSource datasource;

 
@Bean
 
public MyDAO createMyDataAccessObject() {

   
return new AnyOldDAO(datasource);
 
}
}

Now, this blog isn’t going to show you how to build a fully functional factory for loading classes using dependency injection and and annotations. It will, however, take a peak behind the magic and demonstrate some of the techniques that may have been employed by the Guys at Spring and the EJB3 Team.

Let’s consider for one moment... if you were writing a fully functional factory for loading classes, then what kinds of tasks would you need to accomplish? The first one I can think of is, given some kind of starting point, being able to walk through all the classes on the class-path figuring out which ones are marked with our annotations. This should include classes both on the file system as .class files and those stuck inside JAR files. Secondly, when testing for annotations, you’ll need to check both class level, method level, and field level attributes in order to auto-wire the bits together. Lastly, you need to instantiate the classes and do the auto-wiring, whilst getting around any accessibility problems... all of which is too much t put into one blog.

Today’s blog covers step one, and will demonstrate how to traverse a package structure from a known starting point. The starting point will be a package name, which is no surprise as that the way Spring does it with the following configuration file entry:

<mvc:annotation-driven />

Sample below ignores any annotations for one moment and merely creates a list of Class objects, which are then displayed on the screen with a simple System.out.println(...).

public class ClassesInPackage {

 
public static void main(String[] args) throws ClassNotFoundException, IOException {

   
System.out.println("Finding all classes in package...");
    ClassesInPackage instance =
new ClassesInPackage();

    List<Class<?>> result = instance.getClasses
("org.apache.log4j");
    listResults
(result);

    System.out.println
("-- END --");
 
}

 
private List<Class<?>> getClasses(String packageName) throws ClassNotFoundException, IOException {

   
String path = packageName.replace('.', '/'); // Convert the package name
                            // into a file path

   
File directory = getPackageDirectory(path);
    List<Class<?>> classes = findAllClasses
(directory, packageName);
   
return classes;
 
}

 
private File getPackageDirectory(String path) throws IOException {

   
ClassLoader classLoader = getClassLoader();
    URL resource = classLoader.getResource
(path);
    String fileNameDecoded = URLDecoder.decode
(resource.getFile(), "UTF-8");
   
return new File(fileNameDecoded);
 
}

 
private ClassLoader getClassLoader() {
   
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
   
assert classLoader != null;
   
return classLoader;
 
}

 
private List<Class<?>> findAllClasses(File directory, String packageName) throws ClassNotFoundException {

   
ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
    classes.addAll
(findClasses(directory, packageName));
   
return classes;
 
}

 
private List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException {
   
List<Class<?>> classes = new ArrayList<Class<?>>();

   
if (directory.exists()) {
     
File[] files = directory.listFiles();
     
for (File file : files) {
       
String fileName = file.getName();
       
if (file.isDirectory()) {
         
classes.addAll(findClasses(file, getClassName(packageName, fileName)));
       
} else if (isValidClassName(fileName)) {
         
classes.add(createClass(packageName, fileName));
       
}
      }
    }

   
return classes;
 
}

 
private String getClassName(String packageName, String fileName) {

   
String retVal;
   
if (packageName.length() == 0) {
     
retVal = fileName; // This is the default package
   
} else {
     
retVal = packageName + "." + fileName;
   
}
   
return retVal;
 
}

 
private boolean isValidClassName(String fileName) {
   
return fileName.endsWith(".class") && !fileName.contains("$");
 
}

 
private Class<?> createClass(String packageName, String fileName) throws ClassNotFoundException {

   
String className = getClassName(packageName, fileName.substring(0, fileName.length() - 6));
   
return Class.forName(className);
 
}

 
private static void listResults(List<Class<?>> clazzes) {

   
int i = 1;
   
for (Class<?> clazz : clazzes) {
     
System.out.println(i++ + ") Found: " + clazz.getName());
   
}
  }

}

Note that in this sample, I’m also ignoring classes that are located in JAR files. Accessing those is fairly similar, you just need to use the JarFile class, but more on that later perhaps...

The getClasses(...) method is the starting point of this class. Firstly, it gets hold of the directory containing our classes using getPackageDirectory(...) and then uses a spot of recursion, with findClasses(...) calling findClasses(...) every time it finds a new directory. Each call to the this method returns a list of classes, which is then added to the preceding list... and that’s the simple case covered. My next blog covers extracting classes from a JAR file.

No comments: