SkillAgentSearch skills...

Dirk

Dirk DI, a light-weight DI framework which can have its dependencies changed at runtime

Install / Use

/learn @hjohn/Dirk
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Dirk DI

Maven Central Build Status Coverage License javadoc

Dirk is a small, highly customizable, dynamic dependency injection framework.

Quick Start

Add a dependency to Dirk in your POM:

<groupId>org.int4.dirk</groupId>
<artifactId>dirk-di<artifactId>
<version>1.0.0-beta1</version>

Assume there is a small class which depends on a String which Dirk should provide:

public class Greeter {
    @Inject private String greeting;

    public void greet() {
        System.out.println(greeting);
    }
}

Create an injector:

Injector injector = Injectors.autoDiscovering();

Register a String and the Greeter class:

injector.registerInstance("Hello World");
injector.register(Greeter.class);

Let the injector create an instance of Greeter, then call its greet method and observe that the injected greeting is printed to the console:

Greeter greeter = injector.getInstance(Greeter.class);

greeter.greet();  // prints "Hello World"

Features

  • Dependency Injection
    • Constructor, Method and Field injection
    • Supports qualifiers, scopes, generics and lifecycle callbacks
  • Dynamic
    • Register and unregister types at any time
    • Ensures all dependencies are always resolvable and unambiguous
  • Highly Customizable
    • Choose what annotations and extensions Dirk should use
    • Built-in styles for Jakarta (CDI), JSR-330 and Dirk DI or create a new one
  • Extendable
    • Fully documented API and SPI
    • Common DI features are just extensions in Dirk
  • Small
    • Core jar and its dependencies are around 200 kB

Several well known features of DI systems are implemented as standard extensions to Dirk's core system. Included are extensions to support:

  • Producer methods and fields
  • Delayed lookup of dependencies (providers)
  • Assisted Injection
  • Proxy creation and injection
  • List, Set and Optional injection

Available Dependency Injection Styles

| |Dirk|CDI|Jakarta|JSR-330| |---|---|---|---|---| |Artifact|dirk-di|dirk-cdi|dirk-jakarta|dirk-jsr330| |Standard Annotations|jakarta.inject|jakarta.inject|jakarta.inject|javax.inject| |Additional Annotations|org.int4.dirk.annotations|jakarta.enterprise.inject|org.int4.dirk.annotations|org.int4.dirk.annotations| |Default Annotation|@Default|@Default|-|-| |Any Annotation|@Any|@Any|-|-| |Optional Injection|@Opt|-|@Opt|@Opt| |Producer Support|@Produces|@Produces|@Produces|@Produces| |Assisted Injection|@Assisted & @Argument<sup>1</sup>|-|@Assisted & @Argument<sup>1</sup>|@Assisted & @Argument<sup>1</sup>| |Indirect Injection|Provider|Provider & Instance|Provider|Provider| |Collection Injection|List & Set|-|List & Set|List & Set| |Proxy Support|Yes<sup>2</sup>|Yes<sup>2</sup>|Yes<sup>2</sup>|Yes<sup>2</sup>|

<sup>1</sup> When detected on classpath by including org.int4.dirk.extensions:extensions-assisted
<sup>2</sup> When detected on classpath by including org.int4.dirk.extensions:extensions-proxy

Documentation

Terminology

|Term|Explanation| |---|---| |Candidate|A qualified type that could be used to satisfy a dependency| |Dependency|A qualified type required by an inject annotated constructor, method or field|

Dependency Injection

Dependencies are other classes or types that are required for the correct functioning of a class. A dependency can be a class, an interface, a generic type or a primitive type. Dependency injection supplies these required values automatically. Dependencies can be supplied through constructor or method parameters or by setting fields directly.

Constructor injection:

public class Greeter {
    @Inject
    public Greeter(String greeting) { ... }
}

Method injection:

public class Greeter {
    @Inject
    void setGreeting(String greeting) { ... }
}

Field injection:

public class Greeter {
    @Inject 
    private String greeting;
}

Any of the above forms can have a String dependency injected. Note that when using method or field injection the values are set after the constructor is called. Referring to these values in the constructor therefore could result in an error, instead consider implementing this logic in an initializer method that can be called after injection completes. See Lifecycle Callbacks for more information.

Type Resolution

When considering the type to inject for a dependency, the system follows standard Java rules when doing type conversions. Any type conversion which does not require a cast is allowed, except primitive widening conversions. This includes boxing and unboxing conversions and compatible generic conversions.

When a class is registered with an injector it can satisfy one or more types. The types that can be satisfied are all implemented interfaces, its super classes and any interfaces and super classes these implement or extend in turn. The Greeter class for example could be a candidate for dependencies of type Greeter and Object.

The framework also does automatic boxing and unboxing conversion. For primitive types and their boxed types this adds another possible type they can supply. An Integer can be used to inject an int or vice versa. Other types the Integer class could satisfy are Number (its super class), Object (Number's super class), Comparable<Integer> (implemented interface) and Serializable (interface implemented by Number).

Example:

injector.registerInstance(42);  // register an int with value 42

Would satisfy all the following dependencies:

@Inject int i;
@Inject Integer integer;
@Inject Number number;
@Inject Object object;
@Inject Comparable<Integer> integerComparable;
@Inject Comparable<? extends Number> numberComparable;

... but would not satisfy:

@Inject long l;  // Primitive widening conversion not allowed
@Inject Long longValue;  // Integer cannot be cast to Long
@Inject Comparable<Number> comparable;  // Incompatible generic type

Qualifiers

Types registered with the injector can be annotated with qualifier annotations. These annotations provide another way to distinguish candidates besides their types. This makes it possible to distinguish between multiple candidates that may all match a dependency where exactly one dependency is required. Qualifiers can be placed on candidates and on dependencies. In order for a candidate to match, it must have all the qualifiers specified on the dependency.

A dependency with an @English qualifier annotation:

@Inject @English private String greeting;

Or as a constructor or method parameter:

public Greeter(@English String greeting) { ... }
public void setGreeting(@English String greeting) { ... }

Candidates can be annotated directly with qualifiers, or they can be specified during registration (for instances).

A Greeter candidate with an @English qualifier annotation:

@English
public class Greeter { ... }

Registering String candidate instances with different qualifiers:

injector.registerInstance("Hello World", English.class);
injector.registerInstance("Hallo Wereld", Dutch.class);

As an example, given two String candidates, one annotated with @Greeting and @English, the other annotated with @Greeting and @Dutch:

injector.registerInstance("Hello World", English.class, Greeting.class);
injector.registerInstance("Hallo Wereld", Dutch.class, Greeting.class);

Then the following dependencies could be satisfied:

@Greeting @English String s;  // an English greeting
@Greeting @Dutch String s;    // a Dutch greeting
@English String s;            // any English String
@Dutch String s;              // any Dutch String

The following dependencies will not be satisfied:

@Greeting String s;          // ambiguous, English or Dutch greeting?
String s;                    // ambiguous, there are two String candidates
@Greeting @French String s;  // unsatisfiable, no French greeting was registered
@English int englishNumber;  // unsatisfiable, no int was registered

Scopes

Scopes are used to control the lifecycle of candidates, and which instance of a candidate is used to satisfy a dependency. The injector supports two types of scopes, pseudo-scopes and normal scopes. Candidates which have a pseudo-scope are never wrapped in a proxy, and do not need to use indirection to resolve scope conflicts. Candidates with a normal scope will require a proxy (or indirection via a provider) when injected into other candidates with a different scope.

Which scopes are considered pseudo-scopes and which scopes provide the mandatory singleton and unscoped scopes is determined by the used ScopeStrategy. The actual implementation of each scope is provided by a corresponding ScopeResolver when creating the injector.

Lifecycle

When the injector is configured to do lifecycle callbacks (for example, calling @PostConstruct or @PreDestroy annotated methods), the injector will call these, respectively, after injection completes and just before the candidate is removed.

Dependency validation during registration

When adding or removing candidates from the injector, the injector ensures that all (remaining) registered candidates can have their dependencies

Related Skills

View on GitHub
GitHub Stars33
CategoryDevelopment
Updated2mo ago
Forks0

Languages

Java

Security Score

75/100

Audited on Jan 29, 2026

No findings