Skip to content
Erhan Bağdemir edited this page Sep 6, 2024 · 52 revisions

Mikron is a minimalistic general purpose IoC container for dependency injection and externalized configuration management for Java. The framework allows you to harness the convenience of dependency injection and configuration management. Mikron enabled applications to instantiate an IoC container at start-up. Managed objects reside in the IoC container and wired together by dependency injection. To create a managed instance, you must first declare it by annotating classes with the @Managed annotation, whose instance the life-cycle is under the control of the container.

To start using Mikron, you can add the Maven dependency into your project:

  <dependency>  
    <groupId>net.reevik</groupId>  
    <artifactId>mikron</artifactId>  
    <version>${latest}</version>  
  </dependency>  

Mikron Application

By using @ManagedApplication on the classes which contains the main method, we declare a Mikron application. The annotation takes a list of Java packages as parameter, where managed instances are to be scanned by the IoC container:

@ManagedApplication(packages = {"your.package.to.scan"})
public class MainApplication {
}

You should either set the exact package name where the managed instances will be looked up, the scan is not recursive, but you can use "*" wildcard to initiate a recursive search, e.g., "net.reevik.foo.*".

Application Context

Mikron applications must instantiate Mikron context, that is the IoC container, where managed instances reside. The context scans through the packages for managed objects on start-up, which are declared in the @ManagedApplication. The factory method, MikronContext#init() must take the class file, which is annotated with @ManagedApplication, as a parameter so that it can access the package list:

  public static void main(String[] args) {
    Optional<AnnotatedManagedClass> instance;
    try (MikronContext context = MikronContext.init(MainApplication.class)) {
      instance = context.getInstance(AnnotatedManagedClass.class.getName());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

The context controls the managed object's life-cycle, i.e., it creates, initializes and eventually destroys them. As you may notice, the MikronContext implements AutoCloseable interface. If the IoC container gets closed, the managed instances will be destroyed. You can also access managed objects by calling MikronContext#getInstance().

Managed Objects

Managed instances are declared by using @Managed annotation on classes. By doing so, you make them candidates for the managed object scanner. Let's take a look at the following example:

@Managed
public class ManagedObject {

  @Wire
  private ManagedDependency managedDependency;
}

Whenever the MikronContext detects a managed object definitions, it creates a new instance by calling the default constructor by default. The managed object must be within one of the packages, which are declared in @ManagedApplication. You can also utilize the constructor injection and define your constructor with parameters annotated with @Wire annotation:

@Managed
public record Foo(Bar bar) {

  @Prefer
  public Foo(@Wire Bar bar) {
    this.bar = bar;
  }
}

If multiple constructors are defined, to eliminate confusion, you must mark the constructor with @Prefer, which is to be called by the context during object creation. As demonstrated by the example above, the constructor parameters can be annotated with @Wire annotation. The IoC container will inject managed objects while creating a new instance. You can also use @Wire annotation to declare dependency injection points on class fields, where others managed objects get injected. If the dependency cannot be found or out of the scope, the field remains null.

Only concrete classes can be declared as managed. You can define injection points on abstract classes though, but abstract classes themselves may not be managed instances, rather the concrete classes implementing the abstract ones.

Configuration Management for Managed Objects

Managed objects may require externalized configurations, e.g., in property files. You can create property files containing configurations for the manage instances, which can be injected into "Configurable" class fields:

package net.reevik.example;

// imports statements

@Managed
public class ConfigurableManagedObject {

  @Configurable(name = "config.temp")
  private Integer temperature;

  public Integer getTemperature() {
    return temperature;
  }
}

If there is a property-file in the classpath with the name "net.reevik.example.ConfigurableManagedObject.properties" containing the configuration property,

config.temp=42

The value "42" will be set to the temperature field. The name of the property-file must consist of the package and the class name, e.g., <package name>.<class name>.properties.

Testing with Mikron

You can also use the IoC container in your unit and integration tests:

@ManagedApplication(packages = {"com.foo.*"})
@ManagedTest
public class ExtensionBindingTest {

  @Wire
  private ExtensionManagedInstance extensionManagedInstance;

  @Test
  void testContextBinding() {
    assertThat(extensionManagedInstance).isNotNull();
  }
}

To enable the Mikron in a unit test, you must use @ManagedApplication and the packages as parameter, which need to be scanned, and @ManagedTest to activate the unit test extension.