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

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

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>


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

Posted by Matt Sicker <ma...@apache.org>.
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