Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • @Inject - marks a constructor, field, or method for injection. Classes that use this annotation can be obtained from an Injector which handles binding runtime instances to each of the injection points. Marking a no-arg method with @Inject is effectively an initialization method as these are invoked last. Injection starts with the constructor, then fields, then methods (methods with arguments executed first before no-arg methods). Inject methods must not be abstract.

    • Code Block
      languagejava
      class InjectExample {
      	DependencyA dependencyA;
          @Inject DependencyB dependencyB;
       	DependencyC dependencyC;
      
      	@Inject
      	InjectExample(DependencyA a) {
      		dependencyA = a;
      	}
      
      	@Inject
      	void setC(DependencyC c) {
      		dependencyC = c;
      	}
      
      	@Inject
      	void postConstruct() {
      		StatusLogger.getLogger().debug("Finished injecting everything");
      	}
      }


  • @QualifierType - marks an annotation as a qualifier annotation. Qualifiers are used for providing multiple variants of the same type which can be identified by the qualifier annotation. Qualifiers can be specified on a class, an inject method parameter, a factory method, and an inject field.
  • @Named - qualifier for a named instance. This annotation can specify alias names, too (similar to the existing @PluginAliases annotation).

    • Code Block
      class QualifierExample {
      	@Named String foo; // uses field name by default for value in @Named
      	@Named({"primary", "legacy-name"}) String bar; // inline use of name plus aliases
      }


  • @ScopeType - marks an annotation as a scope annotation. Scopes provide a strategy for determining the lifecycle of instances in its scope. Without a scope, instances are created fresh each time they're injected or obtained from Injector.
  • @Singleton - scope type that maintains at most one instance for each qualified type. That is, every injected singleton instance and every singleton instance obtained from Injector will be the same instance.
  • @FactoryType - marks an annotation as a factory type. Factory annotations are used for marking methods as factories for the returned instance of that method. This is intended to support existing plugin factory related annotations like @PluginFactory and @PluginBuilderFactory.
  • @Factory - marks a method as a factory for instances it returns. Factory methods can be static or not, though instance factory methods may only be installed by installing an instance of the class with said factory method.

    • Code Block
      class FactoryExample {
      	@Inject Dependency dep;
      
      	@Factory
      	ProducedInstance newInstance(@Named String name) {
      		return new ProducedInstance(name);
      	}
      }

      Then, the load and install using an anonymous class for an installation module.

      Code Block
      Injector injector = DI.createInjector(new Object() {
      	@Factory
      	@Named
      	String getName() { return "example"; }
      }, FactoryExample.class);
      
      var producedInstance = injector.getInstance(ProducedInstance.class);


Another API change is replacing existing Builder<T> classes with java.util.function.Supplier<T>. To support backward compatibility, when a static factory method returns an object that implements Builder<T> or Supplier<T>, then that instance is injected and used as a factory for its type T.

The Core plugin type is updated to use this injection API so that in addition to the existing support for injecting @PluginElement, @PluginAttribute, @PluginBuilderAttribute, @PluginValue, @PluginNode, and @PluginConfiguration instances, this is extended to support for injection via fields, methods, and constructors, along with injection of any other instances Injector has bindings for or knows how to create bindings on demand for. Classes that can have on-demand bindings are injectable classes which are classes with either one @Inject constructor or a no-args constructor. Implementations of LogEventFactory are a good example of injectable classes where the choice of class is configurable at runtime, though the dependency chain involved can be made explicit while removing boilerplate dependency injection code to where this class is relevant. An abbreviated example of what a Supplier<LoggerConfig> class may look like:

Code Block
public class Builder implements java.util.function.Supplier<LoggerConfig> {
    // ...

    // note that methods with qualified parameters are implicitly @Inject
    public Builder withLevel(@PluginAttribute Level level) {
        this.level = level;
        return this;
    }

    public Builder withLoggerName(
            @Required(message = "Loggers cannot be configured without a name") @PluginAttribute String name) {
        this.loggerName = name;
        return this;
    }

    public Builder withRefs(@PluginElement AppenderRef[] refs) {
        this.refs = refs;
        return this;
    }

    public Builder withConfig(@PluginConfiguration Configuration config) {
        this.config = config;
        return this;
    }

    public Builder withFilter(@PluginElement Filter filter) {
        this.filter = filter;
        return this;
    }

    // need to specify @Inject here because LogEventFactory is an unqualified bean
    @Inject
    public Builder setLogEventFactory(LogEventFactory logEventFactory) {
        this.logEventFactory = logEventFactory;
        return this;
    }

    @Override
    public LoggerConfig get() {
        // ...
    }
}


Keys and Bindings

The Key<T> class provides a way to identify plugins by type, optional qualifier, and a name. Existing @PluginAttribute and @PluginValue annotations are natural qualifier types, though given that these annotations are duplicated in log4j-plugins and log4j-core, a more generic mechanism is needed to treat these as equivalent. This is where the name aspect of a Key comes in; all named-style qualifier annotations are treated as @Named qualifiers. The ConfigurationInjector and ConfigurationBinder API in log4j-plugins is replaced with a simpler strategy for supplying a (possibly converted) value for a particular Node instance. Nodes can be configured via Injector which is where general dependency injection occurs along with binding of provided configuration attributes in the Node instance. Annotation-handling strategies for configuration injection are replaced with a parsing strategy while strategies for binding are inlined into the general logic of Injector.

...