Dependency Injection
Log4j 3.x extends its plugin system into a more general dependency injection API inspired by javax.inject
and its open source implementations such as Feather and Guice along with Spring Framework and BeanFactory
API.
Dependency injection can be thought of as a replacement for calling new
or custom factory methods in Java code; alternatively, it can be thought of as a factory for factory instances.
Classes define injection points which may be fields, method parameters, and constructor parameters, whose values are injected at runtime.
Classes may define factory methods which provide a way to create instances of their return type while allowing for dependency injection of the factory method’s arguments.
Instance factories may be customized through post-processor service classes which is how much of its functionality is defined.
Overview
The org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory
runtime keeps a registry of bindings between Key<T>
and Supplier<T>
, where org.apache.logging.log4j.plugins.di.Key<T>
describes an instance type T
, optional qualifier annotation type, and optional name plus namespace, and java.util.function.Supplier<T>
defines a factory for obtaining instances of T
matching the corresponding Key<T>
.
To obtain a factory for a given key, if a binding is not already registered for said key, then the raw class of the key (i.e., the Class<T>
instance from Key<T>
) is checked for a single constructor annotated with @org.apache.logging.log4j.plugins.Inject
or a no-arg constructor if no @Inject
constructor is present.
The injectable constructor is invoked with arguments which are obtained by looking up factories matching the Key<?>
of each constructor parameter.
Then, fields that are annotated with @Inject
or annotated with an annotation that is itself annotated with @QualifierType
(i.e., a qualifier annotation) are injected from factories matching the Key<?>
of the field.
Next, methods that are annotated with @Inject
or have a parameter annotated with a qualifier annotation are invoked using factories matching the Key<?>
of each parameter.
Lastly, no-arg methods annotated with @Inject
are invoked which can be useful for post-injection initialization logic.
Additional strategies for resolving factories for unbound keys may be registered which are consulted first before falling back to this @Inject
reflection logic.
Bindings may be registered using ConfigurableInstanceFactory::registerBinding
or ConfigurableInstanceFactory::registerBindingIfAbsent
using explicit Binding<T>
instances, or they may be registered using ConfigurableInstanceFactory::registerBundle
as bundles which are instances or injectable Class<T>
instances that contain one or more annotated factory methods.
Annotated factory methods are non-abstract methods annotated with an annotation that is itself annotated with @org.apache.logging.log4j.plugins.FactoryType
.
Built-in factory annotations include @org.apache.logging.log4j.plugins.Factory
and @org.apache.logging.log4j.plugins.PluginFactory
.
ConfigurableInstanceFactory
can create @Configurable
namespace plugins from a tree of org.apache.logging.log4j.plugins.Node
instances using org.apache.logging.log4j.core.config.ConfigurationProcessor
.
This allows for node attributes and child nodes to be injected into a plugin class along with any other registered bindings.
To configure a Node
, its plugin class is checked for a static method annotated with a factory annotation (such as @PluginFactory
or @Factory
) first before falling back to checking for an @Inject
constructor or a no-args constructor.
This static factory method or constructor has its parameters injected.
If the return value of a static plugin factory method implements java.util.function.Supplier
, then member injection is performed on the instance and the return value of Supplier::get
is returned instead.
See Plugins for more information.
ConfigurableInstanceFactory
can inject members into an arbitrary object instance by using ConfigurableInstanceFactory::injectMembers
.
This can be useful for injecting Log4j-managed instances into external or application code.
An ConfigurableInstanceFactory
can be obtained via LoggerContext::getInstanceFactory
.
Injection Points
Injection points are injectable fields or parameters where a dependency should be injected.
Injectable fields are fields annotated with @Inject
or a qualifier annotation.
Injectable methods are methods annotated with @Inject
or are not annotated with a factory annotation and have at least one parameter annotated with a qualifier annotation.
Injectable constructors are constructors annotated with @Inject
; only one such constructor should exist per class.
When a field or parameter is annotated with a name-providing annotation (i.e., an annotation annotated with @org.apache.logging.log4j.plugins.name.NameProvider
), then the provided name or name of the field or parameter are included in the Key<T>
for the injection point.
When these elements are annotated with a @Namespace
annotation or meta-annotation, then that namespace name is included in the Key<T>
for the injection point.
Similarly, when a field or parameter is annotated with a qualifier annotation, then that qualifier annotation type is included in the Key<T>
for the injection point.
An example of each injection point with a no-arg @Inject
method to show the post-injected state:
class ExampleBean {
@Override
public String toString() {
return "ExampleBean";
}
}
class FieldInjection {
@Inject
ExampleBean exampleBean;
@Inject
void init() {
System.out.println(exampleBean);
}
}
class ConstructorInjection {
final ExampleBean bean;
@Inject
ConstructorInjection(final ExampleBean exampleBean) {
bean = exampleBean;
}
@Inject
void init() {
System.out.println(exampleBean);
}
}
class MethodInjection {
ExampleBean bean;
@Inject
void setBean(final ExampleBean exampleBean) {
bean = exampleBean;
}
@Inject
void init() {
System.out.println(exampleBean);
}
}
Names and Qualifiers
Qualifiers are annotations that are annotated with @org.apache.logging.log4j.plugins.QualifierType
.
Qualifiers provide a way to match dependencies and factories based on more than just their type.
For example, the @org.apache.logging.log4j.plugins.Named
qualifier allows for creating different bindings of the same type with different names (along with support for aliases).
Qualifiers on an injection point request a binding with that qualifier type and name.
Qualifiers on a factory method register a binding with that qualifier type and name.
The name for a qualifier is provided via an org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider
strategy class given in the @NameProvider
annotation declared on the qualifier annotation.
Aliases are likewise provided via an org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider
strategy class given in the @AliasesProvider
annotated declared on the qualifier annotation.
Scopes
Scopes control the lifecycle of instances returned from a factory.
Scopes correspond to an annotation that is annotated with @org.apache.logging.log4j.plugins.ScopeType
.
By default, @org.apache.logging.log4j.plugins.Singleton
and the default scope are supported.
The @Singleton
scope ensures that only a single instance is returned from factories bound in that scope.
The default scope returns a new instance every time.
Additional scopes may be registered via ConfigurableInstanceFactory::registerScope
.
Injector Callbacks
An ConfigurableInstanceFactory
may be initialized with org.apache.logging.log4j.plugins.di.spi.ConfigurableInstanceFactoryPostProcessor
service classes.
These service classes must be declared in their respective module-info.java
files containing provides org.apache.logging.log4j.plugins.di.spi.ConfigurableInstanceFactoryPostProcessor with my.fully.qualified.ClassName;
and should also be declared in a file named META-INF/services/org.apache.logging.log4j.plugins.di.spi.ConfigurableInstanceFactoryPostProcessor
containing the line my.fully.qualified.ClassName
for traditional classpath usage.
Post-processor services are invoked in the order defined by the @Order
annotation on the class.
Each post-processor is given the ConfigurableInstanceFactory
being initialized where it can be introspected and modified.
Configurable Bindings
The default callback sets up bindings for the following keys if none have been registered. Some of these bindings were previously configured through various system properties which are supported via the default callback and its default bindings, though they can be directly registered via custom callbacks with a negative order value.
-
org.apache.logging.log4j.core.ContextDataInjector
-
org.apache.logging.log4j.core.config.ConfigurationFactory
-
org.apache.logging.log4j.core.config.composite.MergeStrategy
-
org.apache.logging.log4j.core.impl.LogEventFactory
-
org.apache.logging.log4j.core.lookup.InterpolatorFactory
-
org.apache.logging.log4j.core.lookup.StrSubstitutor
-
org.apache.logging.log4j.core.selector.ContextSelector
-
org.apache.logging.log4j.core.time.Clock
-
org.apache.logging.log4j.core.time.NanoClock
-
org.apache.logging.log4j.core.util.ShutdownCallbackRegistry
-
org.apache.logging.log4j.core.util.WatchManager
-
org.apache.logging.log4j.core.config.ConfigurationScheduler