Skip to content

Panda Utilities: Dependency Injection

Dzikoysk edited this page Mar 23, 2020 · 4 revisions

Dependency Injection

The lightweight and actively developed built-in dependency injection library. Supported operations:

  • Creating a new instance of the specified type using Injector (constructors)
  • Invoking methods using Injector (methods)

Full example can be found as a test class: DependencyInjectionTest.java

Usage

Firstly, you have to create Injector which keeps all the registered bindings (injected values)

DependencyInjection.createInjector()

// If you are able to register binding at the init time, it's also recommended to use the following structure
Injector injector = DependencyInjection.createInjector(resources -> {
    // bind resources
});

Injector supports three main ways to bind with a value:

  • Binding to the specified type resources.on(<Type>)
  • Binding to the specified annotation resources.annotatedWith(<Annotation>)
  • [Experimental] Binding to the specified annotation mapped into the metadata type. It might be useful to bind similar annotations (annotations cannot be inherited) and it's required for *wired structures resources.annotatedWithMetadata(<Annotation>)

Each binding supports three ways of assigning value:

  • assign(<Type>) - creates a new instance of type per call
  • assignInstance(<Object>)/assignInstance(Supplier<Object>) - binds the specified value/some kind of lazy values
  • assignHandler((<Expected Type Of Value>, <Annotation>) -> { /* logic */ }) - binds custom handler

Let's build random example based on these methods

@Injectable // mark annotation as DI ready annotation
@Retention(RetentionPolicy.RUNTIME) // make sure that the annotation is visible at runtime
@interface AwesomeRandom { }

static final class Entity {
    private final UUID id;

    @Inject //it's not required, but it might be useful to disable "unused method" warnings/scanning for annotations
    private Entity(@AwesomeRandom UUID random) {
        this.id = random;
    }

    public UUID getId() {
        return id;
    }
}

We'd like to generate a new id per each Entity instance with a private constructor. It's also important for us, to support id in two forms:

  • String
  • UUID
Injector injector = DependencyInjection.createInjector(resources -> {
    resources.annotatedWith(AwesomeRandom.class).assignHandler((expectedType, annotation) -> {
        if (expectedType.equals(String.class)) {
            return UUID.randomUUID().toString();
        }

        if (expectedType.equals(UUID.class)) {
            return UUID.randomUUID();
        }

        throw new IllegalArgumentException("Unsupported type " + expectedType);
    });
});

// Create entities using the injector instance
Entity entityA = injector.newInstance(Entity.class);
Entity entityB = injector.newInstance(Entity.class);

// Print generated values
System.out.println(entityA.getId());
System.out.println(entityB.getId());

The output produces some random identifiers as intended 👍

e23442b2-f695-41fa-9290-0f1192118a1a
9f92121c-096e-4bdb-b6ad-0901974bbe37

Process finished with exit code 0

Full example is available here -> DependencyInjectionWikiTest.java

Wired structures

Wired, or to be more appropriate - weird, structures allow us to annotate properties using the metadata format. It should be treated only as some kind of funny feature and should not be used in a serious projects.

@Wired({
    @Wired.Link(parameter = "value", with = CustomAnnotation.class, value = "hello-wired")
})
void wired(String value) { }

Considering the possibilities of wired annotations, it might be tricky workaround to use some external annotations which are not marked as @Injectable and to change (not improve) the readability of methods with a hundred of annotated parameters.

Clone this wiki locally