Understanding Dependency Injection by writing a DI Container - from scratch! (Part 3)
Tags: java, dependencyinjection, spring, jee
Published: 2020-02-11 21:55:43

---
title: Understanding Dependency Injection by writing a DI Container - from scratch! (Part 3)
published: true
description: Dependency Injection is a difficult topic to grasp. Let's break it down into simple pieces by writing a basic, yet fully functional DI container from scratch!
series: DI From Scratch
tags: java, dependency injection, spring, jee
---

This is the third part of my "DI From Scratch" series. In the previous article, we built a basic DI container. Now, we want to take it yet another step further and automatically discover the available service classes.


# DI Stage 7: Auto-detecting Services

> Find the source code of this section [on github](https://github.com/MartinHaeusler/blog/tree/master/diFromScratch/src/main/java/org/example/simplecdi/stage7/classpathscan)

Our current state represents (a strongly simplified, yet functional) version of libraries such as [Google Guice](https://github.com/google/guice). However, if you are familiar with [Spring Boot](https://spring.io/projects/spring-boot), it goes one step further. Do you think that it's annoying that we have to specify the service classes explicitly in a set? Wouldn't it be nice if there was a way to auto-detect service classes? Let's find them!

```java
public class ClassPathScanner {

    // this code is very much simplified; it works, but do not use it in production!
    public static Set<Class<?>> getAllClassesInPackage(String packageName) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            dirs.add(new File(resource.getFile()));
        }
        Set<Class<?>> classes = new HashSet<>();
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName));
        }
        return classes;
    }

    private static List<Class<?>> findClasses(File directory, String packageName) throws Exception {
        List<Class<?>> classes = new ArrayList<>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                classes.addAll(findClasses(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
            }
        }
        return classes;
    }

}
```

Here we have to deal with the `ClassLoader` API. This particular API is quite old and dates back to the earliest days of Java, but it still works. We start with a `packageName` to scan for. Each `Thread` in the JVM has a `contextClassLoader` assigned, from which the `Class` objects are being loaded. Since the classloader operates on files, we need to convert the package name into a file path (replacing `'.'` by `'/'`). Then, we ask the classloader for all *resources* in this path, and convert them into `File`s one by one. In practice, there will be only one resource here: our package, represented as a directory.

From there, we recursively iterate over the file tree of our directory package, loking for files ending in `.class`. We convert every class file we encounter into a class name (cutting off the trailing `.class`) to end up with our class name. Then, we finally call `Class.forName(...)` on it to retrieve the class.

So we have a way to retrieve all classes in our base package. How do we use it? Let's add a static factory method to our `DIContext` class that produces a `DIContext` for a given base package:


```java
    public static DIContext createContextForPackage(String rootPackageName) throws Exception {
        Set<Class<?>> allClassesInPackage = ClassPathScanner.getAllClassesInPackage(rootPackageName);
        Set<Class<?>> serviceClasses = new HashSet<>();
        for(Class<?> aClass : allClassesInPackage){   
            serviceClasses.add(aClass);
        }
        return new DIContext(serviceClasses);
    }
```

Finally, we need to make use of this new factory method in our `createContext()` method:

```java
    private static DIContext createContext() throws Exception {
        String rootPackageName = Main.class.getPackage().getName();
        return DIContext.createContextForPackage(rootPackageName);
    }
```

We retrieve the base package name from the `Main` class (the class I've used to contain my `main()` method). 

But wait! We have a problem. Our classpath scanner will detect **all** classes, whether they are services or not. We need to tell the algorithm which ones we want with - you guessed it - an annotation:

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {

}
```

Let's annotate our services with it:

```java
@Service
public class ServiceAImpl implements ServiceA { ... }

@Service
public class ServiceBImpl implements ServiceB { ... }
```

... and filter our classes accordingly:

```java
    public static DIContext createContextForPackage(String rootPackageName) throws Exception {
        Set<Class<?>> allClassesInPackage = ClassPathScanner.getAllClassesInPackage(rootPackageName);
        Set<Class<?>> serviceClasses = new HashSet<>();
        for(Class<?> aClass : allClassesInPackage){
            if(aClass.isAnnotationPresent(Service.class)){
                serviceClasses.add(aClass);
            }
        }
        return new DIContext(serviceClasses);
    }
```

# We are done... are we?

And there you have it - a minimalistic, poorly optimized, yet fully functional DI container. But hold on, why do you need several megabytes worth of library code if the core is so simple? Well...

- Our classpath scanner is very wonky. It certainly doesn't cover all cases, nesting depths, inner classes etc. Libraries like [Guava's `ClassPath`](https://github.com/google/guava/wiki/ReflectionExplained#classpath) do a much better job at this.

- A big advantage of DI is that we can hook into the lifecycle of a service. For example, we might want to do something once the service has been created (`@PostConstruct`). We might want to inject dependencies via setters, not fields. We might want to use constructor injection, as we did in the beginning. We might want to wrap our services in proxies to have code executed *before* and *after* each method (e.g. `@Transactional`). All of those "bells and whistles" are provided e.g. by Spring.

- Our wiring algorithm doesn't respect base classes (and their fields) at all.

- Our `getServiceInstance(...)` method is very poorly optimized, as it linearly scans for the matching instance every time.

- You will certainly want to have different contexts for testing and production. If you are interested in that, have a look at [Spring Profiles](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-profiles).

- We only have one way of defining services; some might require additional configuration. See Springs `@Configuration` and `@Bean` annotations for details on that.

- Many other small bits and pieces.

# Summary

We have created a very simple DI container which:
 - encapsulates the creation of a service network
 - creates the services and wires them together
 - is capable of scanning the classpath for service classes
 - showcases the use of reflection and annotations

We also discussed the **reasoning** for our choices:
 - First, we replaced static references by objects and constructors.
 - Then, we introduced interfaces to further decouple the objects.
 - We discovered that cyclic dependencies are a problem, so we introduced setters.
 - We observed that calling all setters for building the service network manually is error-prone. We resorted to reflection to automate this process.
 - Finally, we added classpath scanning to auto-detect service classes.

If you came this far, thanks for reading along! I hope you enjoyed the read.