diff --git a/README.markdown b/README.markdown
index ede22446..7cc8c440 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,404 +1,535 @@
-Dirk: Dynamic Dependency Injection Framework
-============================================
+# Dirk DI
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.hjohn.ddif/ddif-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.hjohn.ddif/ddif-core)
[![Build Status](https://github.com/hjohn/hs.ddif/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/hjohn/hs.ddif/actions)
[![Coverage](https://codecov.io/gh/hjohn/hs.ddif/branch/master/graph/badge.svg?token=QCNNRFYF98)](https://codecov.io/gh/hjohn/hs.ddif)
[![License](https://img.shields.io/badge/License-BSD_2--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause)
+[![javadoc](https://javadoc.io/badge2/com.github.hjohn.ddif/ddif-core/javadoc.svg)](https://javadoc.io/doc/com.github.hjohn.ddif/ddif-core)
-A light-weight framework that allows you to use standard JSR-330 javax.inject
-Annotations to create instances of objects, even when they're dynamically loaded at
-runtime. This framework will allow you to package your classes in separate JAR's,
-load them at runtime, and have them injected with dependencies or serve as dependencies
-for other classes.
+Dirk is a small, highly customizable, dynamic dependency injection framework.
-For example given a class implementing the `PaymentProcessor` interface:
+## Quick Start
- public class CreditCardPaymentProcessor implements PaymentProcessor {
- }
+Add a dependency to Dirk in your POM:
-The below class would get its `paymentProvider` field injected with an instance of
-the above class:
+ org.int4.dirk
+ dirk-di
+ 1.0.0
- public class BookShop {
- @Inject
- private PaymentProcessor paymentProcessor;
+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);
+ }
}
-This framework differs from most standard DI frameworks in that dependencies can be
-added and removed at runtime. The used Injector will make sure that dependencies can
-be satisfied and will refuse to add or remove classes when it would result in broken
-dependencies.
+Create an injector:
+
+ Injector injector = Injectors.autoDiscovering();
+
+Register a `String` and the `Greeter` class:
+
+ injector.registerInstance("Hello World");
+ injector.register(Greeter.class);
-For example, if a class registered with an Injector instance needs a `PaymentProvider`,
-then attempting to remove the only matching `PaymentProvider` from the Injector will
-fail. Likewise, registering a class that needs a `PaymentProvider` would fail when no
-class is registered that could provide one.
+Let the injector create an instance of `Greeter`, then call its `greet` method and observe that the
+injected greeting is printed to the console:
-Other than those restrictions, the Injector is free to be modified at runtime in any
-way that is desired.
+ Greeter greeter = injector.getInstance(Greeter.class);
-You may ask if dependencies are enforced in such a way, how would it ever be possible
-to depend on something that a dynamically loaded class offers? This can be achieved by
-using optional dependencies and by using Collection dependencies.
+ greeter.greet(); // prints "Hello World"
-An example of a Collection dependency:
+## Features
- public class BookShop {
+- 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`1|-|`@Assisted` & `@Argument`1|`@Assisted` & `@Argument`1|
+|Indirection Injection|`Provider`|`Provider` & `Instance`|`Provider`|`Provider`|
+|Collection Injection|`List` & `Set`|-|`List` & `Set`|`List` & `Set`|
+|Proxy Support|Yes2|Yes2|Yes2|Yes2|
+
+1 When detected on classpath by including `org.int4.dirk.extensions:extensions-assisted`
+2 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 another 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
- private Set booksOnShelf;
+ public Greeter(String greeting) { ... }
}
-If there are no books registered with the Injector, it will simply inject an
-empty collection. However, if at creation time of the BookShop instance there are
-books registered those will all get injected.
-
-To make this dependency even more dynamic, it can be wrapped in a `Provider` (see
-JSR-330 docs), like this:
+Method injection:
- public class BookShop {
+ public class Greeter {
@Inject
- private Provider> booksOnShelfProvider;
+ void setGreeting(String greeting) { ... }
}
-In this instance, each time the `get` method of the Provider is called, the Injector
-will look for all registered books, even ones that were added or removed after the
-`BookShop` instance was created, and return them.
+Field injection:
-## Features
+ public class Greeter {
+ @Inject
+ private String greeting;
+ }
-* Supports all JSR-330 annotations
- * `@Inject`, `@Qualifier`, `@Named`, `@Scope`, `@Singleton`
-* Light-weight, very few dependencies
-* Inject classes with dependencies, even if loaded at runtime
- * Constructor, Field and Setter injection
- * Assisted injection using any qualified SAM class or interface
- * Optional injection with the `@Opt` annotation or any `@Nullable` annotation
- * Scoping of injectables with the `ScopeResolver` interface
- * Injection of generic types (for example `List extends Number>`)
- * Injection of collections containing all matching dependencies, if any
- * Backed by `TypeExtension` interface, easily extended for special handling of other types
-* Injection candidates can be supplied by:
- * Direct registration of types and instances
- * Scanning packages or jars for annotated classes using `ComponentScanner`
- * Auto discovery of dependencies not explicitly registered
- * Producer methods or fields annotated with the `@Produces` annotation
- * JSR-330 providers
-* Highly extendable:
- * `TypeExtension`s allow for handling special types at injection points:
- * Support is available for `Provider`, `List` and `Set`
- * Easily create support for, for example, `Optional`, `Supplier` or `Map`
- * Coming soon: support for CDI's `Instance`
- * `InjectableExtension`s allow for discovering additional injectables based on interfaces or annotations:
- * Support for `@Produces` via `ProducesInjectableExtension`
- * Support for classes extending `Provider` is available via `ProviderInjectableExtension`
-* Very configurable:
- * See `Injectors` class for examples of how to configure a custom `Injector`
- * Can be configured to work like CDI or Spring (work in progress)
- * Select which annotations trigger injection
- * Add additional qualifiers automatically (like `@Default` or `@Any`)
-
-## Requirements
-* Java Runtime Environment 8+ installed
-
-# Getting started
-A basic example will be used to illustrate how this framework works. We start with
-a class that needs some dependencies injected:
-
- public class BookShop {
+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.
- @Inject
- private Set booksOnShelf;
+### Type Resolution
- @Inject
- public BookShop(CreditCardPaymentProcessor paymentProcessor) {
- ...
- }
- }
+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.
-To have this class injected, we'll need to create an Injector and configure it
-properly:
+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`.
- Injector injector = Injectors.manual();
+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` (implemented interface) and `Serializable` (interface implemented by
+`Number`).
- injector.register(CreditCardPaymentProcessor.class);
- injector.register(BookShop.class);
+Example:
-Now we can ask the Injector to create us an instance of `BookShop`:
+ injector.registerInstance(42); // register an int with value 42
- BookShop bookShop = injector.getInstance(BookShop.class);
+Would satisfy all the following dependencies:
-This will create a `BookShop` instance by calling the constructor annotated with `@Inject` and providing it with
-an instance of `CreditCardPaymentProcessor`. The `booksOnShelf` field will be injected (after the Constructor
-has completed) with an empty Set as no `Book` injection candidates are currently known to the Injector.
+ @Inject int i;
+ @Inject Integer integer;
+ @Inject Number number;
+ @Inject Object object;
+ @Inject Comparable integerComparable;
+ @Inject Comparable extends Number> numberComparable;
-By registering some `Book` objects, and asking for a new Instance of `BookShop` we can get the `booksOnShelf` field
-populated with some books:
+... but would not satisfy:
- injector.registerInstance(new Book("Dune"));
+ @Inject long l; // Primitive widening conversion not allowed
+ @Inject Long longValue; // Integer cannot be cast to Long
+ @Inject Comparable comparable; // Incompatible generic type
+
+### Qualifiers
- BookShop anotherBookShop = injector.getInstance(BookShop.class);
+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.
-The second `BookShop` instance will have a `Set` of `Book` objects matching those known by the Injector. If you
-wanted to have all `BookShop` instances to have access to the latest known books, you can wrap the collection
-with a `Provider`. The `BookShop` instances can then query for the latest books by calling the `get` method of
-the `Provider` any time they want:
+A dependency with an `@English` qualifier annotation:
- public class BookShop {
+ @Inject @English private String greeting;
- @Inject
- private Provider> booksOnShelf;
+Or as a constructor or method parameter:
- public Set getLatestBooks() {
- return booksOnShelf.get();
- }
+ 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).
-## Auto discovery of dependencies
+A `Greeter` candidate with an `@English` qualifier annotation:
-Registering every dependency manually (and in the correct order) can quickly become tedious. In our above
-example, the `BookShop` class needed to be registered as well as its `CreditCardPaymentProcessor` dependency.
-We can however have the Injector discover these automatically, as long as the dependencies themselves are
-concrete classes that have a public default constructor or have exactly one constructor with annotated with `@Inject`.
+ @English
+ public class Greeter { ... }
-In order to discover dependencies automatically, we have to create the Injector slightly differently:
+Registering `String` candidate instances with different qualifiers:
- Injector injector = Injectors.autoDiscovering();
+ injector.registerInstance("Hello World", English.class);
+ injector.registerInstance("Hallo Wereld", Dutch.class);
-Now we can get an instance of `BookShop` without any further dependencies needing to be registered:
+As an example, given two `String` candidates, one annotated with `@Greeting` and `@English`, the other
+annotated with `@Greeting` and `@Dutch`:
- BookShop bookShop = injector.getInstance(BookShop.class);
+ injector.registerInstance("Hello World", English.class, Greeting.class);
+ injector.registerInstance("Hallo Wereld", Dutch.class, Greeting.class);
-Under the hood, the Injector will notice there is no `BookShop` injection candidate registered. However,
-by analyzing the `BookShop` class it sees that it can be instantiated with a Constructor that requires a
-`CreditCardPaymentProcessor` -- unfortunately, there is also no `CreditCardPaymentProcessor` registered. The
-Injector then recursively analyzes the `CreditCardPaymentProcessor` class, and registers this class with
-itself as it noticed that it can be simply instantiated with a default constructor.
+Then the following dependencies could be satisfied:
-Now that the dependencies for `BookShop` can also be satisfied, the `BookShop` class is registered with the
-Injector, and an instance is returned.
+ @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:
-## Assisted Injection
+ @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
-Assisted injection makes it possible to automatically create a factory for a class that will inject known
-dependencies automatically while allowing you to supply additional arguments of your own. An abstract class
-or interface needs to be supplied annotated with the `@Assisted` annotation. It also must have a single
-abstract method (SAM type) that returns the correct type. This type will serve as the factory that can be
-injected. Secondly the product of the factory should have its supplied arguments annotated with `@Argument`
-while its dependencies will be injected as usual.
+### Scopes
-An example Product with a dependency on `Engine` and a user supplied argument `wheelCount`:
+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.
- public class Car {
- @Inject
- public Car(Engine engine, @Argument int wheelCount) { ... }
- }
+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 satisfied. If the addition or removal would result in unsatisfied or
+ambiguous dependencies then an exception is thrown explaining the problem and the addition or removal is rolled back
+to the previous consistent state.
-Next create an interface with a single method with only the parameters you wish to supply. The injector
-will automatically implement it. Note that the name of the parameter `wheelCount` must match the name in
-the Product `Car`. Classes should be compiled with parameter name information in order to discover these
-names at runtime; alternatively the `@Argument` annotation can be used to supply these manually.
+The injector can throw the following exceptions to indicate a problem during addition or removal:
- @Assisted
- interface CarFactory {
- Car createCar(int wheelCount);
+- `AmbiguousDependencyException` when a new candidate was added which requires a single candidate for a dependency
+but multiple candidates are available
+- `UnsatisfiedDependencyException` when a new candidate was added which requires a single candidate for a dependency
+but no candidates were available
+- `AmbiguousRequiredDependencyException` when the addition or removal of a candidate would cause another dependency in
+another candidate to become ambiguous, for example when a new candidate supplies another option for a dependency that
+was already satisfied
+- `UnsatisfiedRequiredDependencyException` when the addition or removal of a candidate would cause another dependency
+in another candidate to become unsatisfied, for example when a removed candidate was the only supplier of a dependency
+- `CyclicDependencyException` when a dependency cycle was detected amongst two or more candidates that was not broken
+by making use of a provider
+- `ScopeConflictException` when a scoped dependency depends directly on another scoped dependency, and the conflict
+could not be resolved automatically by means of a proxy
+
+## Registration of Candidates at runtime
+
+Dirk allows addition and removal of new candidates at any time, assuming the change won't leave the injector in an
+inconsistent state where it can't satisfy all its existing dependencies.
+
+Let's assume there is a class which requires a list of books:
+
+ class BookShop {
+ @Inject List availableBooks;
+
+ List getBooks() { return availableBooks; }
}
-The factory interface can now be injected anywhere you wish to create a `Car` instance. Only `wheelCount`
-needs to be supplied, while `engine` gets injected as usual:
+The class is registered, and an instance is obtained:
- public class Garage {
- @Inject
- public Garage(CarFactory factory) {
- Car car = factory.createCar(5);
- }
+ injector.register(BookShop.class);
+
+ BookShop bookShop = injector.getInstance(BookShop.class);
+
+When calling `getBooks` the list will be empty as no candidates were registered that supply a type `Book`:
+
+ assertThat(bookShop.getBooks()).isEmpty(); // passes
+
+This can be resolved by registering a `Book` and obtaining a new `BookShop` instance:
+
+ injector.registerInstance(new Book("Dune"));
+
+ BookShop bookShop2 = injector.getInstance(BookShop.class);
+
+ assertThat(bookShop2.getBooks()).hasSize(1); // passes
+
+Creating a new `BookShop` every time the list of available books changes is a bit cumbersome, so it is possible to
+use indirection to obtain the latest available books by means of a provider. Redefine the `BookShop` class:
+
+ class ModifiedBookShop {
+ @Inject Provider> availableBooks;
+
+ List getBooks() { return availableBooks.get(); }
}
-## Producers
-Producers are fields or methods that can supply a dependency that can be used for injection. They are useful
-for objects that are too complex for the injector to construct itself or for example make use of object pooling.
+An instance of this class will now immediately respond to a change in the books available:
+
+ injector.register(ModifiedBookShop.class);
+
+ ModifiedBookShop modifiedBookShop = injector.getInstance(ModifiedBookShop.class);
+
+ assertThat(modifiedBookShop.getBooks()).isEmpty(); // passes
+
+ injector.registerInstance(new Book("Dune"));
+
+ assertThat(modifiedBookShop.getBooks()).hasSize(1); // passes
-For example:
+In more complex scenario's it is sometimes necessary to register multiple candidates simultaneously to avoid a chicken/egg
+type problem. Given two classes:
- @Producer
- public static Connection createConnection() {
- return new Connection( ... );
+ class A {
+ @Inject B b; // depends on B
}
-When registering a class containing this method, the injector will start providing instances of `Connection`
-by calling the `createConnection` method every time a `Connection` is needed.
+ class B {
+ @Inject Provider a; // indirectly depends on A
+ }
+
+Registering either of these two candidates separately is not allowed as not all dependencies could be satisfied:
-It is also possible to annotate a field in this way:
+ injector.register(A.class); // --> UnsatisfiedDependencyException, requires B
+ injector.register(B.class); // --> UnsatisfiedDependencyException, requires A
- @Producer
- private static Connection connection = new Connection();
+However, registering both at the same time works as expected:
-Just like the method, the field will be read every time a new `Connection` is required.
+ injector.register(List.of(A.class, B.class)); // works!
-### Scoping
+## Auto Discovery
-By default, an instance produced by a producer has no scope applied to it. This means it will be called or
-read every time an instance of the type it supplies is required. By annotating the producer method or field
-with a scope annotation the same instance will be returned by the injector within an active scope. For example:
+When auto discovery is enabled, Dirk will attempt to automatically register candidates referred to in dependencies
+that are not yet registered, assuming it can find a way to construct them. Given two classes:
- @Producer
- @Singleton
- public static Connection createConnection() {
- return new Connection( ... );
+ class BookShop {
+ @Inject CreditCardPaymentProcessor paymentProcessor;
}
-The above producer will only be called once, and the same `Connection` instance will be injected everywhere.
+ public class CreditCardPaymentProcessor {
+ }
-### Qualifiers
+In order to create a `BookShop` instance, normally a `CreditCardPaymentProcessor` must be registered first (or simultaneously):
-Producers may be annotated with qualifiers to distinguish instances of the same type:
+ Injector injector = Injectors.manual();
- @Producer @Red Color color = Color.RED;
- @Producer @Green Color createGreen() { return Color.GREEN; }
- @Producer @Named("username") String name = "John";
+ injector.register(CreditCardPaymentProcessor.class);
+ injector.register(BookShop.class);
-At injection sites this qualifier can then be used to get a specific instance:
-
- @Inject @Red Color color; // will be Color.RED
- @Inject @Green Color color; // will be Color.GREEN
- @Inject @Named("username") String name; // will be "John"
+ BookShop bookShop = injector.getInstance(BookShop.class);
+
+Since `CreditCardPaymentProcessor` has an empty public constructor, the candidate could be automatically discovered when
+Dirk encounters the missing dependency in `BookShop`:
+
+ Injector injector = Injectors.autoDiscovering();
-Or to get all `Color` instances:
+ injector.register(BookShop.class); // registers BookShop and the discovered type CreditCardPaymentProcessor
- @Inject List colors; // will be List.of(Color.RED, Color.GREEN)
+ BookShop bookShop = injector.getInstance(BookShop.class);
+
+This automatic discovery works recursively and so large graphs of dependent candidates can all be discovered by
+registering only a few root candidates.
-### Dependencies
+## Optional Injection
-A producer can also have dependencies of its own. This can be achieved by either adding parameters to the
-method declaration or by making it a non-static method of a class that has dependencies. Here are two ways to make
-the `createConnection` method depend on a `Credentials` instance:
+Dirk allows dependencies to be optional if configured as such. When a dependency is optional, and no suitable candidate
+is available, Dirk will either inject `null` (for constructor and method parameters) or skip injection completely (for
+field injection). For example:
- @Producer
- public static Connection createConnection(Credentials credentials) {
- return new Connection(credentials, ... );
+ class Greeter {
+ @Inject @Opt private String greeting = "Hello World";
}
-Or:
+When no `String` candidate is available, this version of the `Greeter` class will fall back to a default greeting.
+Using constructor injection the same can be achieved as follows:
- public class DatabaseSetup {
- @Inject Credentials credentials;
+ class Greeter {
+ final String greeting;
- @Producer
- public Connection createConnection() {
- return new Connection(credentials, ... );
+ @Inject
+ Greeter(@Opt String greeting) {
+ this.greeting = greeting == null ? "Hello World" : greeting;
}
}
-Note that for non-static producer methods or fields, an instance of the declaring class must be available or
-can be created on demand. It is not possible for the declaring class to be directly or indirectly dependent on
-something one of its fields or methods produces as this would be a circular dependency. Declare the relevant
-producer `static` or use the `Provider` interface to avoid this.
+This allows a convenient way to have default values for, for example, configuration settings:
-## Optional dependencies
+ class TimeProvider {
+ @Inject @Opt @Named("server.ntp.url") String url = "pool.ntp.org";
+ @Inject @Opt @Named("server.sync.interval") int interval = 3600; // every hour
+ }
-Dependencies can be marked optional by either using a `Nullable` annotation (any annotation named `Nullable`
-is supported) or the `Opt` annotation provided by this project. When such a dependency is encountered,
-it will not be injected if the required dependency is not available. For example:
+## Customizing Injectors
- @Inject @Nullable @Named("database.user") private String user;
- @Inject @Opt @Named("database.timeout") private int timeout = 1000;
+The injector can be customized in several ways:
-In the above example, `user` will be left `null` and `timeout` will be left `1000` if the corresponding
-dependency is unavailable.
+- The `InjectorStrategy` allows for detailed customization of how the injector works:
+ - `AnnotationStrategy` is responsible for detecting injectable members and their qualifiers
+ - `ScopeStrategy` is responsible for detecting scope annotations, and configuring default scope annotations
+ - `ProxyStrategy` is responsible for creating proxies
+ - `LifeCycleCallbacksFactory` controls which and in what order lifecycle methods are to be called
+- Using `InjectionTargetExtension`s
+ - Allows customizing injection targets of a specific generic interface type
+ - Inject lists of instances using the `ListInjectionTargetExtension`
+ - Inject sets of instances using the `SetInjectionTargetExtension`
+ - Inject providers to allow runtime creation of instances using the `ProviderInjectionTargetExtension`
+ - Inject CDI `Instance` types using the `InstanceInjectionTargetExtension`
+- Using `TypeRegistrationExtension`s
+ - Allows detecting additional candidates when a type is registered
+ - Add additional candidates for each `@Produces` annotation using the `ProducesTypeRegistrationExtension`
+ - Add a candidate when types implement the `Provider` interface
+ - Assisted injection: When registering a SAM type (a class or interface with a single abstract method) automatically generate
+ an implementation of this method which produces a new candidate, see `AssistedTypeRegistrationExtension`
+- Using `ScopeResolver`s
+ - Allows creating resolvers for custom scope annotations
+- The injector can be configured to only provide candidates explicitly registered with it or also allow auto
+ discovery of additional candidates through their dependencies
-## Scoping
+## Extensions
-By default, all dependencies are created by the injector each time they are needed, except for dependencies
-supplied by providers or producers (which gives the user control over when to create a new instance or when
-to return an existing one) or dependencies which were registered as an instance.
+### Type Registration Extensions
-To prevent the injector creating a new instance every time, classes and producers can be annotated with
-`@Singleton`.
+A `TypeRegistrationExtension` is called whenever a new type is encountered by the injector during registration.
+The extension is given the opportunity to register additional types that it can derive from the new type. Usually,
+additional types are derived by inspecting the given type for annotations or interfaces it implements.
-### Scope Resolvers
+### Injection Target Extension
-An injector can be configured with instances of the `ScopeResolver` interface. This interface makes it
-possible to introduce a custom scope allowing the injector to track a different instance of a dependency
-depending on the currently active scope it is accessed within. The `AbstractScopeResolver` subclass
-simplifies this process further by only requiring the name of the annotation that marks the custom
-scope, and a method which identifies which scope is currently active.
+An `InjectionTargetExtension` allows creating instances of a specific generic interface prior to injection.
+The interface must have at least one type variable which the extension must expose to the injector as its element
+type. The element type, together with any qualifiers on the injection target, determines which candidates can be
+used by the extension to provide the interface implementation. The extension is allowed to delay the lookup or
+perform it immediately.
-For example, given a `ScopeResolver`:
+Interfaces which are extended by an injection target extension can no longer be directly injected, even if a
+candidate implements the interface.
- public class TestScopeResolver extends AbstractScopeResolver {
- private static String currentScope = "default";
+Examples are the `ListInjectionTargetExtension` and the `ProviderInjectionTargetExtension`, which provide support for
+injecting lists of candidates or delayed lookup of candidates (through the `List` and `Provider` interfaces)
+respectively.
- @Override
- public Class extends Annotation> getScopeAnnotationClass() {
- return TestScoped.class;
- }
+Extensions which do delayed lookup are allowed to wrap any other extended type, but in all other cases extensions
+cannot be nested.
- @Override
- public String getCurrentScope() {
- return currentScope;
- }
+# The Core Library
- public void changeScope(String scope) {
- this.currentScope = scope;
- }
- }
+Dirk's library and extension modules provide several standard extensions, strategies and scope resolvers that are
+normally integrated directly in most dependency injection frameworks. These are maintained as part of the project
+and can be used as desired, or serve as a starting point for 3rd party customizations.
-.. and a `@Scope` annotation:
+## Injection Target Extensions
- @Scope
- @Documented
- @Retention(RUNTIME)
- public @interface TestScoped { }
+- `ListInjectionTargetExtension` supports injecting multiple matching candidates as instances in a `List`.
+- `SetInjectionTargetExtension` supports injecting multiple matching candidates as instances in a `Set`.
+- `ProviderInjectionTargetExtension` supports indirect access to candidate instances using a
+ configurable interface (often the `Provider` interface)
+
+## Type Registration Extensions
-... and a `@TestScoped` annotated class registered with the injector:
+### `ProducesTypeRegistrationExtension`
- @TestScoped
- public class Car { }
+Allows fields and methods to act as factories for additional candidates using a configurable producer annotation.
+Producer methods with parameters are considered to offer candidates with dependencies which are to be provided when the
+method is called.
-... and an injector constructed with the above `ScopeResolver`:
+The following examples assume the annotation `@Produces` is configured to mark producer fields and methods:
- Injector injector = Injectors.manual(new TestScopeResolver());
+A field which produces a `String`:
- injector.register(Car.class);
+ @Produces String greeting = "Hello World";
-When asking the injector for a `Car` instance, it will first determine the current scope. This is achieved
-by finding the `ScopeResolver` responsible for resolving `TestScoped` annotations. The resolver is queried
-for an instance of `Car`, which the `AbstractScopeResolver` will attempt to find in the currently active scope
-supplied by `getCurrentScope`.
+A method which produces a `Connection` given a URI:
-If no `Car` instance is available in the current scope, the injector will create one and store it in the
-resolver. If one was available, it is simply returned. For example:
+ @Produces Connection createConnection(URI uri) { ... }
- Car car = injector.getInstance(Car.class);
+Producers can be static or non-static. Static producers can be called at any time and have no access to any
+dependencies injected in their owner instance. Non-static producers can either have dependencies provided as parameters
+(in the case of a producer method) or by accessing fields in their owner instance.
-The above will return a new `Car` and is registered with our `TestScopeResolver`. When the injector is
-queried again, the same instance is returned:
+### `ProviderTypeRegistrationExtension`
- injector.getInstance(Car.class) == injector.getInstance(Car.class)
+Allows a type to act as a factory for an additional candidate when it implements a configurable interface (typically,
+the `Provider` interface).
-Depending on the currently active scope, the injector will return different instances. Let `resolver` be
-an instance of our `TestScopeResolver`:
+Below an example of a class that implements the `Provider` interface and provides an additional candidate `Connection`:
- Car car = injector.getInstance(Car.class);
- Car sameCar = injector.getInstance(Car.class);
+ class ConnectionProvider implements Provider {
+ @Inject URI uri;
- assert car == sameCar;
+ @Override
+ public Connection get() { ... }
+ }
- resolver.changeScope("local");
+### `AssistedTypeRegistationExtension`
- Car otherCar = injector.getInstance(Car.class);
+Allows a type which has a single abstract method (a SAM type) with a non-void return type to act as a factory for the
+returned type; any dependencies the produced type may have are injected by the injector, including the arguments
+supplied to the factory method; this is otherwise known as assisted injection.
- assert car != otherCar;
+Assume we have a candidate that requires an additional parameter at runtime before it can be constructed, but also
+has a required dependency:
-Scopes can also be inactive. The `AbstractScopeResolver` allows this by returning a `null` as current scope.
-When an attempt is made to get an instance of a scoped object when no scope is active, an `OutOfScopeException`
-will be thrown:
+ class Greeter {
+ @Inject String greeting; // a normal dependency
+ @Inject @Argument LocalTime timeOfDay;
- resolver.changeScope(null);
+ void greet() { ... }
+ }
+
+With this extension we can have Dirk generate a factory that can be injected, and which takes the additional argument
+as a parameter:
+
+ @Inject Function greeterFactory;
+
+The factory can be called as a normal `Function`:
+
+ Greeter greeter = greeterFactory.apply(LocalTime.now());
+
+ greeter.greet();
- Car car = injector.getInstance(Car.class); // throws exception
+Any SAM type can be used, as long as the arguments its method accepts can be matched up with the arguments in the
+produced type. The following two examples would also allow constructing the `Greeter` type:
+
+ interface GreeterFactory {
+ Greeter createGreeter(LocalTime timeOfDay);
+ }
+
+... and:
+
+ abstract class GreeterFactory {
+ Greeter createGreeter(LocalTime timeOfDay);
+ }
# BSD License
@@ -421,7 +552,7 @@ https://github.com/ronmamo/reflections
Linking: Maven Dependency
-Usage:
+Used for:
- Scanning classpath for annotations
### Geantyref
@@ -431,7 +562,7 @@ https://github.com/leangen/geantyref
Linking: Maven Dependency
-Usage:
+Used for:
- Implementation of Annotation interface
### Apache Commons Lang
@@ -441,7 +572,7 @@ https://commons.apache.org/proper/commons-lang/
Linking: Embedded (minimal required code directly included)
-Usage:
+Used for:
- `TypeUtils` for dealing with generic types and type variables
### Byte Buddy
@@ -451,5 +582,6 @@ https://bytebuddy.net/
Linking: Maven Dependency (optional)
-Usage:
+Used for:
- Generating factories for Assisted Injection
+- Generating proxies