Skip to content
Helospark edited this page Apr 29, 2018 · 2 revisions

LightDi

Simple lightweight Spring like dependency injection framework

Designed specifically for small applications, that has to have small memory footprint, small jar size and fast startup time, for example plugins, (embedded) standalone application, integration tests, jobs, Android applications, etc.

If you know Spring, read the Usage section, and you will probably be fine.

What is DI

DI stands for "Dependency Injection". In it's simplified form means, that a class will get it's dependencies from outside. There is already a lot of information about the theory, for example Wikipedia, or you can do a search for the term.

Here I would like to concentrate on the practical aspect.

For example here is a sample code using DI:

public class StudentService {
    private double passingThreashold;
    private StudentDatabase database;
    private StudentGradeCalculator calculator;

    public StudentService(StudentDatabase database, StudentGradeCalculator calculator, double passingThreashold) {
        this.database = database;
        this.calculator = calculator;
        this.passingThreashold = passingThreashold;
    }

    public boolean doesStudentPass(int id) {
        Student student = database.getById(id);
        double grade = calculator.calculateGrade(student);
        return grade > passingThreashold;
    }
}

public class MysqlBasedStudentDatabase implements StudentDatabase {
    public Student getById(int id) {
        // imagine some DB query here
    }
}

public class StudentGradeCalculator {
    public double calculateGrade(Student student) {
        // imagine some calculate here
    }
}

There are couple of things to note here:

  • StudentService has a constructor where it gets all of it's dependency
  • Most of the classes have simplified to a single method, and a class is simplified to an empty container
  • Methods contain fairly simple logic

Some of you may have questions like:

  • Why not just use a single class?
  • Or static methods in the class to avoid instantiation?
  • Why not create the dependencies with new?

These are good questions. The answer is somewhat the limitation of Java itself. Mainly that methods are not first class citizens in the language.

Using static methods would mean, that the binding happens at compile time, therefore it is not possible to change it (without some very intrusive mocking).

On the other hand injected instances will be dynamically invoked, if I were to inject a child class of the given type, the correct method would get selected during runtime. This is really powerful, since I could just change the StudentDatabase instance to read students from DB, file, standard input, etc. Similarly I could change the calculator by injecting a different implementation.

This also why it's good to separate the logic to many classes, since then it is easy to select just a single piece of logic to change.

Creating a dependency with new within the class has the same effect as static methods.

Changing the implementation could also be used to mock out dependencies, so the code can be tested in isolation. Or we could cut just some of the dependencies out to test a large portion of the code, but not the entire code (for example removing actual remote REST calls).

Why to use a framework

Now if we follow the above dependency injection logic all the way, we will have factories where the dependencies are injected together:

StudentGradeCalculator calculator = new StudentGradeCalculator(...)
StudentDatabase database = new MysqlBasedStudentDatabase(some, param);
StudentService service = new StudentService(database, calculator, Double.valueOf(System.getProperty("PASSING_THRESHOLD")));

These factories can get pretty large. They can sometimes also contain logic based on environment variables so that different dependencies are wired together based on environment. They can get pretty repetitive lots of time.

This is where DI frameworks come in. They will do the wiring based on defined logic and convention. LightDI uses annotations to define the logic.

The above classes could be written as:

@Service
public class StudentService {
    private double passingThreashold;
    private StudentDatabase database;
    private StudentGradeCalculator calculator;

    public StudentService(StudentDatabase database, StudentGradeCalculator calculator, @Value("PASSING_THREASHOLD") double passingThreashold) {
        this.database = database;
        this.calculator = calculator;
        this.passingThreashold = passingThreashold;
    }
}

@Service
public class MysqlBasedStudentDatabase implements StudentDatabase {
   // not changed
}

@Service
public class StudentGradeCalculator {
   // not changed
}

LightDI puts the annotated classes into so called application context (basically a container containing the instances, their relation, configuration, properties) and will wire together automatically.

When LightDI sees a constructor for an annotated class, it will try to find matching dependencies, if there is just a single match in the application context, it will automatically fill it and call the constructor (if there is no single match, you need to be more specific, see later in the guide).

LightDI also uses environment variables, property files to inject these values as well. Also it can make conditions based on these values, so you could have a property file define which DB to use, LightDI will create the instances based on that.

Annotated classes/instances are called beans.

LightDI highlights

  • Classpath scanning for beans
  • Spring like (if you are familiar with Spring, you will have no problem using it)
  • Android support
  • Annotation driven (no XMLs)
  • Conditional bean support, based on existance of other beans, classes, properties
  • JUnit test support (using lightdi-test dependency)
  • Fast startup time, small memory footprint

Usage

  • Annotate classes you would like in the application context with @Component, @Service or @Component annotation, also other annotations (see the guide further).
  • Load the context by package: LightDiContext context = LightDi.initContextByPackage("com.package.you.want") or @Configuration class: LightDiContext context = LightDi.initContextByClass(MainConfiguration.class)
  • You can get out an instance from the returning context by: context.getBean(BeanYouWant.class)
  • Close the context when you are done with it: context.close()

More resources

Clone this wiki locally