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