You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@logging.apache.org by Matt Sicker <ma...@apache.org> on 2019/10/06 18:32:39 UTC

Re: [logging-log4j2] 04/04: Extract binder, injector, and name provider APIs

Might be easier to review this via GitHub:
https://github.com/apache/logging-log4j2/commit/a1c3c7829e2fe3edea415a25121e31fb0afc0f1a

On Sun, 6 Oct 2019 at 13:29, <ma...@apache.org> wrote:
>
> This is an automated email from the ASF dual-hosted git repository.
>
> mattsicker pushed a commit to branch master
> in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
>
> commit a1c3c7829e2fe3edea415a25121e31fb0afc0f1a
> Author: Matt Sicker <bo...@gmail.com>
> AuthorDate: Sun Oct 6 13:25:49 2019 -0500
>
>     Extract binder, injector, and name provider APIs
>
>     This refactors some of the strategies used for configuring and
>     instantiating plugins. The previously named
>     ConfigurationInjectionBuilder classes have been renamed to be
>     ConfigurationInjector as they are no longer builder classes.
>     This API has been simplified to find an appropriate injector
>     given an AnnotatedElement which can automatically set both
>     the AnnotatedElement and the Annotation. Conversion types have
>     been weakened to use java.lang.reflect.Type rather than Class.
>     This introduces a new plugin SPI named OptionBinder for
>     abstracting the binding strategies previously used in the
>     massive PluginBuilder class as desired by LOG4J2-860. This SPI
>     is extended to include support for method-based configuration
>     injection into plugin builder classes as specified in
>     LOG4J2-2700. This introduces another plugin SPI for specifying
>     the name of an annotated element rather than relying on Java
>     reflection to calculate the name. This SPI also fixes
>     LOG4J2-2693.
>
>     Signed-off-by: Matt Sicker <bo...@gmail.com>
> ---
>  .../org/apache/logging/log4j/util/Strings.java     |  11 ++
>  .../log4j/core/config/AbstractConfiguration.java   |   3 +-
>  .../log4j/core/config/plugins/PluginAttribute.java |  11 +-
>  .../config/plugins/PluginBuilderAttribute.java     |  10 +-
>  .../core/config/plugins/PluginConfiguration.java   |  15 +-
>  .../log4j/core/config/plugins/PluginElement.java   |  10 +-
>  .../log4j/core/config/plugins/PluginNode.java      |  10 +-
>  .../log4j/core/config/plugins/PluginValue.java     |  11 +-
>  .../inject/PluginConfigurationInjector.java        |  23 +++
>  .../core/config/plugins/util/PluginBuilder.java    | 167 +++++++-------------
>  .../plugins/visitors/AbstractPluginVisitor.java    |  45 ------
>  .../plugins/visitors/PluginAttributeVisitor.java   |  96 ++++++-----
>  .../visitors/PluginBuilderAttributeVisitor.java    |  31 ++--
>  .../visitors/PluginConfigurationVisitor.java       |  44 ------
>  .../plugins/visitors/PluginElementVisitor.java     |  99 ++++++------
>  .../config/plugins/visitors/PluginNodeVisitor.java |  19 +--
>  .../plugins/visitors/PluginValueVisitor.java       |  18 +--
>  .../core/config/plugins/visitors/package-info.java |   6 +-
>  .../logging/log4j/plugins/PluginAliases.java       |   8 +-
>  .../logging/log4j/plugins/PluginAttribute.java     |  39 +++--
>  .../log4j/plugins/PluginBuilderAttribute.java      |  13 +-
>  .../log4j/plugins/PluginBuilderFactory.java        |   4 +-
>  .../logging/log4j/plugins/PluginElement.java       |  26 ++-
>  .../logging/log4j/plugins/PluginFactory.java       |   9 +-
>  .../apache/logging/log4j/plugins/PluginNode.java   |  12 +-
>  .../apache/logging/log4j/plugins/PluginValue.java  |  27 +++-
>  .../log4j/plugins/bind/AbstractOptionBinder.java   |  62 ++++++++
>  .../log4j/plugins/bind/FactoryMethodBinder.java    |  58 +++++++
>  .../log4j/plugins/bind/FieldOptionBinder.java      |  36 +++++
>  .../log4j/plugins/bind/MethodOptionBinder.java     |  26 +++
>  .../logging/log4j/plugins/bind/OptionBinder.java   |   7 +
>  .../log4j/plugins/bind/OptionBindingException.java |  24 +++
>  .../AbstractConfigurationInjectionBuilder.java     | 175 ---------------------
>  .../inject/AbstractConfigurationInjector.java      | 118 ++++++++++++++
>  .../inject/ConfigurationInjectionBuilder.java      | 105 -------------
>  .../plugins/inject/ConfigurationInjector.java      |  48 ++++++
>  .../log4j/plugins/inject/InjectionStrategy.java    |  42 -----
>  .../log4j/plugins/inject/InjectorStrategy.java     |  16 ++
>  .../plugins/inject/PluginAttributeBuilder.java     |  81 ----------
>  .../plugins/inject/PluginAttributeInjector.java    |  72 +++++++++
>  .../inject/PluginBuilderAttributeBuilder.java      |  49 ------
>  .../inject/PluginBuilderAttributeInjector.java     |  19 +++
>  .../log4j/plugins/inject/PluginElementBuilder.java | 110 -------------
>  .../plugins/inject/PluginElementInjector.java      |  88 +++++++++++
>  .../log4j/plugins/inject/PluginNodeBuilder.java    |  39 -----
>  .../log4j/plugins/inject/PluginNodeInjector.java   |  17 ++
>  .../log4j/plugins/inject/PluginValueBuilder.java   |  52 ------
>  .../log4j/plugins/inject/PluginValueInjector.java  |  27 ++++
>  .../logging/log4j/plugins/inject/package-info.java |   4 +-
>  .../plugins/name/AnnotatedElementNameProvider.java |  69 ++++++++
>  .../logging/log4j/plugins/name/NameProvider.java   |  15 ++
>  .../plugins/name/PluginAttributeNameProvider.java  |  13 ++
>  .../name/PluginBuilderAttributeNameProvider.java   |  13 ++
>  .../plugins/name/PluginElementNameProvider.java    |  13 ++
>  .../plugins/name/PluginValueNameProvider.java      |  13 ++
>  .../apache/logging/log4j/plugins/package-info.java |   3 +
>  .../plugins/validation/constraints/Required.java   |   2 +-
>  .../plugins/validation/constraints/ValidHost.java  |   2 +-
>  .../plugins/validation/constraints/ValidPort.java  |   2 +-
>  src/changes/changes.xml                            |   9 ++
>  60 files changed, 1142 insertions(+), 1054 deletions(-)
>
> diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
> index e285502..f236b61 100644
> --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
> +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
> @@ -19,6 +19,7 @@ package org.apache.logging.log4j.util;
>  import java.util.Iterator;
>  import java.util.Locale;
>  import java.util.Objects;
> +import java.util.Optional;
>
>  /**
>   * <em>Consider this class private.</em>
> @@ -214,6 +215,16 @@ public final class Strings {
>      }
>
>      /**
> +     * Removes control characters from both ends of this String returning {@code Optional.empty()} if the String is
> +     * empty ("") after the trim or if it is {@code null}.
> +     *
> +     * @see #trimToNull(String)
> +     */
> +    public static Optional<String> trimToOptional(final String str) {
> +        return Optional.ofNullable(str).map(String::trim).filter(s -> !s.isEmpty());
> +    }
> +
> +    /**
>       * <p>Joins the elements of the provided {@code Iterable} into
>       * a single String containing the provided elements.</p>
>       *
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
> index 371ceb9..47794fa 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
> @@ -63,7 +63,6 @@ import org.apache.logging.log4j.core.script.ScriptRef;
>  import org.apache.logging.log4j.core.util.Constants;
>  import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
>  import org.apache.logging.log4j.core.util.Loader;
> -import org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder;
>  import org.apache.logging.log4j.util.NameUtil;
>  import org.apache.logging.log4j.core.util.Source;
>  import org.apache.logging.log4j.core.time.NanoClock;
> @@ -971,7 +970,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
>       * @param event the LogEvent that spurred the creation of this plugin
>       * @return the created plugin object or {@code null} if there was an error setting it up.
>       * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
> -     * @see ConfigurationInjectionBuilder
> +     * @see org.apache.logging.log4j.plugins.inject.ConfigurationInjector
>       * @see org.apache.logging.log4j.plugins.convert.TypeConverter
>       */
>      private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
> index a5595cf..e75de09 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
> @@ -16,11 +16,15 @@
>   */
>  package org.apache.logging.log4j.core.config.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
>  import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>  import org.apache.logging.log4j.util.Strings;
>
> -import java.lang.annotation.*;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
>
>  /**
>   * Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be
> @@ -33,7 +37,7 @@ import java.lang.annotation.*;
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginAttributeVisitor.class)
> +@InjectorStrategy(PluginAttributeVisitor.class)
>  public @interface PluginAttribute {
>
>      /**
> @@ -86,7 +90,6 @@ public @interface PluginAttribute {
>       */
>      String defaultString() default Strings.EMPTY;
>
> -    // TODO: could we allow a blank value and infer the attribute name through reflection?
>      /**
>       * Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
>       */
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
> index 584d5f8..17ad890 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
> @@ -17,11 +17,15 @@
>
>  package org.apache.logging.log4j.core.config.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
>  import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>  import org.apache.logging.log4j.util.Strings;
>
> -import java.lang.annotation.*;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
>
>  /**
>   * Marks a field as a Plugin Attribute.
> @@ -30,7 +34,7 @@ import java.lang.annotation.*;
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginBuilderAttributeVisitor.class)
> +@InjectorStrategy(PluginBuilderAttributeVisitor.class)
>  public @interface PluginBuilderAttribute {
>
>      /**
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
> index e941388..dee6f67 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
> @@ -16,23 +16,24 @@
>   */
>  package org.apache.logging.log4j.core.config.plugins;
>
> +import org.apache.logging.log4j.core.config.plugins.inject.PluginConfigurationInjector;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +
>  import java.lang.annotation.Documented;
>  import java.lang.annotation.ElementType;
>  import java.lang.annotation.Retention;
>  import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
> -import org.apache.logging.log4j.core.config.plugins.visitors.PluginConfigurationVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -
>  /**
> - * Identifies a parameter or field as a Configuration.
> - * @see org.apache.logging.log4j.core.config.Configuration
> + * Identifies the current {@link org.apache.logging.log4j.core.config.Configuration}. This can be injected as a
> + * parameter to a static {@linkplain org.apache.logging.log4j.plugins.PluginFactory factory method}, or as a field
> + * or single-parameter method in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginConfigurationVisitor.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginConfigurationInjector.class)
>  public @interface PluginConfiguration {
>      // empty
>  }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
> index 81c5a47..5c4f41e 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
> @@ -17,9 +17,13 @@
>  package org.apache.logging.log4j.core.config.plugins;
>
>  import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>
> -import java.lang.annotation.*;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
>
>  /**
>   * Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files.
> @@ -28,7 +32,7 @@ import java.lang.annotation.*;
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginElementVisitor.class)
> +@InjectorStrategy(PluginElementVisitor.class)
>  public @interface PluginElement {
>
>      /**
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
> index 3411a12..14a136c 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
> @@ -17,9 +17,13 @@
>  package org.apache.logging.log4j.core.config.plugins;
>
>  import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>
> -import java.lang.annotation.*;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
>
>  /**
>   * Identifies a Plugin configuration Node.
> @@ -28,7 +32,7 @@ import java.lang.annotation.*;
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginNodeVisitor.class)
> +@InjectorStrategy(PluginNodeVisitor.class)
>  public @interface PluginNode {
>      // empty
>  }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
> index 0a93acb..9389aa9 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
> @@ -17,10 +17,13 @@
>  package org.apache.logging.log4j.core.config.plugins;
>
>  import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>
> -
> -import java.lang.annotation.*;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
>
>  /**
>   * Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be
> @@ -32,7 +35,7 @@ import java.lang.annotation.*;
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginValueVisitor.class)
> +@InjectorStrategy(PluginValueVisitor.class)
>  public @interface PluginValue {
>
>      String value();
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java
> new file mode 100644
> index 0000000..6148f42
> --- /dev/null
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java
> @@ -0,0 +1,23 @@
> +package org.apache.logging.log4j.core.config.plugins.inject;
> +
> +import org.apache.logging.log4j.core.config.Configuration;
> +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
> +import org.apache.logging.log4j.core.util.TypeUtil;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
> +
> +public class PluginConfigurationInjector extends AbstractConfigurationInjector<PluginConfiguration, Configuration> {
> +    @Override
> +    public Object inject(final Object target) {
> +        if (TypeUtil.isAssignable(conversionType, configuration.getClass())) {
> +            debugLog.append("Configuration");
> +            if (configuration.getName() != null) {
> +                debugLog.append('(').append(configuration.getName()).append(')');
> +            }
> +            return optionBinder.bindObject(target, configuration);
> +        } else {
> +            LOGGER.warn("Element with type {} annotated with @PluginConfiguration is not compatible with type {}.",
> +                    conversionType, configuration.getClass());
> +            return target;
> +        }
> +    }
> +}
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
> index b59c538..7624de6 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
> @@ -26,12 +26,13 @@ import org.apache.logging.log4j.plugins.Node;
>  import org.apache.logging.log4j.plugins.PluginAliases;
>  import org.apache.logging.log4j.plugins.PluginBuilderFactory;
>  import org.apache.logging.log4j.plugins.PluginFactory;
> -import org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder;
> +import org.apache.logging.log4j.plugins.bind.FactoryMethodBinder;
> +import org.apache.logging.log4j.plugins.bind.FieldOptionBinder;
> +import org.apache.logging.log4j.plugins.bind.MethodOptionBinder;
> +import org.apache.logging.log4j.plugins.inject.ConfigurationInjector;
>  import org.apache.logging.log4j.plugins.util.Builder;
>  import org.apache.logging.log4j.plugins.util.PluginType;
>  import org.apache.logging.log4j.plugins.util.TypeUtil;
> -import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
> -import org.apache.logging.log4j.plugins.validation.ConstraintValidators;
>  import org.apache.logging.log4j.status.StatusLogger;
>  import org.apache.logging.log4j.util.ReflectionUtil;
>  import org.apache.logging.log4j.util.StringBuilders;
> @@ -42,11 +43,9 @@ import java.lang.reflect.Field;
>  import java.lang.reflect.InvocationTargetException;
>  import java.lang.reflect.Method;
>  import java.lang.reflect.Modifier;
> -import java.util.Collection;
>  import java.util.List;
>  import java.util.Map;
>  import java.util.Objects;
> -import java.util.Optional;
>  import java.util.concurrent.ConcurrentHashMap;
>  import java.util.concurrent.ConcurrentMap;
>  import java.util.function.Function;
> @@ -126,26 +125,21 @@ public class PluginBuilder implements Builder<Object> {
>                      pluginType.getPluginClass().getName());
>              final Builder<?> builder = createBuilder(this.clazz);
>              if (builder != null) {
> -                injectFields(builder);
> -                return builder.build();
> +                return injectBuilder(builder);
>              }
>          } catch (final ConfigurationException e) { // LOG4J2-1908
>              LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e);
>              return null; // no point in trying the factory method
>          } catch (final Exception e) {
> -            LOGGER.error("Could not create plugin of type {} for element {}: {}",
> -                    this.clazz, node.getName(),
> -                    (e instanceof InvocationTargetException ? e.getCause() : e).toString(), e);
> +            LOGGER.error("Could not create plugin of type {} for element {}: {}", clazz, node.getName(),
> +                    e.toString(), e);
>          }
>          // or fall back to factory method if no builder class is available
>          try {
> -            final Method factory = findFactoryMethod(this.clazz);
> -            final Object[] params = generateParameters(factory);
> -            return factory.invoke(null, params);
> -        } catch (final Exception e) {
> -            LOGGER.error("Unable to invoke factory method in {} for element {}: {}",
> -                    this.clazz, this.node.getName(),
> -                    (e instanceof InvocationTargetException ? e.getCause() : e).toString(), e);
> +            return injectFactoryMethod(findFactoryMethod(this.clazz));
> +        } catch (final Throwable e) {
> +            LOGGER.error("Could not create plugin of type {} for element {}: {}", clazz, node.getName(),
> +                    e.toString(), e);
>              return null;
>          }
>      }
> @@ -158,7 +152,7 @@ public class PluginBuilder implements Builder<Object> {
>      private static Builder<?> createBuilder(final Class<?> clazz)
>          throws InvocationTargetException, IllegalAccessException {
>          for (final Method method : clazz.getDeclaredMethods()) {
> -            if (method.isAnnotationPresent(PluginBuilderFactory.class) &&
> +            if ((method.isAnnotationPresent(PluginFactory.class) || method.isAnnotationPresent(PluginBuilderFactory.class)) &&
>                  Modifier.isStatic(method.getModifiers()) &&
>                  TypeUtil.isAssignable(Builder.class, method.getReturnType())) {
>                  ReflectionUtil.makeAccessible(method);
> @@ -173,62 +167,47 @@ public class PluginBuilder implements Builder<Object> {
>          return null;
>      }
>
> -    private void injectFields(final Builder<?> builder) throws IllegalAccessException {
> +    private Object injectBuilder(final Builder<?> builder) {
>          final Object target = builder instanceof BuilderWrapper ? ((BuilderWrapper) builder).getBuilder() : builder;
>          final List<Field> fields = TypeUtil.getAllDeclaredFields(target.getClass());
> -        AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true);
> +        AccessibleObject.setAccessible(fields.toArray(new Field[0]), true);
>          final StringBuilder log = new StringBuilder();
> -        boolean invalid = false;
> -        StringBuilder reason = new StringBuilder();
> +        // TODO: collect OptionBindingExceptions into a composite error message (ConfigurationException?)
>          for (final Field field : fields) {
> -            log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
> -            final Annotation[] annotations = field.getDeclaredAnnotations();
> -            final String[] aliases = extractPluginAliases(annotations);
> -            for (Annotation a : annotations) {
> -                if (a instanceof PluginAliases ||
> -                        a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
> -                    continue; // already processed
> -                }
> -                final Object value = ConfigurationInjectionBuilder.findBuilderForInjectionStrategy(a.annotationType())
> -                        .flatMap(b -> Optional.ofNullable(b
> -                                .withAliases(aliases)
> -                                .withAnnotation(a)
> -                                .withConfiguration(configuration)
> -                                .withConfigurationNode(node)
> -                                .withConversionType(field.getType())
> -                                .withDebugLog(log)
> -                                .withMember(field)
> -                                .withStringSubstitutionStrategy(substitutor)
> -                                .build()))
> -                        .orElse(null);
> -                if (value != null) {
> -                    field.set(target, value);
> -                }
> -            }
> -            final Collection<ConstraintValidator<?>> validators =
> -                ConstraintValidators.findValidators(annotations);
> -            final Object value = field.get(target);
> -            for (final ConstraintValidator<?> validator : validators) {
> -                if (!validator.isValid(field.getName(), value)) {
> -                    invalid = true;
> -                    if (reason.length() > 0) {
> -                        reason.append(", ");
> -                    } else {
> -                        reason.append("Arguments given for element ").append(node.getName())
> -                            .append(" are invalid: ");
> -                    }
> -                    reason.append("field '").append(field.getName()).append("' has invalid value '")
> -                        .append(value).append("'");
> +            ConfigurationInjector.forAnnotatedElement(field).ifPresent(injector -> {
> +                log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
> +                injector.withAliases(extractPluginAliases(field.getAnnotations()))
> +                        .withConversionType(field.getGenericType())
> +                        .withOptionBinder(new FieldOptionBinder(field))
> +                        .withDebugLog(log)
> +                        .withStringSubstitutionStrategy(substitutor)
> +                        .withConfiguration(configuration)
> +                        .withNode(node)
> +                        .inject(target);
> +            });
> +        }
> +        // TODO: tests
> +        for (final Method method : target.getClass().getMethods()) {
> +            ConfigurationInjector.forAnnotatedElement(method).ifPresent(injector -> {
> +                if (method.getParameterCount() != 1) {
> +                    throw new IllegalArgumentException("Cannot inject to a plugin builder method with parameter count other than 1");
>                  }
> -            }
> +                log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
> +                injector.withAliases(extractPluginAliases(method.getAnnotations()))
> +                        .withConversionType(method.getGenericParameterTypes()[0])
> +                        .withOptionBinder(new MethodOptionBinder(method))
> +                        .withDebugLog(log)
> +                        .withStringSubstitutionStrategy(substitutor)
> +                        .withConfiguration(configuration)
> +                        .withNode(node)
> +                        .inject(target);
> +            });
>          }
>          log.append(log.length() == 0 ? builder.getClass().getSimpleName() + "()" : ")");
>          LOGGER.debug(log.toString());
> -        if (invalid) {
> -            throw new ConfigurationException(reason.toString());
> -        }
>          checkForRemainingAttributes();
>          verifyNodeChildrenUsed();
> +        return builder.build();
>      }
>
>      /**
> @@ -255,60 +234,30 @@ public class PluginBuilder implements Builder<Object> {
>          throw new IllegalStateException("No factory method found for class " + clazz.getName());
>      }
>
> -    private Object[] generateParameters(final Method factory) {
> +    private Object injectFactoryMethod(final Method factory) throws Throwable {
>          final StringBuilder log = new StringBuilder();
> -        final Class<?>[] types = factory.getParameterTypes();
> -        final Annotation[][] annotations = factory.getParameterAnnotations();
> -        final Object[] args = new Object[annotations.length];
> -        boolean invalid = false;
> -        for (int i = 0; i < annotations.length; i++) {
> +        final FactoryMethodBinder binder = new FactoryMethodBinder(factory);
> +        binder.forEachParameter((parameter, optionBinder) -> {
>              log.append(log.length() == 0 ? factory.getName() + "(" : ", ");
> -            final String[] aliases = extractPluginAliases(annotations[i]);
> -            final Class<?> conversionType = types[i];
> -            for (final Annotation a : annotations[i]) {
> -                if (a instanceof PluginAliases ||
> -                        a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
> -                    continue; // already processed
> -                }
> -                final Object value = ConfigurationInjectionBuilder.findBuilderForInjectionStrategy(a.annotationType())
> -                        .flatMap(b -> Optional.ofNullable(b
> -                                .withAliases(aliases)
> -                                .withAnnotation(a)
> -                                .withConfiguration(configuration)
> -                                .withConfigurationNode(node)
> -                                .withConversionType(conversionType)
> -                                .withDebugLog(log)
> -                                .withMember(factory)
> -                                .withStringSubstitutionStrategy(substitutor)
> -                                .build()))
> -                        .orElse(null);
> -                // don't overwrite existing values if the builder gives us no value to inject
> -                if (value != null) {
> -                    args[i] = value;
> -                }
> -            }
> -            final Collection<ConstraintValidator<?>> validators =
> -                ConstraintValidators.findValidators(annotations[i]);
> -            final Object value = args[i];
> -            final String argName = "arg[" + i + "](" + simpleName(value) + ")";
> -            for (final ConstraintValidator<?> validator : validators) {
> -                if (!validator.isValid(argName, value)) {
> -                    invalid = true;
> -                }
> -            }
> -        }
> +            ConfigurationInjector.forAnnotatedElement(parameter).ifPresent(injector -> injector
> +                            .withAliases(extractPluginAliases(parameter.getAnnotations()))
> +                            .withConversionType(parameter.getParameterizedType())
> +                            .withOptionBinder(optionBinder)
> +                            .withDebugLog(log)
> +                            .withStringSubstitutionStrategy(substitutor)
> +                            .withConfiguration(configuration)
> +                            .withNode(node)
> +                            .inject(binder));
> +        });
>          log.append(log.length() == 0 ? factory.getName() + "()" : ")");
>          checkForRemainingAttributes();
>          verifyNodeChildrenUsed();
>          LOGGER.debug(log.toString());
> -        if (invalid) {
> -            throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
> -        }
> -        return args;
> +        return binder.invoke();
>      }
>
>      private static String[] extractPluginAliases(final Annotation... parmTypes) {
> -        String[] aliases = null;
> +        String[] aliases = {};
>          for (final Annotation a : parmTypes) {
>              if (a instanceof PluginAliases) {
>                  aliases = ((PluginAliases) a).value();
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java
> deleted file mode 100644
> index b812459..0000000
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java
> +++ /dev/null
> @@ -1,45 +0,0 @@
> -package org.apache.logging.log4j.core.config.plugins.visitors;
> -
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjectionBuilder;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.reflect.Member;
> -import java.util.function.Function;
> -
> -abstract class AbstractPluginVisitor<Ann extends Annotation, Cfg> extends AbstractConfigurationInjectionBuilder<Ann, Cfg> {
> -
> -    AbstractPluginVisitor(final Class<Ann> clazz) {
> -        super(clazz);
> -    }
> -
> -    public AbstractPluginVisitor<Ann, Cfg> setAnnotation(final Annotation annotation) {
> -        if (clazz.isInstance(annotation)) {
> -            withAnnotation(clazz.cast(annotation));
> -        }
> -        return this;
> -    }
> -
> -    public AbstractPluginVisitor<Ann, Cfg> setAliases(final String... aliases) {
> -        withAliases(aliases);
> -        return this;
> -    }
> -
> -    public AbstractPluginVisitor<Ann, Cfg> setConversionType(final Class<?> conversionType) {
> -        withConversionType(conversionType);
> -        return this;
> -    }
> -
> -    public AbstractPluginVisitor<Ann, Cfg> setMember(final Member member) {
> -        withMember(member);
> -        return this;
> -    }
> -
> -    public Object visit(final Cfg configuration, final Node node, final Function<String, String> substitutor, final StringBuilder log) {
> -        return this.withConfiguration(configuration)
> -                .withConfigurationNode(node)
> -                .withStringSubstitutionStrategy(substitutor)
> -                .withDebugLog(log)
> -                .build();
> -    }
> -}
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
> index f8e30c2..dcc5220 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
> @@ -16,62 +16,78 @@
>   */
>  package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
>  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
>  import org.apache.logging.log4j.util.NameUtil;
>  import org.apache.logging.log4j.util.StringBuilders;
> +import org.apache.logging.log4j.util.Strings;
>
> +import java.lang.reflect.Type;
> +import java.util.Collections;
>  import java.util.Map;
> +import java.util.Optional;
> +import java.util.concurrent.ConcurrentHashMap;
>  import java.util.function.Function;
>
>  /**
>   * @deprecated Provided to support legacy plugins.
>   */
> -public class PluginAttributeVisitor extends AbstractPluginVisitor<PluginAttribute, Object> {
> -    public PluginAttributeVisitor() {
> -        super(PluginAttribute.class);
> +// copy of PluginAttributeInjector
> +public class PluginAttributeVisitor extends AbstractConfigurationInjector<PluginAttribute, Configuration> {
> +
> +    private static final Map<Type, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
> +
> +    static {
> +        final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
> +        extractors.put(int.class, PluginAttribute::defaultInt);
> +        extractors.put(Integer.class, PluginAttribute::defaultInt);
> +        extractors.put(long.class, PluginAttribute::defaultLong);
> +        extractors.put(Long.class, PluginAttribute::defaultLong);
> +        extractors.put(boolean.class, PluginAttribute::defaultBoolean);
> +        extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
> +        extractors.put(float.class, PluginAttribute::defaultFloat);
> +        extractors.put(Float.class, PluginAttribute::defaultFloat);
> +        extractors.put(double.class, PluginAttribute::defaultDouble);
> +        extractors.put(Double.class, PluginAttribute::defaultDouble);
> +        extractors.put(byte.class, PluginAttribute::defaultByte);
> +        extractors.put(Byte.class, PluginAttribute::defaultByte);
> +        extractors.put(char.class, PluginAttribute::defaultChar);
> +        extractors.put(Character.class, PluginAttribute::defaultChar);
> +        extractors.put(short.class, PluginAttribute::defaultShort);
> +        extractors.put(Short.class, PluginAttribute::defaultShort);
> +        extractors.put(Class.class, PluginAttribute::defaultClass);
> +        DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
>      }
>
>      @Override
> -    public Object build() {
> -        final String name = this.annotation.value();
> -        final Map<String, String> attributes = node.getAttributes();
> -        final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> -        final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> -        final Object defaultValue = findDefaultValue(stringSubstitutionStrategy);
> -        final Object value = convert(replacedValue, defaultValue);
> -        final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> -        StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> -        return value;
> +    public Object inject(final Object target) {
> +        final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
> +        if (value.isPresent()) {
> +            final String s = value.get();
> +            return optionBinder.bindString(target, s);
> +        } else {
> +            return injectDefaultValue(target);
> +        }
>      }
>
> -    private Object findDefaultValue(Function<String, String> substitutor) {
> -        if (this.conversionType == int.class || this.conversionType == Integer.class) {
> -            return this.annotation.defaultInt();
> -        }
> -        if (this.conversionType == long.class || this.conversionType == Long.class) {
> -            return this.annotation.defaultLong();
> -        }
> -        if (this.conversionType == boolean.class || this.conversionType == Boolean.class) {
> -            return this.annotation.defaultBoolean();
> -        }
> -        if (this.conversionType == float.class || this.conversionType == Float.class) {
> -            return this.annotation.defaultFloat();
> +    private Object injectDefaultValue(final Object target) {
> +        final Function<PluginAttribute, Object> extractor = DEFAULT_VALUE_EXTRACTORS.get(conversionType);
> +        if (extractor != null) {
> +            final Object value = extractor.apply(annotation);
> +            debugLog(value);
> +            return optionBinder.bindObject(target, value);
>          }
> -        if (this.conversionType == double.class || this.conversionType == Double.class) {
> -            return this.annotation.defaultDouble();
> +        final String value = stringSubstitutionStrategy.apply(annotation.defaultString());
> +        if (Strings.isNotBlank(value)) {
> +            debugLog(value);
> +            return optionBinder.bindString(target, value);
>          }
> -        if (this.conversionType == byte.class || this.conversionType == Byte.class) {
> -            return this.annotation.defaultByte();
> -        }
> -        if (this.conversionType == char.class || this.conversionType == Character.class) {
> -            return this.annotation.defaultChar();
> -        }
> -        if (this.conversionType == short.class || this.conversionType == Short.class) {
> -            return this.annotation.defaultShort();
> -        }
> -        if (this.conversionType == Class.class) {
> -            return this.annotation.defaultClass();
> -        }
> -        return substitutor.apply(this.annotation.defaultString());
> +        return target;
> +    }
> +
> +    private void debugLog(final Object value) {
> +        final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> +        StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
>      }
>  }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
> index 6c7f6b0..d834b5b 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
> @@ -17,31 +17,26 @@
>
>  package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
>  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
>  import org.apache.logging.log4j.util.NameUtil;
>  import org.apache.logging.log4j.util.StringBuilders;
>
> -import java.util.Map;
> -
>  /**
>   * @deprecated Provided for support for PluginBuilderAttribute.
>   */
> -public class PluginBuilderAttributeVisitor extends AbstractPluginVisitor<PluginBuilderAttribute, Object> {
> -
> -    public PluginBuilderAttributeVisitor() {
> -        super(PluginBuilderAttribute.class);
> -    }
> -
> +// copy of PluginBuilderAttributeInjector
> +public class PluginBuilderAttributeVisitor extends AbstractConfigurationInjector<PluginBuilderAttribute, Configuration> {
>      @Override
> -    public Object build() {
> -        final String overridden = this.annotation.value();
> -        final String name = overridden.isEmpty() ? this.member.getName() : overridden;
> -        final Map<String, String> attributes = node.getAttributes();
> -        final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> -        final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> -        final Object value = convert(replacedValue, null);
> -        final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> -        StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> -        return value;
> +    public Object inject(final Object target) {
> +        return findAndRemoveNodeAttribute()
> +                .map(stringSubstitutionStrategy)
> +                .map(value -> {
> +                    String debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> +                    StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> +                    return optionBinder.bindString(target, value);
> +                })
> +                .orElseGet(() -> optionBinder.bindObject(target, null));
>      }
>  }
> \ No newline at end of file
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java
> deleted file mode 100644
> index 20976c7..0000000
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java
> +++ /dev/null
> @@ -1,44 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.core.config.plugins.visitors;
> -
> -import org.apache.logging.log4j.core.config.Configuration;
> -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
> -
> -/**
> - * PluginVisitor implementation for {@link PluginConfiguration}.
> - */
> -public class PluginConfigurationVisitor extends AbstractPluginVisitor<PluginConfiguration, Configuration> {
> -    public PluginConfigurationVisitor() {
> -        super(PluginConfiguration.class);
> -    }
> -
> -    @Override
> -    public Object build() {
> -        if (this.conversionType.isInstance(configuration)) {
> -            debugLog.append("Configuration");
> -            if (configuration.getName() != null) {
> -                debugLog.append('(').append(configuration.getName()).append(')');
> -            }
> -            return configuration;
> -        }
> -        LOGGER.warn("Variable annotated with @PluginConfiguration is not compatible with type {}.",
> -                configuration.getClass());
> -        return null;
> -    }
> -}
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
> index de80831..e216293 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
> @@ -17,37 +17,38 @@
>
>  package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
>  import org.apache.logging.log4j.core.config.plugins.PluginElement;
>  import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
>  import org.apache.logging.log4j.plugins.util.PluginType;
> +import org.apache.logging.log4j.plugins.util.TypeUtil;
>
>  import java.lang.reflect.Array;
> +import java.lang.reflect.Type;
>  import java.util.ArrayList;
>  import java.util.Arrays;
>  import java.util.Collection;
>  import java.util.List;
> +import java.util.Optional;
>
>  /**
>   *  @deprecated Provided to support legacy plugins.
>   */
> -public class PluginElementVisitor extends AbstractPluginVisitor<PluginElement, Object> {
> -    public PluginElementVisitor() {
> -        super(PluginElement.class);
> -    }
> -
> +// copy of PluginElementInjector
> +public class PluginElementVisitor extends AbstractConfigurationInjector<PluginElement, Configuration> {
>      @Override
> -    public Object build() {
> -        final String name = this.annotation.value();
> -        if (this.conversionType.isArray()) {
> -            setConversionType(this.conversionType.getComponentType());
> +    public Object inject(final Object target) {
> +        final Optional<Class<?>> componentType = getComponentType(conversionType);
> +        if (componentType.isPresent()) {
> +            final Class<?> compType = componentType.get();
>              final List<Object> values = new ArrayList<>();
>              final Collection<Node> used = new ArrayList<>();
>              debugLog.append("={");
>              boolean first = true;
>              for (final Node child : node.getChildren()) {
> -                final PluginType<?> childType = child.getType();
> -                if (name.equalsIgnoreCase(childType.getElementName()) ||
> -                        this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> +                final PluginType<?> type = child.getType();
> +                if (name.equalsIgnoreCase(type.getElementName()) || compType.isAssignableFrom(type.getPluginClass())) {
>                      if (!first) {
>                          debugLog.append(", ");
>                      }
> @@ -55,56 +56,56 @@ public class PluginElementVisitor extends AbstractPluginVisitor<PluginElement, O
>                      used.add(child);
>                      final Object childObject = child.getObject();
>                      if (childObject == null) {
> -                        LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName());
> -                        continue;
> -                    }
> -                    if (childObject.getClass().isArray()) {
> -                        debugLog.append(Arrays.toString((Object[]) childObject)).append('}');
> +                        LOGGER.warn("Skipping null object returned for element {} in node {}", child.getName(), node.getName());
> +                    } else if (childObject.getClass().isArray()) {
> +                        Object[] children = (Object[]) childObject;
> +                        debugLog.append(Arrays.toString(children)).append('}');
>                          node.getChildren().removeAll(used);
> -                        return childObject;
> +                        return optionBinder.bindObject(target, children);
> +                    } else {
> +                        debugLog.append(child.toString());
> +                        values.add(childObject);
>                      }
> -                    debugLog.append(child.toString());
> -                    values.add(childObject);
>                  }
>              }
>              debugLog.append('}');
> -            // note that we need to return an empty array instead of null if the types are correct
> -            if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) {
> -                LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.",
> -                        name, values.get(0).getClass(), this.conversionType);
> +            if (!values.isEmpty() && !TypeUtil.isAssignable(compType, values.get(0).getClass())) {
> +                LOGGER.error("Cannot assign element {} a list of {} as it is incompatible with {}", name, values.get(0).getClass(), compType);
>                  return null;
>              }
>              node.getChildren().removeAll(used);
> -            // we need to use reflection here because values.toArray() will cause type errors at runtime
> -            final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size());
> -            for (int i = 0; i < array.length; i++) {
> -                array[i] = values.get(i);
> +            // using List::toArray here would cause type mismatch later on
> +            final Object[] vals = (Object[]) Array.newInstance(compType, values.size());
> +            for (int i = 0; i < vals.length; i++) {
> +                vals[i] = values.get(i);
> +            }
> +            return optionBinder.bindObject(target, vals);
> +        } else {
> +            final Optional<Node> matchingChild = node.getChildren().stream().filter(this::isRequestedNode).findAny();
> +            if (matchingChild.isPresent()) {
> +                final Node child = matchingChild.get();
> +                debugLog.append(child.getName()).append('(').append(child.toString()).append(')');
> +                node.getChildren().remove(child);
> +                return optionBinder.bindObject(target, child.getObject());
> +            } else {
> +                debugLog.append(name).append("=null");
> +                return optionBinder.bindObject(target, null);
>              }
> -            return array;
> -        }
> -        final Node namedNode = findNamedNode(name, node.getChildren());
> -        if (namedNode == null) {
> -            debugLog.append(name).append("=null");
> -            return null;
>          }
> -        debugLog.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')');
> -        node.getChildren().remove(namedNode);
> -        return namedNode.getObject();
>      }
>
> -    private Node findNamedNode(final String name, final Iterable<Node> children) {
> -        for (final Node child : children) {
> -            final PluginType<?> childType = child.getType();
> -            if (childType == null) {
> -                //System.out.println();
> -            }
> -            if (name.equalsIgnoreCase(childType.getElementName()) ||
> -                this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> -                // FIXME: check child.getObject() for null?
> -                // doing so would be more consistent with the array version
> -                return child;
> +    private boolean isRequestedNode(final Node child) {
> +        final PluginType<?> type = child.getType();
> +        return name.equalsIgnoreCase(type.getElementName()) || TypeUtil.isAssignable(conversionType, type.getPluginClass());
> +    }
> +
> +    private static Optional<Class<?>> getComponentType(final Type type) {
> +        if (type instanceof Class<?>) {
> +            final Class<?> clazz = (Class<?>) type;
> +            if (clazz.isArray()) {
> +                return Optional.of(clazz.getComponentType());
>              }
>          }
> -        return null;
> +        return Optional.empty();
>      }
>  }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
> index 57c0d89..bf9a364 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
> @@ -17,23 +17,18 @@
>
>  package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
>  import org.apache.logging.log4j.core.config.plugins.PluginNode;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
>
>  /**
>   *  @deprecated Provided to support legacy plugins.
>   */
> -public class PluginNodeVisitor extends AbstractPluginVisitor<PluginNode, Object> {
> -    public PluginNodeVisitor() {
> -        super(PluginNode.class);
> -    }
> -
> +// copy of PluginNodeInjector
> +public class PluginNodeVisitor extends AbstractConfigurationInjector<PluginNode, Configuration> {
>      @Override
> -    public Object build() {
> -        if (this.conversionType.isInstance(node)) {
> -            debugLog.append("Node=").append(node.getName());
> -            return node;
> -        }
> -        LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass());
> -        return null;
> +    public Object inject(final Object target) {
> +        debugLog.append("Node=").append(node.getName());
> +        return optionBinder.bindObject(target, node);
>      }
>  }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
> index 9600514..95f86f4 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
> @@ -17,23 +17,21 @@
>
>  package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
>  import org.apache.logging.log4j.core.config.plugins.PluginValue;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
>  import org.apache.logging.log4j.util.StringBuilders;
>  import org.apache.logging.log4j.util.Strings;
>
>  /**
>   *  @deprecated Provided to support legacy plugins.
>   */
> -public class PluginValueVisitor extends AbstractPluginVisitor<PluginValue, Object> {
> -    public PluginValueVisitor() {
> -        super(PluginValue.class);
> -    }
> -
> +// copy of PluginValueInjector
> +public class PluginValueVisitor extends AbstractConfigurationInjector<PluginValue, Configuration> {
>      @Override
> -    public Object build() {
> -        final String name = this.annotation.value();
> +    public Object inject(final Object target) {
>          final String elementValue = node.getValue();
> -        final String attributeValue = node.getAttributes().get("value");
> +        final String attributeValue = node.getAttributes().get(name);
>          String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
>          if (Strings.isNotEmpty(elementValue)) {
>              if (Strings.isNotEmpty(attributeValue)) {
> @@ -43,10 +41,10 @@ public class PluginValueVisitor extends AbstractPluginVisitor<PluginValue, Objec
>              }
>              rawValue = elementValue;
>          } else {
> -            rawValue = removeAttributeValue(node.getAttributes(), "value");
> +            rawValue = findAndRemoveNodeAttribute().orElse(null);
>          }
>          final String value = stringSubstitutionStrategy.apply(rawValue);
>          StringBuilders.appendKeyDqValue(debugLog, name, value);
> -        return value;
> +        return optionBinder.bindString(target, value);
>      }
>  }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
> index 276ec0c..9b747e3 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
> @@ -16,9 +16,7 @@
>   */
>
>  /**
> - * Visitor classes for extracting values from a Configuration or Node corresponding to a plugin annotation.
> - * Visitor implementations must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder},
> - * and the corresponding annotation must be annotated with
> - * {@link org.apache.logging.log4j.plugins.inject.InjectionStrategy}.
> + * Legacy injector strategies for legacy annotations. Plugins should use the updated annotations provided in
> + * {@code org.apache.logging.log4j.plugins}.
>   */
>  package org.apache.logging.log4j.core.config.plugins.visitors;
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
> index 4dce103..bb3962a 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
> @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
>  /**
> - * Identifies a list of aliases for a Plugin, PluginAttribute, or PluginBuilderAttribute.
> + * Identifies a list of aliases for an annotated plugin element. This is supported by plugin classes and other element
> + * types supported by the annotations in this package.
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
> +@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
>  public @interface PluginAliases {
>
> +    /**
> +     * Aliases the annotated element can also be referred to. These aliases are case-insensitive.
> +     */
>      String[] value();
>  }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
> index efc769a..30cccad 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
> @@ -16,8 +16,10 @@
>   */
>  package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginAttributeBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginAttributeInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginAttributeNameProvider;
>  import org.apache.logging.log4j.util.Strings;
>
>  import java.lang.annotation.Documented;
> @@ -27,16 +29,24 @@ import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
>  /**
> - * Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be
> - * used based on the type this annotation is attached to. Thus, for primitive types, the default<i>Type</i>
> - * attribute will be used for some <i>Type</i>. However, for more complex types (including enums), the default
> - * string value is used instead and should correspond to the string that would correctly convert to the appropriate
> - * enum value using {@link Enum#valueOf(Class, String) Enum.valueOf}.
> + * Identifies a Plugin Attribute along with optional metadata. Plugin attributes can be injected as parameters to
> + * a static {@linkplain PluginFactory factory method}, or as fields and single-parameter methods in a plugin
> + * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
> + *
> + * <p>Default values may be specified via one of the <code>default<var>Type</var></code> attributes depending on the
> + * annotated type. Unlisted types that are supported by a corresponding
> + * {@link org.apache.logging.log4j.plugins.convert.TypeConverter} may use the {@link #defaultString()} attribute.
> + * When annotating a field, a default value can be specified by the field's initial value instead of using one of the
> + * annotation attributes.</p>
> + *
> + * <p>Plugin attributes with sensitive data such as passwords should specify {@link #sensitive()} to avoid having
> + * their values logged in debug logs.</p>
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginAttributeBuilder.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginAttributeInjector.class)
> +@NameProvider(PluginAttributeNameProvider.class)
>  public @interface PluginAttribute {
>
>      /**
> @@ -89,11 +99,18 @@ public @interface PluginAttribute {
>       */
>      String defaultString() default Strings.EMPTY;
>
> -    // TODO: could we allow a blank value and infer the attribute name through reflection?
>      /**
>       * Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
> +     * If blank, defaults to using reflection on the annotated element as such:
> +     *
> +     * <ul>
> +     *     <li>Field: uses the field name.</li>
> +     *     <li>Method: when named <code>set<var>XYZ</var></code> or <code>with<var>XYZ</var></code>, uses the rest
> +     *     (<var>XYZ</var>) of the method name. Otherwise, uses the name of the first parameter.</li>
> +     *     <li>Parameter: uses the parameter name.</li>
> +     * </ul>
>       */
> -    String value();
> +    String value() default Strings.EMPTY;
>
>      /**
>       * Indicates that this attribute is a sensitive one that shouldn't be logged directly. Such attributes will instead
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
> index 3f3d597..06cb8f0 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
> @@ -17,8 +17,10 @@
>
>  package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginBuilderAttributeBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginBuilderAttributeInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginBuilderAttributeNameProvider;
>  import org.apache.logging.log4j.util.Strings;
>
>  import java.lang.annotation.Documented;
> @@ -29,12 +31,15 @@ import java.lang.annotation.Target;
>
>  /**
>   * Marks a field as a Plugin Attribute.
> + *
> + * @deprecated use {@link PluginAttribute}
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
> -@InjectionStrategy(PluginBuilderAttributeBuilder.class)
> -// TODO: this annotation can be combined with @PluginAttribute
> +@InjectorStrategy(PluginBuilderAttributeInjector.class)
> +@NameProvider(PluginBuilderAttributeNameProvider.class)
> +@Deprecated
>  public @interface PluginBuilderAttribute {
>
>      /**
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java
> index 4d15caa..a698fe1 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java
> @@ -25,11 +25,13 @@ import java.lang.annotation.Target;
>
>  /**
>   * Marks a method as a factory for custom Plugin builders.
> + *
> + * @deprecated use {@link PluginFactory}
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
>  @Target(ElementType.METHOD)
> -// TODO: this can be combined with @PluginFactory as differentiating them by method signature is obvious
> +@Deprecated
>  public @interface PluginBuilderFactory {
>      // empty
>  }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
> index deb4641..edd5412 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
> @@ -16,8 +16,11 @@
>   */
>  package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginElementBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginElementInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginElementNameProvider;
> +import org.apache.logging.log4j.util.Strings;
>
>  import java.lang.annotation.Documented;
>  import java.lang.annotation.ElementType;
> @@ -26,17 +29,28 @@ import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
>  /**
> + * Identifies a Plugin Element which allows for plugins to be configured and injected into another plugin.
> + * Plugin elements can be injected as parameters to a static {@linkplain PluginFactory factory method}, or as fields and
> + * single-parameter methods in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
>   * Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files.
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginElementBuilder.class)
> -// TODO: this can have a default value to use reflection
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginElementInjector.class)
> +@NameProvider(PluginElementNameProvider.class)
>  public @interface PluginElement {
>
>      /**
>       * Identifies the case-insensitive element name (or attribute name) this corresponds with in a configuration file.
> +     * If blank, defaults to using reflection on the annotated element as such:
> +     *
> +     * <ul>
> +     *     <li>Field: uses the field name.</li>
> +     *     <li>Method: when named <code>set<var>XYZ</var></code> or <code>with<var>XYZ</var></code>, uses the rest
> +     *     (<var>XYZ</var>) of the method name. Otherwise, uses the name of the first parameter.</li>
> +     *     <li>Parameter: uses the parameter name.</li>
> +     * </ul>
>       */
> -    String value();
> +    String value() default Strings.EMPTY;
>  }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
> index b071510..f1d89c6 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
> @@ -23,8 +23,13 @@ import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
>  /**
> - * Identifies a Method as the factory to create the plugin. This annotation should only be used on a {@code static}
> - * method, and its parameters should be annotated with the appropriate Plugin annotations.
> + * Identifies a static method as a factory to create a plugin or a
> + * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class} for constructing a plugin.
> + * Factory methods should annotate their parameters with {@link PluginAttribute}, {@link PluginElement},
> + * {@link PluginValue}, or other plugin annotations annotated with
> + * {@link org.apache.logging.log4j.plugins.inject.InjectorStrategy}.
> + * If a factory method returns a builder class, this method should have no arguments; instead, the builder class should
> + * annotate its fields or single-parameter methods to inject plugin configuration data.
>   * <p>
>   * There can only be one factory method per class.
>   * </p>
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
> index 2f5cef3..dcc5b0c 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
> @@ -16,8 +16,8 @@
>   */
>  package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginNodeBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginNodeInjector;
>
>  import java.lang.annotation.Documented;
>  import java.lang.annotation.ElementType;
> @@ -26,12 +26,14 @@ import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
>  /**
> - * Identifies a Plugin configuration Node.
> + * Identifies the configuration {@link Node} currently being configured. This can be injected as a parameter to a static
> + * {@linkplain PluginFactory factory method}, or as a field or single-parameter method in a plugin
> + * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginNodeBuilder.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginNodeInjector.class)
>  public @interface PluginNode {
>      // empty
>  }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
> index 84c4385..a08eaf0 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
> @@ -16,8 +16,10 @@
>   */
>  package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginValueBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginValueInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginValueNameProvider;
>
>  import java.lang.annotation.Documented;
>  import java.lang.annotation.ElementType;
> @@ -26,16 +28,25 @@ import java.lang.annotation.RetentionPolicy;
>  import java.lang.annotation.Target;
>
>  /**
> - * Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be
> - * used as a placeholder value somewhere.
> + * Identifies a Plugin Value and its corresponding attribute alias for configuration formats that don't distinguish
> + * between values and attributes. A value is typically used differently from an attribute in that it is either the
> + * main configuration value required or it is the only value needed to create a plugin. A plugin value can be injected
> + * as a parameter to a static {@linkplain PluginFactory factory method}, or as a field or single-parameter method in a
> + * plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
>   *
> - * @see org.apache.logging.log4j.core.config.PropertiesPlugin
> + * <p>For example, a Property plugin corresponds to a property entry in a configuration file. The property name is
> + * specified as an attribute, and the property value is specified as a value.</p>
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginValueBuilder.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginValueInjector.class)
> +@NameProvider(PluginValueNameProvider.class)
>  public @interface PluginValue {
>
> -    String value();
> +    /**
> +     * Specifies the case-insensitive attribute name to use in configuration formats that don't distinguish between
> +     * attributes and values. By default, this uses the attribute name {@code value}.
> +     */
> +    String value() default "value";
>  }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractOptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractOptionBinder.java
> new file mode 100644
> index 0000000..e3603ce
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractOptionBinder.java
> @@ -0,0 +1,62 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import org.apache.logging.log4j.Logger;
> +import org.apache.logging.log4j.plugins.convert.TypeConverter;
> +import org.apache.logging.log4j.plugins.convert.TypeConverterRegistry;
> +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
> +import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
> +import org.apache.logging.log4j.plugins.validation.ConstraintValidators;
> +import org.apache.logging.log4j.status.StatusLogger;
> +
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Type;
> +import java.util.Collection;
> +import java.util.Objects;
> +import java.util.function.Function;
> +
> +public abstract class AbstractOptionBinder<E extends AnnotatedElement> implements OptionBinder {
> +    protected static final Logger LOGGER = StatusLogger.getLogger();
> +
> +    final E element;
> +    final String name;
> +    private final Type injectionType;
> +    private final Collection<ConstraintValidator<?>> validators;
> +
> +    AbstractOptionBinder(final E element, final Function<E, Type> injectionTypeExtractor) {
> +        this.element = Objects.requireNonNull(element);
> +        this.name = AnnotatedElementNameProvider.getName(element);
> +        Objects.requireNonNull(injectionTypeExtractor);
> +        this.injectionType = Objects.requireNonNull(injectionTypeExtractor.apply(element));
> +        this.validators = ConstraintValidators.findValidators(element.getAnnotations());
> +    }
> +
> +    @Override
> +    public Object bindString(final Object target, final String value) {
> +        Object convertedValue = null;
> +        if (value != null) {
> +            final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(injectionType);
> +            try {
> +                convertedValue = converter.convert(value);
> +            } catch (final Exception e) {
> +                LOGGER.error("Cannot convert string '{}' to type {} in option named {}. {}", value, injectionType, name, e);
> +            }
> +        }
> +        return bindObject(target, convertedValue);
> +    }
> +
> +    void validate(final Object value) {
> +        boolean valid = true;
> +        for (ConstraintValidator<?> validator : validators) {
> +            valid &= validator.isValid(name, value);
> +        }
> +        // FIXME: this doesn't seem to work properly with primitive types
> +//        if (valid && value != null && !TypeUtil.isAssignable(injectionType, value.getClass())) {
> +//            LOGGER.error("Cannot bind value of type {} to option {} with type {}", value.getClass(), name, injectionType);
> +//            valid = false;
> +//        }
> +        if (!valid) {
> +            throw new OptionBindingException(name, value);
> +        }
> +    }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java
> new file mode 100644
> index 0000000..80bee70
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java
> @@ -0,0 +1,58 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import java.lang.reflect.InvocationTargetException;
> +import java.lang.reflect.Method;
> +import java.lang.reflect.Parameter;
> +import java.util.Map;
> +import java.util.Objects;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.function.BiConsumer;
> +
> +// TODO: can support constructor factory following same pattern
> +public class FactoryMethodBinder {
> +
> +    private final Method factoryMethod;
> +    private final Map<Parameter, OptionBinder> binders = new ConcurrentHashMap<>();
> +    private final Map<Parameter, Object> boundParameters = new ConcurrentHashMap<>();
> +
> +    public FactoryMethodBinder(final Method factoryMethod) {
> +        this.factoryMethod = Objects.requireNonNull(factoryMethod);
> +        for (final Parameter parameter : factoryMethod.getParameters()) {
> +            binders.put(parameter, new ParameterOptionBinder(parameter));
> +        }
> +    }
> +
> +    public void forEachParameter(final BiConsumer<Parameter, OptionBinder> consumer) {
> +        binders.forEach(consumer);
> +    }
> +
> +    public Object invoke() throws Throwable {
> +        final Parameter[] parameters = factoryMethod.getParameters();
> +        final Object[] args = new Object[parameters.length];
> +        for (int i = 0; i < parameters.length; i++) {
> +            args[i] = boundParameters.get(parameters[i]);
> +        }
> +        try {
> +            return factoryMethod.invoke(null, args);
> +        } catch (final IllegalAccessException e) {
> +            throw new OptionBindingException("Cannot access factory method " + factoryMethod, e);
> +        } catch (final InvocationTargetException e) {
> +            throw e.getCause();
> +        }
> +    }
> +
> +    private class ParameterOptionBinder extends AbstractOptionBinder<Parameter> {
> +        private ParameterOptionBinder(final Parameter parameter) {
> +            super(parameter, Parameter::getParameterizedType);
> +        }
> +
> +        @Override
> +        public Object bindObject(final Object target, final Object value) {
> +            validate(value);
> +            if (value != null) {
> +                boundParameters.put(element, value);
> +            }
> +            return target;
> +        }
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldOptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldOptionBinder.java
> new file mode 100644
> index 0000000..bbd9a6b
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldOptionBinder.java
> @@ -0,0 +1,36 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import java.lang.reflect.Field;
> +import java.util.Objects;
> +
> +public class FieldOptionBinder extends AbstractOptionBinder<Field> {
> +
> +    public FieldOptionBinder(final Field field) {
> +        super(field, Field::getGenericType);
> +    }
> +
> +    @Override
> +    public Object bindObject(final Object target, final Object value) {
> +        Objects.requireNonNull(target);
> +        // FIXME: if we specify a default field value, @PluginAttribute's defaultType will override that
> +        if (value == null) {
> +            try {
> +                Object defaultValue = element.get(target);
> +                validate(defaultValue);
> +                LOGGER.trace("Using default value {} for option {}", defaultValue, name);
> +            } catch (final IllegalAccessException e) {
> +                throw new OptionBindingException("Unable to validate option " + name, e);
> +            }
> +            return target;
> +        }
> +        validate(value);
> +        try {
> +            element.set(target, value);
> +            LOGGER.trace("Using value {} for option {}", value, name);
> +            return target;
> +        } catch (final IllegalAccessException e) {
> +            throw new OptionBindingException(name, value, e);
> +        }
> +    }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodOptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodOptionBinder.java
> new file mode 100644
> index 0000000..6398520
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodOptionBinder.java
> @@ -0,0 +1,26 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import java.lang.reflect.InvocationTargetException;
> +import java.lang.reflect.Method;
> +import java.util.Objects;
> +
> +public class MethodOptionBinder extends AbstractOptionBinder<Method> {
> +
> +    public MethodOptionBinder(final Method method) {
> +        super(method, m -> m.getGenericParameterTypes()[0]);
> +    }
> +
> +    @Override
> +    public Object bindObject(final Object target, final Object value) {
> +        Objects.requireNonNull(target);
> +        validate(value);
> +        try {
> +            element.invoke(target, value);
> +        } catch (final IllegalAccessException e) {
> +            throw new OptionBindingException(name, value, e);
> +        } catch (final InvocationTargetException e) {
> +            throw new OptionBindingException(name, value, e.getCause());
> +        }
> +        return target;
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBinder.java
> new file mode 100644
> index 0000000..6a6872f
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBinder.java
> @@ -0,0 +1,7 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +public interface OptionBinder {
> +    Object bindString(final Object target, final String value);
> +
> +    Object bindObject(final Object target, final Object value);
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBindingException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBindingException.java
> new file mode 100644
> index 0000000..0cc37b1
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBindingException.java
> @@ -0,0 +1,24 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +public class OptionBindingException extends IllegalArgumentException {
> +
> +    public OptionBindingException(final String name, final Object value) {
> +        super("Invalid value '" + value + "' for option '" + name + "'");
> +    }
> +
> +    public OptionBindingException(final String name, final Object value, final Throwable cause) {
> +        super("Unable to set option '" + name + "' to value '" + value + "'", cause);
> +    }
> +
> +    public OptionBindingException(final String s) {
> +        super(s);
> +    }
> +
> +    public OptionBindingException(final String message, final Throwable cause) {
> +        super(message, cause);
> +    }
> +
> +    public OptionBindingException(final Throwable cause) {
> +        super(cause);
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjectionBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjectionBuilder.java
> deleted file mode 100644
> index 2039947..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjectionBuilder.java
> +++ /dev/null
> @@ -1,175 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.Logger;
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.convert.TypeConverters;
> -import org.apache.logging.log4j.status.StatusLogger;
> -import org.apache.logging.log4j.util.Strings;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.reflect.Member;
> -import java.util.Map;
> -import java.util.Objects;
> -import java.util.function.Function;
> -
> -/**
> - * Base class for InjectionStrategyBuilder implementations. Provides fields and setters for the builder leaving only
> - * {@link ConfigurationInjectionBuilder#build()} to be implemented.
> - *
> - * @param <Ann> the Plugin annotation type.
> - */
> -public abstract class AbstractConfigurationInjectionBuilder<Ann extends Annotation, Cfg> implements ConfigurationInjectionBuilder<Ann, Cfg> {
> -
> -    /** Status logger. */
> -    protected static final Logger LOGGER = StatusLogger.getLogger();
> -
> -    /**
> -     *
> -     */
> -    protected final Class<Ann> clazz;
> -    /**
> -     *
> -     */
> -    protected Ann annotation;
> -    /**
> -     *
> -     */
> -    protected String[] aliases;
> -    /**
> -     *
> -     */
> -    protected Class<?> conversionType;
> -    /**
> -     *
> -     */
> -    protected Member member;
> -
> -    protected Cfg configuration;
> -
> -    protected Node node;
> -
> -    protected Function<String, String> stringSubstitutionStrategy;
> -
> -    protected StringBuilder debugLog;
> -
> -    /**
> -     * This constructor must be overridden by implementation classes as a no-arg constructor.
> -     *
> -     * @param clazz the annotation class this PluginVisitor is for.
> -     */
> -    protected AbstractConfigurationInjectionBuilder(final Class<Ann> clazz) {
> -        this.clazz = clazz;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withAnnotation(final Annotation anAnnotation) {
> -        final Annotation a = Objects.requireNonNull(anAnnotation, "No annotation was provided");
> -        if (this.clazz.isInstance(a)) {
> -            this.annotation = clazz.cast(a);
> -        }
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withAliases(final String... someAliases) {
> -        this.aliases = someAliases;
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withConversionType(final Class<?> aConversionType) {
> -        this.conversionType = Objects.requireNonNull(aConversionType, "No conversion type class was provided");
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withMember(final Member aMember) {
> -        this.member = aMember;
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withConfiguration(final Cfg aConfiguration) {
> -        this.configuration = aConfiguration;
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> aStringSubstitutionStrategy) {
> -        this.stringSubstitutionStrategy = aStringSubstitutionStrategy;
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withDebugLog(final StringBuilder aDebugLog) {
> -        this.debugLog = aDebugLog;
> -        return this;
> -    }
> -
> -    @Override
> -    public ConfigurationInjectionBuilder<Ann, Cfg> withConfigurationNode(final Node aNode) {
> -        this.node = aNode;
> -        return this;
> -    }
> -
> -    /**
> -     * Removes an Entry from a given Map using a key name and aliases for that key. Keys are case-insensitive.
> -     *
> -     * @param attributes the Map to remove an Entry from.
> -     * @param name       the key name to look up.
> -     * @param aliases    optional aliases of the key name to look up.
> -     * @return the value corresponding to the given key or {@code null} if nonexistent.
> -     */
> -    protected static String removeAttributeValue(final Map<String, String> attributes,
> -                                                 final String name,
> -                                                 final String... aliases) {
> -        for (final Map.Entry<String, String> entry : attributes.entrySet()) {
> -            final String key = entry.getKey();
> -            final String value = entry.getValue();
> -            if (key.equalsIgnoreCase(name)) {
> -                attributes.remove(key);
> -                return value;
> -            }
> -            if (aliases != null) {
> -                for (final String alias : aliases) {
> -                    if (key.equalsIgnoreCase(alias)) {
> -                        attributes.remove(key);
> -                        return value;
> -                    }
> -                }
> -            }
> -        }
> -        return null;
> -    }
> -
> -    /**
> -     * Converts the given value into the configured type falling back to the provided default value.
> -     *
> -     * @param value        the value to convert.
> -     * @param defaultValue the fallback value to use in case of no value or an error.
> -     * @return the converted value whether that be based on the given value or the default value.
> -     */
> -    protected Object convert(final String value, final Object defaultValue) {
> -        if (defaultValue instanceof String) {
> -            return TypeConverters.convert(value, this.conversionType, Strings.trimToNull((String) defaultValue));
> -        }
> -        return TypeConverters.convert(value, this.conversionType, defaultValue);
> -    }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java
> new file mode 100644
> index 0000000..2513cfd
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java
> @@ -0,0 +1,118 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.Logger;
> +import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.PluginAliases;
> +import org.apache.logging.log4j.plugins.bind.OptionBinder;
> +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
> +import org.apache.logging.log4j.status.StatusLogger;
> +
> +import java.lang.annotation.Annotation;
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Type;
> +import java.util.Arrays;
> +import java.util.Collection;
> +import java.util.Collections;
> +import java.util.Map;
> +import java.util.Objects;
> +import java.util.Optional;
> +import java.util.function.Function;
> +
> +public abstract class AbstractConfigurationInjector<Ann extends Annotation, Cfg> implements ConfigurationInjector<Ann, Cfg> {
> +
> +    protected static final Logger LOGGER = StatusLogger.getLogger();
> +
> +    protected Ann annotation;
> +    protected AnnotatedElement annotatedElement;
> +    protected Type conversionType;
> +    protected String name;
> +    protected Collection<String> aliases = Collections.emptyList();
> +    protected OptionBinder optionBinder;
> +    protected StringBuilder debugLog;
> +    protected Function<String, String> stringSubstitutionStrategy = Function.identity();
> +    protected Cfg configuration;
> +    protected Node node;
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withAnnotation(final Ann annotation) {
> +        this.annotation = Objects.requireNonNull(annotation);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withAnnotatedElement(final AnnotatedElement element) {
> +        this.annotatedElement = Objects.requireNonNull(element);
> +        withName(AnnotatedElementNameProvider.getName(element));
> +        final PluginAliases aliases = element.getAnnotation(PluginAliases.class);
> +        if (aliases != null) {
> +            withAliases(aliases.value());
> +        }
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withConversionType(final Type type) {
> +        this.conversionType = Objects.requireNonNull(type);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withName(final String name) {
> +        this.name = Objects.requireNonNull(name);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withAliases(final String... aliases) {
> +        this.aliases = Arrays.asList(aliases);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withOptionBinder(final OptionBinder binder) {
> +        this.optionBinder = binder;
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withDebugLog(final StringBuilder debugLog) {
> +        this.debugLog = Objects.requireNonNull(debugLog);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> strategy) {
> +        this.stringSubstitutionStrategy = Objects.requireNonNull(strategy);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withConfiguration(final Cfg configuration) {
> +        this.configuration = Objects.requireNonNull(configuration);
> +        return this;
> +    }
> +
> +    @Override
> +    public ConfigurationInjector<Ann, Cfg> withNode(final Node node) {
> +        this.node = Objects.requireNonNull(node);
> +        return this;
> +    }
> +
> +    protected Optional<String> findAndRemoveNodeAttribute() {
> +        Objects.requireNonNull(node);
> +        Objects.requireNonNull(name);
> +        final Map<String, String> attributes = node.getAttributes();
> +        for (final String key : attributes.keySet()) {
> +            if (key.equalsIgnoreCase(name)) {
> +                return Optional.ofNullable(attributes.remove(key));
> +            }
> +            for (final String alias : aliases) {
> +                if (key.equalsIgnoreCase(alias)) {
> +                    return Optional.ofNullable(attributes.remove(key));
> +                }
> +            }
> +        }
> +        return Optional.empty();
> +    }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjectionBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjectionBuilder.java
> deleted file mode 100644
> index bfcfea3..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjectionBuilder.java
> +++ /dev/null
> @@ -1,105 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.util.Builder;
> -import org.apache.logging.log4j.status.StatusLogger;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.reflect.Member;
> -import java.util.Optional;
> -import java.util.function.Function;
> -
> -/**
> - * Builder strategy for parsing and injecting a configuration node. Implementations should contain a default constructor
> - * and must provide a {@link #build()} implementation. This provides type conversion based on the injection point via
> - * {@link org.apache.logging.log4j.plugins.convert.TypeConverters}.
> - *
> - * @param <Ann> the Annotation type.
> - * @param <Cfg> the Configuration type.
> - */
> -public interface ConfigurationInjectionBuilder<Ann extends Annotation, Cfg> extends Builder<Object> {
> -
> -    /**
> -     * Creates a ConfigurationInjectionBuilder instance for the given annotation class using metadata provided by the annotation's
> -     * {@link InjectionStrategy} annotation. This instance must be further populated with
> -     * data before being {@linkplain #build() built} to be useful.
> -     *
> -     * @param injectorType the Plugin annotation class to find a ConfigurationInjectionBuilder for.
> -     * @return a ConfigurationInjectionBuilder instance if one could be created or empty.
> -     */
> -    @SuppressWarnings("unchecked")
> -    static <Ann extends Annotation, Cfg> Optional<ConfigurationInjectionBuilder<Ann, Cfg>> findBuilderForInjectionStrategy(final Class<Ann> injectorType) {
> -        return Optional.ofNullable(injectorType.getAnnotation(InjectionStrategy.class))
> -                .flatMap(type -> {
> -                    try {
> -                        return Optional.of((ConfigurationInjectionBuilder<Ann, Cfg>) type.value().newInstance());
> -                    } catch (final Exception e) {
> -                        StatusLogger.getLogger().error("Error loading PluginBuilder [{}] for annotation [{}].", type.value(), injectorType, e);
> -                        return Optional.empty();
> -                    }
> -                });
> -    }
> -
> -    /**
> -     * Sets the Annotation to be used for this. If the given Annotation is not compatible with this class's type, then
> -     * it is ignored.
> -     *
> -     * @param annotation the Annotation instance.
> -     * @return {@code this}.
> -     * @throws NullPointerException if the argument is {@code null}.
> -     */
> -    ConfigurationInjectionBuilder<Ann, Cfg> withAnnotation(Annotation annotation);
> -
> -    /**
> -     * Sets the list of aliases to use for this injection. No aliases are required, however.
> -     *
> -     * @param aliases the list of aliases to use.
> -     * @return {@code this}.
> -     */
> -    ConfigurationInjectionBuilder<Ann, Cfg> withAliases(String... aliases);
> -
> -    /**
> -     * Sets the class to convert the plugin value to for injection. This should correspond with a class obtained from
> -     * a factory method or builder class field. Not all ConfigurationInjectionBuilder implementations may need this value.
> -     *
> -     * @param conversionType the type to convert the plugin string to (if applicable).
> -     * @return {@code this}.
> -     * @throws NullPointerException if the argument is {@code null}.
> -     */
> -    ConfigurationInjectionBuilder<Ann, Cfg> withConversionType(Class<?> conversionType);
> -
> -    /**
> -     * Sets the Member that this builder is being used for injection upon. For instance, this could be the Field
> -     * that is being used for injecting a value, or it could be the factory method being used to inject parameters
> -     * into.
> -     *
> -     * @param member the member this builder is parsing a value for.
> -     * @return {@code this}.
> -     */
> -    ConfigurationInjectionBuilder<Ann, Cfg> withMember(Member member);
> -
> -    ConfigurationInjectionBuilder<Ann, Cfg> withStringSubstitutionStrategy(Function<String, String> stringSubstitutionStrategy);
> -
> -    ConfigurationInjectionBuilder<Ann, Cfg> withDebugLog(StringBuilder debugLog);
> -
> -    ConfigurationInjectionBuilder<Ann, Cfg> withConfiguration(Cfg configuration);
> -
> -    ConfigurationInjectionBuilder<Ann, Cfg> withConfigurationNode(Node node);
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java
> new file mode 100644
> index 0000000..048298b
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java
> @@ -0,0 +1,48 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.bind.OptionBinder;
> +import org.apache.logging.log4j.util.ReflectionUtil;
> +
> +import java.lang.annotation.Annotation;
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Type;
> +import java.util.Optional;
> +import java.util.function.Function;
> +
> +public interface ConfigurationInjector<Ann extends Annotation, Cfg> {
> +
> +    static <Cfg> Optional<ConfigurationInjector<Annotation, Cfg>> forAnnotatedElement(final AnnotatedElement element) {
> +        for (final Annotation annotation : element.getAnnotations()) {
> +            final InjectorStrategy strategy = annotation.annotationType().getAnnotation(InjectorStrategy.class);
> +            if (strategy != null) {
> +                @SuppressWarnings("unchecked") final ConfigurationInjector<Annotation, Cfg> injector =
> +                        (ConfigurationInjector<Annotation, Cfg>) ReflectionUtil.instantiate(strategy.value());
> +                return Optional.of(injector.withAnnotatedElement(element).withAnnotation(annotation));
> +            }
> +        }
> +        return Optional.empty();
> +    }
> +
> +    ConfigurationInjector<Ann, Cfg> withAnnotation(final Ann annotation);
> +
> +    ConfigurationInjector<Ann, Cfg> withAnnotatedElement(final AnnotatedElement element);
> +
> +    ConfigurationInjector<Ann, Cfg> withConversionType(final Type type);
> +
> +    ConfigurationInjector<Ann, Cfg> withName(final String name);
> +
> +    ConfigurationInjector<Ann, Cfg> withAliases(final String... aliases);
> +
> +    ConfigurationInjector<Ann, Cfg> withOptionBinder(final OptionBinder binder);
> +
> +    ConfigurationInjector<Ann, Cfg> withDebugLog(final StringBuilder debugLog);
> +
> +    ConfigurationInjector<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> strategy);
> +
> +    ConfigurationInjector<Ann, Cfg> withConfiguration(final Cfg configuration);
> +
> +    ConfigurationInjector<Ann, Cfg> withNode(final Node node);
> +
> +    Object inject(final Object target);
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectionStrategy.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectionStrategy.java
> deleted file mode 100644
> index 1a67331..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectionStrategy.java
> +++ /dev/null
> @@ -1,42 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.annotation.Documented;
> -import java.lang.annotation.ElementType;
> -import java.lang.annotation.Retention;
> -import java.lang.annotation.RetentionPolicy;
> -import java.lang.annotation.Target;
> -
> -/**
> - * Meta-annotation to denote the class name to use that implements
> - * {@link ConfigurationInjectionBuilder} for the annotated annotation.
> - */
> -@Documented
> -@Retention(RetentionPolicy.RUNTIME)
> -@Target(ElementType.ANNOTATION_TYPE)
> -public @interface InjectionStrategy {
> -
> -    /**
> -     * The class to use that implements {@link ConfigurationInjectionBuilder}
> -     * for the given annotation. The generic annotation type in {@code ConfigurationInjectionBuilder} should match the
> -     * annotation this annotation is applied to.
> -     */
> -    Class<? extends ConfigurationInjectionBuilder<? extends Annotation, ?>> value();
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java
> new file mode 100644
> index 0000000..d398cb9
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java
> @@ -0,0 +1,16 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import java.lang.annotation.Annotation;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
> +
> +@Documented
> +@Retention(RetentionPolicy.RUNTIME)
> +@Target(ElementType.ANNOTATION_TYPE)
> +// TODO: annotation processor to validate type matches (help avoid runtime errors)
> +public @interface InjectorStrategy {
> +    Class<? extends ConfigurationInjector<? extends Annotation, ?>> value();
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeBuilder.java
> deleted file mode 100644
> index 733a3a7..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeBuilder.java
> +++ /dev/null
> @@ -1,81 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginAttribute;
> -import org.apache.logging.log4j.util.NameUtil;
> -import org.apache.logging.log4j.util.StringBuilders;
> -
> -import java.util.Collections;
> -import java.util.Map;
> -import java.util.concurrent.ConcurrentHashMap;
> -import java.util.function.Function;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginAttribute}.
> - */
> -public class PluginAttributeBuilder extends AbstractConfigurationInjectionBuilder<PluginAttribute, Object> {
> -
> -    private static final Map<Class<?>, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
> -
> -    static {
> -        final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
> -        extractors.put(int.class, PluginAttribute::defaultInt);
> -        extractors.put(Integer.class, PluginAttribute::defaultInt);
> -        extractors.put(long.class, PluginAttribute::defaultLong);
> -        extractors.put(Long.class, PluginAttribute::defaultLong);
> -        extractors.put(boolean.class, PluginAttribute::defaultBoolean);
> -        extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
> -        extractors.put(float.class, PluginAttribute::defaultFloat);
> -        extractors.put(Float.class, PluginAttribute::defaultFloat);
> -        extractors.put(double.class, PluginAttribute::defaultDouble);
> -        extractors.put(Double.class, PluginAttribute::defaultDouble);
> -        extractors.put(byte.class, PluginAttribute::defaultByte);
> -        extractors.put(Byte.class, PluginAttribute::defaultByte);
> -        extractors.put(char.class, PluginAttribute::defaultChar);
> -        extractors.put(Character.class, PluginAttribute::defaultChar);
> -        extractors.put(short.class, PluginAttribute::defaultShort);
> -        extractors.put(Short.class, PluginAttribute::defaultShort);
> -        extractors.put(Class.class, PluginAttribute::defaultClass);
> -        DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
> -    }
> -
> -    public PluginAttributeBuilder() {
> -        super(PluginAttribute.class);
> -    }
> -
> -    @Override
> -    public Object build() {
> -        final String name = this.annotation.value();
> -        final Map<String, String> attributes = node.getAttributes();
> -        final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> -        final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> -        final Object defaultValue = findDefaultValue();
> -        final Object value = convert(replacedValue, defaultValue);
> -        final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> -        StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> -        return value;
> -    }
> -
> -    private Object findDefaultValue() {
> -        return DEFAULT_VALUE_EXTRACTORS.getOrDefault(
> -                conversionType,
> -                pluginAttribute -> stringSubstitutionStrategy.apply(pluginAttribute.defaultString())
> -        ).apply(annotation);
> -    }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java
> new file mode 100644
> index 0000000..1805844
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java
> @@ -0,0 +1,72 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginAttribute;
> +import org.apache.logging.log4j.util.NameUtil;
> +import org.apache.logging.log4j.util.StringBuilders;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.lang.reflect.Type;
> +import java.util.Collections;
> +import java.util.Map;
> +import java.util.Optional;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.function.Function;
> +
> +public class PluginAttributeInjector extends AbstractConfigurationInjector<PluginAttribute, Object> {
> +
> +    private static final Map<Type, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
> +
> +    static {
> +        final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
> +        extractors.put(int.class, PluginAttribute::defaultInt);
> +        extractors.put(Integer.class, PluginAttribute::defaultInt);
> +        extractors.put(long.class, PluginAttribute::defaultLong);
> +        extractors.put(Long.class, PluginAttribute::defaultLong);
> +        extractors.put(boolean.class, PluginAttribute::defaultBoolean);
> +        extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
> +        extractors.put(float.class, PluginAttribute::defaultFloat);
> +        extractors.put(Float.class, PluginAttribute::defaultFloat);
> +        extractors.put(double.class, PluginAttribute::defaultDouble);
> +        extractors.put(Double.class, PluginAttribute::defaultDouble);
> +        extractors.put(byte.class, PluginAttribute::defaultByte);
> +        extractors.put(Byte.class, PluginAttribute::defaultByte);
> +        extractors.put(char.class, PluginAttribute::defaultChar);
> +        extractors.put(Character.class, PluginAttribute::defaultChar);
> +        extractors.put(short.class, PluginAttribute::defaultShort);
> +        extractors.put(Short.class, PluginAttribute::defaultShort);
> +        extractors.put(Class.class, PluginAttribute::defaultClass);
> +        DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
> +    }
> +
> +    @Override
> +    public Object inject(final Object target) {
> +        final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
> +        if (value.isPresent()) {
> +            final String s = value.get();
> +            return optionBinder.bindString(target, s);
> +        } else {
> +            return injectDefaultValue(target);
> +        }
> +    }
> +
> +    private Object injectDefaultValue(final Object target) {
> +        final Function<PluginAttribute, Object> extractor = DEFAULT_VALUE_EXTRACTORS.get(conversionType);
> +        if (extractor != null) {
> +            final Object value = extractor.apply(annotation);
> +            debugLog(value);
> +            return optionBinder.bindObject(target, value);
> +        }
> +        final String value = stringSubstitutionStrategy.apply(annotation.defaultString());
> +        if (Strings.isNotBlank(value)) {
> +            debugLog(value);
> +            return optionBinder.bindString(target, value);
> +        }
> +        return target;
> +    }
> +
> +    private void debugLog(final Object value) {
> +        final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> +        StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> +    }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeBuilder.java
> deleted file mode 100644
> index 8c870db..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeBuilder.java
> +++ /dev/null
> @@ -1,49 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
> -import org.apache.logging.log4j.util.NameUtil;
> -import org.apache.logging.log4j.util.StringBuilders;
> -
> -import java.util.Map;
> -
> -/**
> - * ConfigurationInjectionBuilder for PluginBuilderAttribute. If {@code null} is returned for the
> - * {@link ConfigurationInjectionBuilder#build()}}
> - * method, then the default value of the field should remain untouched.
> - */
> -public class PluginBuilderAttributeBuilder extends AbstractConfigurationInjectionBuilder<PluginBuilderAttribute, Object> {
> -
> -    public PluginBuilderAttributeBuilder() {
> -        super(PluginBuilderAttribute.class);
> -    }
> -
> -    @Override
> -    public Object build() {
> -        final String overridden = this.annotation.value();
> -        final String name = overridden.isEmpty() ? this.member.getName() : overridden;
> -        final Map<String, String> attributes = node.getAttributes();
> -        final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> -        final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> -        final Object value = convert(replacedValue, null);
> -        final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> -        StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> -        return value;
> -    }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java
> new file mode 100644
> index 0000000..db14fa7
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java
> @@ -0,0 +1,19 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
> +import org.apache.logging.log4j.util.NameUtil;
> +import org.apache.logging.log4j.util.StringBuilders;
> +
> +public class PluginBuilderAttributeInjector extends AbstractConfigurationInjector<PluginBuilderAttribute, Object> {
> +    @Override
> +    public Object inject(final Object target) {
> +        return findAndRemoveNodeAttribute()
> +                .map(stringSubstitutionStrategy)
> +                .map(value -> {
> +                    String debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> +                    StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> +                    return optionBinder.bindString(target, value);
> +                })
> +                .orElseGet(() -> optionBinder.bindObject(target, null));
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementBuilder.java
> deleted file mode 100644
> index 9dc4aea..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementBuilder.java
> +++ /dev/null
> @@ -1,110 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.PluginElement;
> -import org.apache.logging.log4j.plugins.util.PluginType;
> -
> -import java.lang.reflect.Array;
> -import java.util.ArrayList;
> -import java.util.Arrays;
> -import java.util.Collection;
> -import java.util.List;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginElement}. Supports arrays as well as singular values.
> - */
> -public class PluginElementBuilder extends AbstractConfigurationInjectionBuilder<PluginElement, Object> {
> -    public PluginElementBuilder() {
> -        super(PluginElement.class);
> -    }
> -
> -    @Override
> -    public Object build() {
> -        final String name = this.annotation.value();
> -        if (this.conversionType.isArray()) {
> -            withConversionType(this.conversionType.getComponentType());
> -            final List<Object> values = new ArrayList<>();
> -            final Collection<Node> used = new ArrayList<>();
> -            debugLog.append("={");
> -            boolean first = true;
> -            for (final Node child : node.getChildren()) {
> -                final PluginType<?> childType = child.getType();
> -                if (name.equalsIgnoreCase(childType.getElementName()) ||
> -                    this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> -                    if (!first) {
> -                        debugLog.append(", ");
> -                    }
> -                    first = false;
> -                    used.add(child);
> -                    final Object childObject = child.getObject();
> -                    if (childObject == null) {
> -                        LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName());
> -                        continue;
> -                    }
> -                    if (childObject.getClass().isArray()) {
> -                        debugLog.append(Arrays.toString((Object[]) childObject)).append('}');
> -                        node.getChildren().removeAll(used);
> -                        return childObject;
> -                    }
> -                    debugLog.append(child.toString());
> -                    values.add(childObject);
> -                }
> -            }
> -            debugLog.append('}');
> -            // note that we need to return an empty array instead of null if the types are correct
> -            if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) {
> -                LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.",
> -                    name, values.get(0).getClass(), this.conversionType);
> -                return null;
> -            }
> -            node.getChildren().removeAll(used);
> -            // we need to use reflection here because values.toArray() will cause type errors at runtime
> -            final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size());
> -            for (int i = 0; i < array.length; i++) {
> -                array[i] = values.get(i);
> -            }
> -            return array;
> -        }
> -        final Node namedNode = findNamedNode(name, node.getChildren());
> -        if (namedNode == null) {
> -            debugLog.append(name).append("=null");
> -            return null;
> -        }
> -        debugLog.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')');
> -        node.getChildren().remove(namedNode);
> -        return namedNode.getObject();
> -    }
> -
> -    private Node findNamedNode(final String name, final Iterable<Node> children) {
> -        for (final Node child : children) {
> -            final PluginType<?> childType = child.getType();
> -            if (childType == null) {
> -                //System.out.println();
> -            }
> -            if (name.equalsIgnoreCase(childType.getElementName()) ||
> -                this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> -                // FIXME: check child.getObject() for null?
> -                // doing so would be more consistent with the array version
> -                return child;
> -            }
> -        }
> -        return null;
> -    }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java
> new file mode 100644
> index 0000000..33f372c
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java
> @@ -0,0 +1,88 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.PluginElement;
> +import org.apache.logging.log4j.plugins.util.PluginType;
> +import org.apache.logging.log4j.plugins.util.TypeUtil;
> +
> +import java.lang.reflect.Array;
> +import java.lang.reflect.Type;
> +import java.util.ArrayList;
> +import java.util.Arrays;
> +import java.util.Collection;
> +import java.util.List;
> +import java.util.Optional;
> +
> +public class PluginElementInjector extends AbstractConfigurationInjector<PluginElement, Object> {
> +    @Override
> +    public Object inject(final Object target) {
> +        final Optional<Class<?>> componentType = getComponentType(conversionType);
> +        if (componentType.isPresent()) {
> +            final Class<?> compType = componentType.get();
> +            final List<Object> values = new ArrayList<>();
> +            final Collection<Node> used = new ArrayList<>();
> +            debugLog.append("={");
> +            boolean first = true;
> +            for (final Node child : node.getChildren()) {
> +                final PluginType<?> type = child.getType();
> +                if (name.equalsIgnoreCase(type.getElementName()) || compType.isAssignableFrom(type.getPluginClass())) {
> +                    if (!first) {
> +                        debugLog.append(", ");
> +                    }
> +                    first = false;
> +                    used.add(child);
> +                    final Object childObject = child.getObject();
> +                    if (childObject == null) {
> +                        LOGGER.warn("Skipping null object returned for element {} in node {}", child.getName(), node.getName());
> +                    } else if (childObject.getClass().isArray()) {
> +                        Object[] children = (Object[]) childObject;
> +                        debugLog.append(Arrays.toString(children)).append('}');
> +                        node.getChildren().removeAll(used);
> +                        return optionBinder.bindObject(target, children);
> +                    } else {
> +                        debugLog.append(child.toString());
> +                        values.add(childObject);
> +                    }
> +                }
> +            }
> +            debugLog.append('}');
> +            if (!values.isEmpty() && !TypeUtil.isAssignable(compType, values.get(0).getClass())) {
> +                LOGGER.error("Cannot assign element {} a list of {} as it is incompatible with {}", name, values.get(0).getClass(), compType);
> +                return null;
> +            }
> +            node.getChildren().removeAll(used);
> +            // using List::toArray here would cause type mismatch later on
> +            final Object[] vals = (Object[]) Array.newInstance(compType, values.size());
> +            for (int i = 0; i < vals.length; i++) {
> +                vals[i] = values.get(i);
> +            }
> +            return optionBinder.bindObject(target, vals);
> +        } else {
> +            final Optional<Node> matchingChild = node.getChildren().stream().filter(this::isRequestedNode).findAny();
> +            if (matchingChild.isPresent()) {
> +                final Node child = matchingChild.get();
> +                debugLog.append(child.getName()).append('(').append(child.toString()).append(')');
> +                node.getChildren().remove(child);
> +                return optionBinder.bindObject(target, child.getObject());
> +            } else {
> +                debugLog.append(name).append("=null");
> +                return optionBinder.bindObject(target, null);
> +            }
> +        }
> +    }
> +
> +    private boolean isRequestedNode(final Node child) {
> +        final PluginType<?> type = child.getType();
> +        return name.equalsIgnoreCase(type.getElementName()) || TypeUtil.isAssignable(conversionType, type.getPluginClass());
> +    }
> +
> +    private static Optional<Class<?>> getComponentType(final Type type) {
> +        if (type instanceof Class<?>) {
> +            final Class<?> clazz = (Class<?>) type;
> +            if (clazz.isArray()) {
> +                return Optional.of(clazz.getComponentType());
> +            }
> +        }
> +        return Optional.empty();
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeBuilder.java
> deleted file mode 100644
> index 06d06f6..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeBuilder.java
> +++ /dev/null
> @@ -1,39 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginNode;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginNode}.
> - */
> -public class PluginNodeBuilder extends AbstractConfigurationInjectionBuilder<PluginNode, Object> {
> -    public PluginNodeBuilder() {
> -        super(PluginNode.class);
> -    }
> -
> -    @Override
> -    public Object build() {
> -        if (this.conversionType.isInstance(node)) {
> -            debugLog.append("Node=").append(node.getName());
> -            return node;
> -        }
> -        LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass());
> -        return null;
> -    }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java
> new file mode 100644
> index 0000000..62353f0
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java
> @@ -0,0 +1,17 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginNode;
> +import org.apache.logging.log4j.plugins.util.TypeUtil;
> +
> +public class PluginNodeInjector extends AbstractConfigurationInjector<PluginNode, Object> {
> +    @Override
> +    public Object inject(final Object target) {
> +        if (TypeUtil.isAssignable(conversionType, node.getClass())) {
> +            debugLog.append("Node=").append(node.getName());
> +            return optionBinder.bindObject(target, node);
> +        } else {
> +            LOGGER.error("Element with type {} annotated with @PluginNode not compatible with type {}.", conversionType, node.getClass());
> +            return target;
> +        }
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueBuilder.java
> deleted file mode 100644
> index 2cfe357..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueBuilder.java
> +++ /dev/null
> @@ -1,52 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - *      http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginValue;
> -import org.apache.logging.log4j.util.StringBuilders;
> -import org.apache.logging.log4j.util.Strings;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginValue}.
> - */
> -public class PluginValueBuilder extends AbstractConfigurationInjectionBuilder<PluginValue, Object> {
> -    public PluginValueBuilder() {
> -        super(PluginValue.class);
> -    }
> -
> -    @Override
> -    public Object build() {
> -        final String name = this.annotation.value();
> -        final String elementValue = node.getValue();
> -        final String attributeValue = node.getAttributes().get("value");
> -        String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
> -        if (Strings.isNotEmpty(elementValue)) {
> -            if (Strings.isNotEmpty(attributeValue)) {
> -                LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" +
> -                                " value ({}). Please specify only one value. Using the element value.",
> -                        node.getName(), attributeValue, elementValue);
> -            }
> -            rawValue = elementValue;
> -        } else {
> -            rawValue = removeAttributeValue(node.getAttributes(), "value");
> -        }
> -        final String value = stringSubstitutionStrategy.apply(rawValue);
> -        StringBuilders.appendKeyDqValue(debugLog, name, value);
> -        return value;
> -    }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java
> new file mode 100644
> index 0000000..0325bba
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java
> @@ -0,0 +1,27 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginValue;
> +import org.apache.logging.log4j.util.StringBuilders;
> +import org.apache.logging.log4j.util.Strings;
> +
> +public class PluginValueInjector extends AbstractConfigurationInjector<PluginValue, Object> {
> +    @Override
> +    public Object inject(final Object target) {
> +        final String elementValue = node.getValue();
> +        final String attributeValue = node.getAttributes().get(name);
> +        String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
> +        if (Strings.isNotEmpty(elementValue)) {
> +            if (Strings.isNotEmpty(attributeValue)) {
> +                LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" +
> +                                " value ({}). Please specify only one value. Using the element value.",
> +                        node.getName(), attributeValue, elementValue);
> +            }
> +            rawValue = elementValue;
> +        } else {
> +            rawValue = findAndRemoveNodeAttribute().orElse(null);
> +        }
> +        final String value = stringSubstitutionStrategy.apply(rawValue);
> +        StringBuilders.appendKeyDqValue(debugLog, name, value);
> +        return optionBinder.bindString(target, value);
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
> index 47259e0..a2afb2b 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
> @@ -17,7 +17,7 @@
>
>  /**
>   * Injection builder classes for parsing data from a {@code Configuration} or {@link org.apache.logging.log4j.plugins.Node}
> - * corresponding to an {@link org.apache.logging.log4j.plugins.inject.InjectionStrategy}-annotated annotation.
> - * Injection strategies must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder}.
> + * corresponding to an {@link org.apache.logging.log4j.plugins.inject.InjectorStrategy}-annotated annotation.
> + * Injection strategies must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjector}.
>   */
>  package org.apache.logging.log4j.plugins.inject;
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java
> new file mode 100644
> index 0000000..a5d7faf
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java
> @@ -0,0 +1,69 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.util.ReflectionUtil;
> +
> +import java.beans.Introspector;
> +import java.lang.annotation.Annotation;
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Field;
> +import java.lang.reflect.Method;
> +import java.lang.reflect.Parameter;
> +import java.util.Optional;
> +
> +/**
> + * Extracts a specified name for some configurable annotated element. A specified name is one given in a non-empty
> + * string in an annotation as opposed to relying on the default name taken from the annotated element itself.
> + *
> + * @param <A> plugin configuration annotation
> + */
> +public interface AnnotatedElementNameProvider<A extends Annotation> {
> +
> +    static String getName(final AnnotatedElement element) {
> +        for (final Annotation annotation : element.getAnnotations()) {
> +            final Optional<String> specifiedName = getSpecifiedNameForAnnotation(annotation);
> +            if (specifiedName.isPresent()) {
> +                return specifiedName.get();
> +            }
> +        }
> +
> +        if (element instanceof Field) {
> +            return ((Field) element).getName();
> +        }
> +
> +        if (element instanceof Method) {
> +            final Method method = (Method) element;
> +            final String methodName = method.getName();
> +            if (methodName.startsWith("set")) {
> +                return Introspector.decapitalize(methodName.substring(3));
> +            }
> +            if (methodName.startsWith("with")) {
> +                return Introspector.decapitalize(methodName.substring(4));
> +            }
> +            return method.getParameters()[0].getName();
> +        }
> +
> +        if (element instanceof Parameter) {
> +            return ((Parameter) element).getName();
> +        }
> +
> +        throw new IllegalArgumentException("Unknown element type for naming: " + element.getClass());
> +    }
> +
> +    static <A extends Annotation> Optional<String> getSpecifiedNameForAnnotation(final A annotation) {
> +        return Optional.ofNullable(annotation.annotationType().getAnnotation(NameProvider.class))
> +                .map(NameProvider::value)
> +                .flatMap(clazz -> {
> +                    @SuppressWarnings("unchecked") final AnnotatedElementNameProvider<A> factory =
> +                            (AnnotatedElementNameProvider<A>) ReflectionUtil.instantiate(clazz);
> +                    return factory.getSpecifiedName(annotation);
> +                });
> +    }
> +
> +    /**
> +     * Returns the specified name from this annotation if given or {@code Optional.empty()} if none given.
> +     *
> +     * @param annotation annotation value of configuration element
> +     * @return specified name of configuration element or empty if none specified
> +     */
> +    Optional<String> getSpecifiedName(final A annotation);
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java
> new file mode 100644
> index 0000000..c374543
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java
> @@ -0,0 +1,15 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import java.lang.annotation.Annotation;
> +import java.lang.annotation.Documented;
> +import java.lang.annotation.ElementType;
> +import java.lang.annotation.Retention;
> +import java.lang.annotation.RetentionPolicy;
> +import java.lang.annotation.Target;
> +
> +@Documented
> +@Retention(RetentionPolicy.RUNTIME)
> +@Target(ElementType.ANNOTATION_TYPE)
> +public @interface NameProvider {
> +    Class<? extends AnnotatedElementNameProvider<? extends Annotation>> value();
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java
> new file mode 100644
> index 0000000..29790a8
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginAttribute;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginAttributeNameProvider implements AnnotatedElementNameProvider<PluginAttribute> {
> +    @Override
> +    public Optional<String> getSpecifiedName(final PluginAttribute annotation) {
> +        return Strings.trimToOptional(annotation.value());
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java
> new file mode 100644
> index 0000000..ae2c12a
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginBuilderAttributeNameProvider implements AnnotatedElementNameProvider<PluginBuilderAttribute> {
> +    @Override
> +    public Optional<String> getSpecifiedName(final PluginBuilderAttribute annotation) {
> +        return Strings.trimToOptional(annotation.value());
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java
> new file mode 100644
> index 0000000..b14eb81
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginElement;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginElementNameProvider implements AnnotatedElementNameProvider<PluginElement> {
> +    @Override
> +    public Optional<String> getSpecifiedName(final PluginElement annotation) {
> +        return Strings.trimToOptional(annotation.value());
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java
> new file mode 100644
> index 0000000..515264c
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginValue;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginValueNameProvider implements AnnotatedElementNameProvider<PluginValue> {
> +    @Override
> +    public Optional<String> getSpecifiedName(final PluginValue annotation) {
> +        return Strings.trimToOptional(annotation.value());
> +    }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
> index b161185..b515d96 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
> @@ -17,5 +17,8 @@
>
>  /**
>   * Annotations for Log4j 2 plugins.
> + *
> + * @see org.apache.logging.log4j.plugins.Plugin
> + * @see org.apache.logging.log4j.plugins.PluginFactory
>   */
>  package org.apache.logging.log4j.plugins;
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
> index 9b8a75d..dd0efa8 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
> @@ -29,7 +29,7 @@ import java.lang.annotation.*;
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.FIELD, ElementType.PARAMETER})
> +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
>  @Constraint(RequiredValidator.class)
>  public @interface Required {
>
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
> index 14dd9a8..6a31ef4 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
> @@ -30,7 +30,7 @@ import java.net.InetAddress;
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.FIELD, ElementType.PARAMETER})
> +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
>  @Constraint(ValidHostValidator.class)
>  public @interface ValidHost {
>
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
> index c4aba16..edb7e72 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
> @@ -29,7 +29,7 @@ import java.lang.annotation.*;
>   */
>  @Documented
>  @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.FIELD, ElementType.PARAMETER})
> +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
>  @Constraint(ValidPortValidator.class)
>  public @interface ValidPort {
>
> diff --git a/src/changes/changes.xml b/src/changes/changes.xml
> index 0a4d5f7..09be11f 100644
> --- a/src/changes/changes.xml
> +++ b/src/changes/changes.xml
> @@ -31,6 +31,15 @@
>           - "remove" - Removed
>      -->
>      <release version="3.0.0" date="2019-xx-xx" description="GA Release 3.0.0">
> +      <action issue="LOG4J2-2700" dev="mattsicker" type="add">
> +        Add support for injecting plugin configuration via builder methods.
> +      </action>
> +      <action issue="LOG4J2-2693" dev="mattsicker" type="fix">
> +        @PluginValue does not support attribute names besides "value".
> +      </action>
> +      <action issue="LOG4J2-860" dev="mattsicker" type="update">
> +        Unify plugin builders and plugin factories.
> +      </action>
>        <action issue="LOG4J2-2690" dev="rgoers" type="update">
>          Locate plugins in modules.
>        </action>
>


-- 
Matt Sicker
Secretary, Apache Software Foundation
VP Logging Services, ASF