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 2022/03/23 03:43:30 UTC

[logging-log4j2] 01/02: Refactor PluginManager usage to utilise Injector

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 f4c65158299eba194ca24e35482d25540593b795
Author: Matt Sicker <ma...@apache.org>
AuthorDate: Mon Mar 21 18:52:17 2022 -0500

    Refactor PluginManager usage to utilise Injector
    
    Signed-off-by: Matt Sicker <ma...@apache.org>
---
 .../java/org/apache/logging/log4j/core/Core.java   |  6 +++
 .../log4j/core/config/AbstractConfiguration.java   | 34 ++++++++++-----
 .../log4j/core/config/PropertiesPlugin.java        | 22 +++++-----
 .../logging/log4j/core/lookup/Interpolator.java    | 45 ++++++++++++-------
 .../logging/log4j/core/lookup/StrLookup.java       |  5 +++
 .../org/apache/logging/log4j/core/util/Loader.java | 50 +++-------------------
 .../apache/logging/log4j/core/util/Watcher.java    |  8 +++-
 .../logging/log4j/core/util/WatcherFactory.java    | 32 ++++----------
 .../logging/log4j/plugins/di/BindingMap.java       | 14 +++++-
 .../logging/log4j/plugins/di/DefaultInjector.java  | 16 ++++---
 .../org/apache/logging/log4j/plugins/di/Key.java   |  8 ++++
 .../logging/log4j/plugins/util/PluginManager.java  |  8 +++-
 12 files changed, 130 insertions(+), 118 deletions(-)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java
index 6a26c04..0886a81 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java
@@ -17,8 +17,14 @@
 
 package org.apache.logging.log4j.core;
 
+import org.apache.logging.log4j.plugins.Named;
+import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+
 public class Core {
 
     public static final String CATEGORY_NAME = "Core";
 
+    public static final Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY_NAME) Key<>() {};
+
 }
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 383ec1e..5acf726 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
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.config;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LogEvent;
@@ -43,11 +44,11 @@ import org.apache.logging.log4j.core.script.ScriptManager;
 import org.apache.logging.log4j.core.script.ScriptManagerFactory;
 import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.core.util.Source;
 import org.apache.logging.log4j.core.util.WatchManager;
 import org.apache.logging.log4j.core.util.Watcher;
 import org.apache.logging.log4j.core.util.WatcherFactory;
+import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
@@ -56,6 +57,7 @@ import org.apache.logging.log4j.plugins.di.Keys;
 import org.apache.logging.log4j.plugins.util.PluginManager;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.apache.logging.log4j.util.LazyValue;
 import org.apache.logging.log4j.util.NameUtil;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.ServiceRegistry;
@@ -131,9 +133,9 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     private ConcurrentMap<String, LoggerConfig> loggerConfigs = new ConcurrentHashMap<>();
     private List<CustomLevelConfig> customLevels = List.of();
     private final ConcurrentMap<String, String> properties = new ConcurrentHashMap<>();
-    private final StrLookup tempLookup = new Interpolator(properties);
-    private final StrSubstitutor runtimeStrSubstitutor = new RuntimeStrSubstitutor(tempLookup);
-    private final StrSubstitutor configurationStrSubstitutor = new ConfigurationStrSubstitutor(runtimeStrSubstitutor);
+    private final StrLookup tempLookup;
+    private final StrSubstitutor runtimeStrSubstitutor;
+    private final StrSubstitutor configurationStrSubstitutor;
     private LoggerConfig root = new LoggerConfig();
     private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<>();
     private final ConfigurationSource configurationSource;
@@ -158,7 +160,10 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
             injector.init();
         }
         componentMap.put(Configuration.CONTEXT_PROPERTIES, properties);
-        pluginManager = new PluginManager(Node.CATEGORY);
+        tempLookup = new Interpolator(new PropertiesLookup(properties), this);
+        runtimeStrSubstitutor = new RuntimeStrSubstitutor(tempLookup);
+        configurationStrSubstitutor = new ConfigurationStrSubstitutor(runtimeStrSubstitutor);
+        pluginManager = injector.getInstance(Core.PLUGIN_MANAGER_KEY);
         configurationScheduler = injector.getInstance(ConfigurationScheduler.class);
         watchManager = injector.getInstance(WatchManager.class);
         setState(State.INITIALIZING);
@@ -195,6 +200,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
 
     public void setPluginManager(final PluginManager pluginManager) {
         this.pluginManager = pluginManager;
+        injector.registerBinding(Core.PLUGIN_MANAGER_KEY, this::getPluginManager);
     }
 
     @Override
@@ -232,16 +238,17 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
         configurationStrSubstitutor.setConfiguration(this);
         initializeScriptManager();
         pluginManager.collectPlugins(pluginPackages);
-        final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
+        final PluginManager levelPlugins = injector.getInstance(new @Named(Level.CATEGORY) Key<>() {});
         levelPlugins.collectPlugins(pluginPackages);
         final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
         if (plugins != null) {
             for (final PluginType<?> type : plugins.values()) {
+                final Class<?> pluginClass = type.getPluginClass();
                 try {
                     // Cause the class to be initialized if it isn't already.
-                    Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
+                    Class.forName(pluginClass.getName(), true, pluginClass.getClassLoader());
                 } catch (final Exception e) {
-                    LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
+                    LOGGER.error("Unable to initialize {} due to {}", pluginClass.getName(), e.getClass()
                             .getSimpleName(), e);
                 }
             }
@@ -292,7 +299,14 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     private void monitorSource(final Reconfigurable reconfigurable, final ConfigurationSource configSource) {
         if (configSource.getLastModified() > 0) {
             final Source cfgSource = new Source(configSource);
-            final Watcher watcher = WatcherFactory.getInstance(pluginPackages)
+            final Key<WatcherFactory> key = Key.forClass(WatcherFactory.class);
+            injector.registerBindingIfAbsent(key, new LazyValue<>(() -> {
+                final PluginManager pluginManager = injector.getInstance(Watcher.PLUGIN_MANAGER_KEY);
+                pluginManager.collectPlugins(pluginPackages);
+                final Map<String, PluginType<?>> watcherPlugins = pluginManager.getPlugins();
+                return new WatcherFactory(watcherPlugins);
+            }));
+            final Watcher watcher = injector.getInstance(key)
                     .newWatcher(cfgSource, this, reconfigurable, listeners, configSource.getLastModified());
             if (watcher != null) {
                 watchManager.watch(cfgSource, watcher);
@@ -638,7 +652,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
         } else {
             final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
             final StrLookup lookup = map == null ? null : new PropertiesLookup(map);
-            Interpolator interpolator = new Interpolator(lookup, pluginPackages);
+            Interpolator interpolator = new Interpolator(lookup, this);
             runtimeStrSubstitutor.setVariableResolver(interpolator);
             configurationStrSubstitutor.setVariableResolver(interpolator);
         }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java
index d027831..94fede4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java
@@ -16,10 +16,6 @@
  */
 package org.apache.logging.log4j.core.config;
 
-import java.util.HashMap;
-import java.util.Map;
-
-
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.PropertiesLookup;
@@ -29,6 +25,9 @@ import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginElement;
 import org.apache.logging.log4j.plugins.PluginFactory;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Handles properties defined in the configuration.
  */
@@ -47,15 +46,16 @@ public final class PropertiesPlugin {
     @PluginFactory
     public static StrLookup configureSubstitutor(@PluginElement("Properties") final Property[] properties,
                                                  @PluginConfiguration final Configuration config) {
+        final Map<String, String> map;
         if (properties == null) {
-            return new Interpolator(config.getProperties());
-        }
-        final Map<String, String> map = new HashMap<>(config.getProperties());
+            map = config.getProperties();
+        } else {
+            map = new HashMap<>(config.getProperties());
 
-        for (final Property prop : properties) {
-            map.put(prop.getName(), prop.getValue());
+            for (final Property prop : properties) {
+                map.put(prop.getName(), prop.getValue());
+            }
         }
-
-        return new Interpolator(new PropertiesLookup(map), config.getPluginPackages());
+        return new Interpolator(new PropertiesLookup(map), config);
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
index de48103..f05fa46 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
@@ -16,20 +16,21 @@
  */
 package org.apache.logging.log4j.core.lookup;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationAware;
 import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.util.ReflectionUtil;
+import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.util.PluginManager;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ReflectionUtil;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 
 /**
  * Proxies other {@link StrLookup}s using a keys within ${} markers.
@@ -53,19 +54,14 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
-    private static final String JMX_LOOKUP = "org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup";
     private static final String JNDI_LOOKUP = "org.apache.logging.log4j.jndi.lookup.JndiLookup";
-    private static final String WEB_LOOKUP = "org.apache.logging.log4j.web.WebLookup";
-    private static final String DOCKER_LOOKUP = "org.apache.logging.log4j.docker.DockerLookup";
-    private static final String SPRING_LOOKUP = "org.apache.logging.log4j.spring.boot.SpringLookup";
-    private static final String KUBERNETES_LOOKUP = "org.apache.logging.log4j.kubernetes.KubernetesLookup";
 
     private final Map<String, StrLookup> strLookupMap = new HashMap<>();
 
     private final StrLookup defaultLookup;
 
     public Interpolator(final StrLookup defaultLookup) {
-        this(defaultLookup, null);
+        this(defaultLookup, List.of());
     }
 
     /**
@@ -76,7 +72,7 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
      * @since 2.1
      */
     public Interpolator(final StrLookup defaultLookup, final List<String> pluginPackages) {
-        this.defaultLookup = defaultLookup == null ? new PropertiesLookup(new HashMap<String, String>()) : defaultLookup;
+        this.defaultLookup = defaultLookup == null ? new PropertiesLookup(Map.of()) : defaultLookup;
         final PluginManager manager = new PluginManager(CATEGORY);
         manager.collectPlugins(pluginPackages);
         final Map<String, PluginType<?>> plugins = manager.getPlugins();
@@ -93,18 +89,35 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
         }
     }
 
+    public Interpolator(final StrLookup defaultLookup, final Configuration configuration) {
+        this.defaultLookup = defaultLookup == null ? new PropertiesLookup(Map.of()) : defaultLookup;
+        final PluginManager pluginManager = configuration.getComponent(StrLookup.PLUGIN_MANAGER_KEY);
+        pluginManager.collectPlugins(configuration.getPluginPackages());
+        for (final Map.Entry<String, PluginType<?>> entry : pluginManager.getPlugins().entrySet()) {
+            try {
+                final Class<? extends StrLookup> strLookupClass = entry.getValue().getPluginClass().asSubclass(StrLookup.class);
+                // TODO: this could use @RequiredProperty on JndiLookup instead
+                if (!strLookupClass.getName().equals(JNDI_LOOKUP) || Constants.JNDI_LOOKUP_ENABLED) {
+                    strLookupMap.put(entry.getKey().toLowerCase(Locale.ROOT), configuration.getComponent(Key.forClass(strLookupClass)));
+                }
+            } catch (final Throwable t) {
+                handleError(entry.getKey(), t);
+            }
+        }
+    }
+
     /**
      * Create the default Interpolator.
      */
     public Interpolator() {
-        this((Map<String, String>) null);
+        this(Map.of());
     }
 
     /**
      * Creates the default Interpolator with the provided properties.
      */
     public Interpolator(final Map<String, String> properties) {
-        this(new PropertiesLookup(properties), Collections.emptyList());
+        this(new PropertiesLookup(properties), List.of());
     }
 
     public StrLookup getDefaultLookup() {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java
index cdef668..5ef6886 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java
@@ -17,6 +17,9 @@
 package org.apache.logging.log4j.core.lookup;
 
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Named;
+import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.util.PluginManager;
 
 /**
  * Lookup a String key to a String value.
@@ -39,6 +42,8 @@ public interface StrLookup {
      */
     String CATEGORY = "Lookup";
 
+    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+
     /**
      * Looks up a String key to a String value.
      * <p>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
index f3f182f..4cbed02 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
@@ -16,15 +16,15 @@
  */
 package org.apache.logging.log4j.core.util;
 
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
-
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.LoaderUtil;
 import org.apache.logging.log4j.util.PropertiesUtil;
 
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+
 /**
  * Load resources (or images) from various sources.
  */
@@ -192,19 +192,6 @@ public final class Loader {
     }
 
     /**
-     * Loads and initializes a named Class using a given ClassLoader.
-     *
-     * @param className The class name.
-     * @param loader The class loader.
-     * @return The class.
-     * @throws ClassNotFoundException if the class could not be found.
-     */
-    public static Class<?> initializeClass(final String className, final ClassLoader loader)
-            throws ClassNotFoundException {
-        return Class.forName(className, true, loader);
-    }
-
-    /**
      * Loads a named Class using a given ClassLoader.
      *
      * @param className The class name.
@@ -270,33 +257,6 @@ public final class Loader {
     }
 
     /**
-     * Loads and instantiates a class given by a property name.
-     *
-     * @param propertyName The property name to look up a class name for.
-     * @param clazz        The class to cast it to.
-     * @param <T>          The type to cast it to.
-     * @return new instance of the class given in the property or {@code null} if the property was unset.
-     * @throws ClassNotFoundException    if the class isn't available to the usual ClassLoaders
-     * @throws IllegalAccessException    if the class can't be instantiated through a public constructor
-     * @throws InstantiationException    if there was an exception whilst instantiating the class
-     * @throws NoSuchMethodException     if there isn't a no-args constructor on the class
-     * @throws InvocationTargetException if there was an exception whilst constructing the class
-     * @throws ClassCastException        if the constructed object isn't type compatible with {@code T}
-     */
-    public static <T> T newCheckedInstanceOfProperty(final String propertyName, final Class<T> clazz)
-        throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException,
-        IllegalAccessException {
-        final String className = PropertiesUtil.getProperties().getStringProperty(propertyName);
-        final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-        try {
-            Thread.currentThread().setContextClassLoader(getClassLoader());
-            return LoaderUtil.newCheckedInstanceOfProperty(propertyName, clazz);
-        } finally {
-            Thread.currentThread().setContextClassLoader(contextClassLoader);
-        }
-    }
-
-    /**
      * Loads and instantiates a Class using the default constructor.
      *
      * @param clazz The class.
@@ -337,7 +297,7 @@ public final class Loader {
     }
 
     /**
-     * Loads a class by name. This method respects the {@link #IGNORE_TCCL_PROPERTY} Log4j property. If this property is
+     * Loads a class by name. This method respects the {@link LoaderUtil#IGNORE_TCCL_PROPERTY} Log4j property. If this property is
      * specified and set to anything besides {@code false}, then the default ClassLoader will be used.
      *
      * @param className The class name.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
index 61552a9..e02bb09 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
@@ -16,10 +16,13 @@
  */
 package org.apache.logging.log4j.core.util;
 
-import java.util.List;
-
 import org.apache.logging.log4j.core.config.ConfigurationListener;
 import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.apache.logging.log4j.plugins.Named;
+import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+
+import java.util.List;
 
 /**
  * Watches for changes in a Source and performs an action when it is modified.
@@ -29,6 +32,7 @@ import org.apache.logging.log4j.core.config.Reconfigurable;
 public interface Watcher {
 
     String CATEGORY = "Watcher";
+    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
     String ELEMENT_TYPE = "watcher";
 
     /**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
index cd4ec28..343815c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
@@ -16,47 +16,31 @@
  */
 package org.apache.logging.log4j.core.util;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationFileWatcher;
 import org.apache.logging.log4j.core.config.ConfigurationListener;
 import org.apache.logging.log4j.core.config.Reconfigurable;
-import org.apache.logging.log4j.plugins.util.PluginManager;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.status.StatusLogger;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * Creates Watchers of various types.
  */
 public class WatcherFactory {
 
     private static final Logger LOGGER = StatusLogger.getLogger();
-    private static final PluginManager pluginManager = new PluginManager(Watcher.CATEGORY);
-
-    private static volatile WatcherFactory factory;
 
     private final Map<String, PluginType<?>> plugins;
 
-    private WatcherFactory(final List<String> packages) {
-        pluginManager.collectPlugins(packages);
-        plugins = pluginManager.getPlugins();
-    }
-
-    public static WatcherFactory getInstance(final List<String> packages) {
-        if (factory == null) {
-            synchronized (pluginManager) {
-                if (factory == null) {
-                    factory = new WatcherFactory(packages);
-                }
-            }
-        }
-        return factory;
+    public WatcherFactory(final Map<String, PluginType<?>> watcherPlugins) {
+        plugins = watcherPlugins;
     }
 
     public Watcher newWatcher(final Source source, final Configuration configuration, final Reconfigurable reconfigurable,
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java
index 1fe03a6..2a58724 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java
@@ -20,6 +20,7 @@ package org.apache.logging.log4j.plugins.di;
 
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 
+import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
@@ -35,8 +36,17 @@ class BindingMap {
         this.bindings = new ConcurrentHashMap<>(bindings.bindings);
     }
 
-    public <T> Binding<T> get(final Key<T> key) {
-        return TypeUtil.cast(bindings.get(key));
+    public <T> Binding<T> get(final Key<T> key, final Collection<String> aliases) {
+        var binding = bindings.get(key);
+        if (binding == null) {
+            for (final String alias : aliases) {
+                binding = bindings.get(key.withName(alias));
+                if (binding != null) {
+                    break;
+                }
+            }
+        }
+        return TypeUtil.cast(binding);
     }
 
     public <T> void put(final Key<T> key, final Supplier<T> factory) {
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
index 7b70c8a..51bf172 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
@@ -20,6 +20,7 @@ package org.apache.logging.log4j.plugins.di;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.plugins.FactoryType;
 import org.apache.logging.log4j.plugins.Inject;
+import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.PluginException;
 import org.apache.logging.log4j.plugins.QualifierType;
@@ -28,6 +29,7 @@ import org.apache.logging.log4j.plugins.Singleton;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
 import org.apache.logging.log4j.plugins.util.AnnotationUtil;
+import org.apache.logging.log4j.plugins.util.PluginManager;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.plugins.validation.Constraint;
@@ -192,15 +194,16 @@ class DefaultInjector implements Injector {
 
     private <T> Supplier<T> getFactory(
             final Key<T> key, final Collection<String> aliases, final Node node, final Set<Key<?>> chain) {
-        final Binding<T> existing = bindingMap.get(key);
+        final Binding<T> existing = bindingMap.get(key, aliases);
         if (existing != null) {
             return existing.getSupplier();
         }
-        for (final String alias : aliases) {
-            final Binding<T> binding = bindingMap.get(key.withName(alias));
-            if (binding != null) {
-                return binding.getSupplier();
-            }
+        final Class<T> rawType = key.getRawType();
+        final Scope scope = getScopeForType(rawType);
+        if (rawType == PluginManager.class && key.getQualifierType() == Named.class) {
+            final Supplier<T> factory = () -> TypeUtil.cast(new PluginManager(key.getName()));
+            bindingMap.put(key, scope.get(key, factory));
+            return bindingMap.get(key, aliases).getSupplier();
         }
         final Supplier<T> instanceSupplier = () -> {
             final StringBuilder debugLog = new StringBuilder();
@@ -208,7 +211,6 @@ class DefaultInjector implements Injector {
             injectMembers(key, node, instance, chain, debugLog);
             return instance;
         };
-        final Scope scope = getScopeForType(key.getRawType());
         return bindingMap.bindIfAbsent(key, scope.get(key, instanceSupplier));
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java
index 6c4bfc5..8368660 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java
@@ -77,6 +77,10 @@ public class Key<T> {
         hashCode = Objects.hash(type, qualifierType, name.toLowerCase(Locale.ROOT));
     }
 
+    public Type getType() {
+        return type;
+    }
+
     public final Class<T> getRawType() {
         return rawType;
     }
@@ -85,6 +89,10 @@ public class Key<T> {
         return name;
     }
 
+    public Class<? extends Annotation> getQualifierType() {
+        return qualifierType;
+    }
+
     /**
      * Returns a new key using the provided name and the same type and qualifier type as this instance.
      */
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
index b87e5a1..8360515 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
@@ -18,15 +18,21 @@
 package org.apache.logging.log4j.plugins.util;
 
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Singleton;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Loads and manages all the plugins.
  */
+@Singleton
 public class PluginManager {
 
     /**