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 2021/07/11 20:44:25 UTC

[logging-log4j2] 03/03: Add foundation for bean annotation processing and plugin metadata

This is an automated email from the ASF dual-hosted git repository.

mattsicker pushed a commit to branch mean-bean-machine
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 7ff8c9cd05413ed4ac54bd4f2e79481aa29070f4
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sat Jul 10 19:54:19 2021 -0500

    Add foundation for bean annotation processing and plugin metadata
---
 log4j-core/pom.xml                                 |   1 +
 .../logging/log4j/core/config/di/BeanManager.java  |  70 +++--
 .../core/config/di/impl/DefaultBeanManager.java    |  10 +-
 .../config/di/impl/DefaultInjectionTarget.java     |   6 +-
 .../di/impl/DefaultInjectionTargetFactory.java     |  10 +-
 .../log4j/core/config/di/impl/Injector.java        |   3 +-
 .../log4j/core/config/plugins/PluginAttribute.java |   2 +
 .../config/plugins/PluginBuilderAttribute.java     |   2 +
 .../core/config/plugins/PluginBuilderFactory.java  |   9 +-
 .../core/config/plugins/PluginConfiguration.java   |   2 +
 .../log4j/core/config/plugins/PluginElement.java   |   2 +
 .../log4j/core/config/plugins/PluginFactory.java   |   9 +-
 .../log4j/core/config/plugins/PluginNode.java      |   2 +
 .../log4j/core/config/plugins/PluginValue.java     |   2 +
 log4j-plugins/pom.xml                              |  14 +-
 .../org/apache/logging/log4j/plugins/Plugin.java   |   3 +
 .../logging/log4j/plugins/PluginAliases.java       |   2 +
 .../logging/log4j/plugins/PluginAttribute.java     |   2 +
 .../log4j/plugins/PluginBuilderAttribute.java      |   2 +
 .../logging/log4j/plugins/PluginElement.java       |   2 +
 .../logging/log4j/plugins/PluginFactory.java       |   3 +
 .../apache/logging/log4j/plugins/PluginNode.java   |   2 +
 .../apache/logging/log4j/plugins/PluginValue.java  |   2 +
 .../org/apache/logging/log4j/plugins/di/Named.java |   1 +
 .../logging/log4j/plugins/di/NamedAliases.java     |   1 +
 .../di/{AnnotationAlias.java => Producer.java}     |  12 +-
 .../apache/logging/log4j/plugins/di/Produces.java  |   3 +-
 .../plugins/di/{Named.java => Qualifier.java}      |  13 +-
 .../log4j/plugins/di/spi/BeanInfoService.java      |  52 ++++
 .../log4j/plugins/name/NamedAliasesProvider.java   |   2 +-
 .../log4j/plugins/name/PluginNameProvider.java     |  20 +-
 .../log4j/plugins/processor/BeanProcessor.java     | 342 +++++++++++++++++++++
 .../log4j/plugins/processor/PluginProcessor.java   |  14 +-
 .../log4j/plugins/processor/PluginService.java     |   4 +-
 .../logging/log4j/plugins/util/AnnotationUtil.java |  10 +-
 log4j-plugins/src/main/java9/module-info.java      |   9 +-
 .../services/javax.annotation.processing.Processor |  18 ++
 .../plugins/test/validation/ExampleBean.java}      |  38 ++-
 .../plugins/test/validation/ImplicitBean.java}     |  37 ++-
 .../test/validation/ImplicitMethodBean.java}       |  45 ++-
 .../plugins/test/validation/ProductionBean.java    |  56 ++++
 .../log4j/plugins/processor/BeanProcessorTest.java |  54 ++++
 log4j-plugins/src/test/java9/module-info.java      |  20 +-
 43 files changed, 759 insertions(+), 154 deletions(-)

diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index 0d5c9c9..8402405 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -330,6 +330,7 @@
                   <includes>
                     <include>module-info.class</include>
                     <include>**/Log4jPlugins.class</include>
+                    <include>**/Log4jBeanInfo.class</include>
                   </includes>
                 </fileset>
                 <fileset>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/BeanManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/BeanManager.java
index 4e35ae6..3168e1b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/BeanManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/BeanManager.java
@@ -18,9 +18,9 @@
 package org.apache.logging.log4j.core.config.di;
 
 import org.apache.logging.log4j.plugins.di.Inject;
-import org.apache.logging.log4j.plugins.di.Produces;
+import org.apache.logging.log4j.plugins.di.Producer;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
-import org.apache.logging.log4j.plugins.name.NameProvider;
 import org.apache.logging.log4j.plugins.util.AnnotationUtil;
 import org.apache.logging.log4j.util.Strings;
 
@@ -106,7 +106,7 @@ public interface BeanManager extends AutoCloseable {
      * Checks if a class has exactly one injectable constructor. A constructor is <i>injectable</i> if:
      * <ol>
      *     <li>it is annotated with {@link Inject}; or</li>
-     *     <li>it has as least one parameter annotated with {@link Inject} or a {@linkplain NameProvider name provider annotation}; or</li>
+     *     <li>it has as least one parameter annotated with a {@linkplain Qualifier qualifier annotation}; or</li>
      *     <li>it is the lone no-arg constructor.</li>
      * </ol>
      *
@@ -114,60 +114,60 @@ public interface BeanManager extends AutoCloseable {
      * @return true if the class has exactly one injectable constructor or false otherwise
      */
     default boolean isInjectable(final Class<?> type) {
+        boolean result = false;
         int injectConstructors = 0;
         final Constructor<?>[] constructors = type.getDeclaredConstructors();
         for (final Constructor<?> constructor : constructors) {
-            if (AnnotationUtil.isAnnotationPresent(constructor, Inject.class)) {
+            if (constructor.isAnnotationPresent(Inject.class)) {
                 injectConstructors++;
             }
         }
-        if (injectConstructors > 1) {
-            return false;
-        }
-        if (injectConstructors == 1) {
-            return true;
-        }
-
-        int implicitConstructors = 0;
-        for (final Constructor<?> constructor : constructors) {
-            for (final Parameter parameter : constructor.getParameters()) {
-                if (isInjectable(parameter)) {
-                    implicitConstructors++;
-                    break;
+        if (injectConstructors <= 1) {
+            if (injectConstructors == 1) {
+                result = true;
+            } else {
+                int implicitConstructors = 0;
+                for (final Constructor<?> constructor : constructors) {
+                    for (final Parameter parameter : constructor.getParameters()) {
+                        if (AnnotatedElementNameProvider.hasName(parameter)) {
+                            implicitConstructors++;
+                            break;
+                        }
+                    }
+                }
+                if (implicitConstructors <= 1) {
+                    if (implicitConstructors == 1) {
+                        result = true;
+                    } else {
+                        try {
+                            type.getDeclaredConstructor();
+                            result = true;
+                        } catch (final NoSuchMethodException ignored) {
+                        }
+                    }
                 }
             }
         }
-        if (implicitConstructors > 1) {
-            return false;
-        }
-        if (implicitConstructors == 1) {
-            return true;
-        }
 
-        try {
-            type.getDeclaredConstructor();
-            return true;
-        } catch (final NoSuchMethodException ignored) {
-            return false;
-        }
+        return result;
     }
 
     /**
      * Checks if an element is injectable. An element is <i>injectable</i> if:
      * <ol>
      *     <li>it is annotated with {@link Inject}; or</li>
-     *     <li>it is annotated with a {@linkplain NameProvider name provider annotation} and is not annotated
-     *     with {@link Produces}.</li>
+     *     <li>it is annotated with a {@linkplain Qualifier qualifier annotation}
+     *     and is not annotated with a {@link Producer} annotation.</li>
      * </ol>
      *
      * @param element field, method, or parameter to check
      * @return true if the element is injectable or false otherwise
      */
     default boolean isInjectable(final AnnotatedElement element) {
-        if (AnnotationUtil.isAnnotationPresent(element, Inject.class)) {
+        if (element.isAnnotationPresent(Inject.class)) {
             return true;
         }
-        if (AnnotationUtil.isAnnotationPresent(element, Produces.class)) {
+        if (AnnotationUtil.isMetaAnnotationPresent(element, Producer.class)) {
             return false;
         }
         return AnnotatedElementNameProvider.hasName(element);
@@ -258,4 +258,8 @@ public interface BeanManager extends AutoCloseable {
     // TODO: integrate with TypeConverters
     // TODO: need some sort of default value strategy to bridge over @PluginAttribute and optional injected values
     // TODO: add support for injecting collections and arrays
+    // TODO: begin integrating with singleton beans in log4j-core
+    // TODO: LoggerContext scope (sort of like a singleton/application scope with each LoggerContext)
+    // TODO: configuration scope? should be similar to LoggerContext scope but can be restarted/reconfigured at runtime
+    // TODO: update annotation processor to output bean descriptors for lazy loading (attempt to provide partial type closures?)
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultBeanManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultBeanManager.java
index a835ae0..7dc6921 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultBeanManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultBeanManager.java
@@ -32,7 +32,7 @@ import org.apache.logging.log4j.core.config.di.UnsatisfiedBeanException;
 import org.apache.logging.log4j.core.config.di.ValidationException;
 import org.apache.logging.log4j.plugins.di.DependentScoped;
 import org.apache.logging.log4j.plugins.di.Disposes;
-import org.apache.logging.log4j.plugins.di.Produces;
+import org.apache.logging.log4j.plugins.di.Producer;
 import org.apache.logging.log4j.plugins.di.Provider;
 import org.apache.logging.log4j.plugins.di.ScopeType;
 import org.apache.logging.log4j.plugins.di.SingletonScoped;
@@ -88,7 +88,7 @@ public class DefaultBeanManager implements BeanManager {
             loadDisposerMethods(beanClass, bean);
             for (Class<?> clazz = beanClass; clazz != null; clazz = clazz.getSuperclass()) {
                 for (final Method method : clazz.getDeclaredMethods()) {
-                    if (AnnotationUtil.isAnnotationPresent(method, Produces.class)) {
+                    if (AnnotationUtil.isMetaAnnotationPresent(method, Producer.class)) {
                         method.setAccessible(true);
                         loadedBeans.add(createBean(method, bean));
                     }
@@ -96,7 +96,7 @@ public class DefaultBeanManager implements BeanManager {
             }
             for (Class<?> clazz = beanClass; clazz != null; clazz = clazz.getSuperclass()) {
                 for (final Field field : clazz.getDeclaredFields()) {
-                    if (AnnotationUtil.isAnnotationPresent(field, Produces.class)) {
+                    if (AnnotationUtil.isMetaAnnotationPresent(field, Producer.class)) {
                         field.setAccessible(true);
                         loadedBeans.add(createBean(field, bean));
                     }
@@ -173,7 +173,7 @@ public class DefaultBeanManager implements BeanManager {
     private void loadDisposerMethods(final Class<?> beanClass, final Bean<?> bean) {
         for (final Method method : beanClass.getDeclaredMethods()) {
             for (final Parameter parameter : method.getParameters()) {
-                if (AnnotationUtil.isAnnotationPresent(parameter, Disposes.class)) {
+                if (parameter.isAnnotationPresent(Disposes.class)) {
                     final String name = AnnotatedElementNameProvider.getName(parameter);
                     final Collection<String> aliases = AnnotatedElementAliasesProvider.getAliases(parameter);
                     method.setAccessible(true);
@@ -217,7 +217,7 @@ public class DefaultBeanManager implements BeanManager {
     @Override
     public void validateInjectionPoint(final InjectionPoint point) {
         final AnnotatedElement element = point.getElement();
-        if (AnnotationUtil.isAnnotationPresent(element, Produces.class)) {
+        if (AnnotationUtil.isMetaAnnotationPresent(element, Producer.class)) {
             throw new DefinitionException("Cannot inject into a @Produces element: " + element);
         }
         final Type type = point.getType();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTarget.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTarget.java
index a6ebed9..46eaad9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTarget.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTarget.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.core.config.di.InjectionPoint;
 import org.apache.logging.log4j.core.config.di.InjectionTarget;
 import org.apache.logging.log4j.plugins.di.Disposes;
 import org.apache.logging.log4j.plugins.di.Inject;
-import org.apache.logging.log4j.plugins.di.Produces;
+import org.apache.logging.log4j.plugins.di.Producer;
 import org.apache.logging.log4j.plugins.util.AnnotationUtil;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 
@@ -86,8 +86,8 @@ class DefaultInjectionTarget<T> implements InjectionTarget<T> {
             final Member member = point.getMember();
             final AnnotatedElement element = point.getElement();
             if (member instanceof Method && !injectedMethods.contains(member) &&
-                    !AnnotationUtil.isAnnotationPresent(element, Produces.class) &&
-                    !AnnotationUtil.isAnnotationPresent(element, Disposes.class)) {
+                    !AnnotationUtil.isMetaAnnotationPresent(element, Producer.class) &&
+                    !element.isAnnotationPresent(Disposes.class)) {
                 final Method method = TypeUtil.cast(member);
                 final Set<InjectionPoint> methodInjectionPoints = injectionPoints.stream()
                         .filter(p -> method.equals(p.getMember()))
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTargetFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTargetFactory.java
index 4d99fd7..d3ed010 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTargetFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/DefaultInjectionTargetFactory.java
@@ -26,7 +26,7 @@ import org.apache.logging.log4j.core.config.di.InjectionTargetFactory;
 import org.apache.logging.log4j.plugins.di.Inject;
 import org.apache.logging.log4j.plugins.di.PostConstruct;
 import org.apache.logging.log4j.plugins.di.PreDestroy;
-import org.apache.logging.log4j.plugins.util.AnnotationUtil;
+import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 
 import java.lang.reflect.AccessibleObject;
@@ -67,7 +67,7 @@ class DefaultInjectionTargetFactory<T> implements InjectionTargetFactory<T> {
     private Constructor<T> getInjectableConstructor() {
         final Constructor<?>[] allConstructors = type.getDeclaredConstructors();
         final List<Constructor<?>> injectConstructors = Arrays.stream(allConstructors)
-                .filter(constructor -> AnnotationUtil.isAnnotationPresent(constructor, Inject.class))
+                .filter(constructor -> constructor.isAnnotationPresent(Inject.class))
                 .collect(Collectors.toList());
         if (injectConstructors.size() > 1) {
             throw new DefinitionException("Found more than one constructor with @Inject for " + type);
@@ -78,7 +78,7 @@ class DefaultInjectionTargetFactory<T> implements InjectionTargetFactory<T> {
             return TypeUtil.cast(constructor);
         }
         final List<Constructor<?>> injectParameterConstructors = Arrays.stream(allConstructors)
-                .filter(constructor -> Arrays.stream(constructor.getParameters()).anyMatch(beanManager::isInjectable))
+                .filter(constructor -> Arrays.stream(constructor.getParameters()).anyMatch(AnnotatedElementNameProvider::hasName))
                 .collect(Collectors.toList());
         if (injectParameterConstructors.size() > 1) {
             throw new DefinitionException("No @Inject constructors found and remaining constructors ambiguous for " + type);
@@ -134,7 +134,7 @@ class DefaultInjectionTargetFactory<T> implements InjectionTargetFactory<T> {
         final List<Method> postConstructMethods = new ArrayList<>();
         for (Class<?> clazz = type; clazz != null; clazz = clazz.getSuperclass()) {
             for (final Method method : clazz.getDeclaredMethods()) {
-                if (AnnotationUtil.isAnnotationPresent(method, PostConstruct.class)) {
+                if (method.isAnnotationPresent(PostConstruct.class)) {
                     postConstructMethods.add(0, method);
                 }
             }
@@ -148,7 +148,7 @@ class DefaultInjectionTargetFactory<T> implements InjectionTargetFactory<T> {
         final List<Method> preDestroyMethods = new ArrayList<>();
         for (Class<?> clazz = type; clazz != null; clazz = clazz.getSuperclass()) {
             for (final Method method : clazz.getDeclaredMethods()) {
-                if (AnnotationUtil.isAnnotationPresent(method, PreDestroy.class)) {
+                if (method.isAnnotationPresent(PreDestroy.class)) {
                     preDestroyMethods.add(method);
                 }
             }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/Injector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/Injector.java
index e29b6ad..eff10f4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/Injector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/di/impl/Injector.java
@@ -22,7 +22,6 @@ import org.apache.logging.log4j.core.config.di.InitializationContext;
 import org.apache.logging.log4j.core.config.di.InitializationException;
 import org.apache.logging.log4j.core.config.di.InjectionPoint;
 import org.apache.logging.log4j.plugins.di.Disposes;
-import org.apache.logging.log4j.plugins.util.AnnotationUtil;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 
 import java.lang.reflect.Constructor;
@@ -96,7 +95,7 @@ public class Injector {
         final Object[] arguments = new Object[parameters.length];
         for (int i = 0; i < parameters.length; i++) {
             final Parameter parameter = parameters[i];
-            if (AnnotationUtil.isAnnotationPresent(parameter, Disposes.class)) {
+            if (parameter.isAnnotationPresent(Disposes.class)) {
                 arguments[i] = producedInstance;
             } else {
                 final InjectionPoint injectionPoint = injectionPoints.stream()
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 a4d62d3..843564f 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
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.config.plugins;
 
 import org.apache.logging.log4j.core.config.plugins.util.PluginAttributeNameProvider;
 import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.name.NameProvider;
 import org.apache.logging.log4j.util.Strings;
@@ -41,6 +42,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD})
 @InjectorStrategy(PluginAttributeVisitor.class)
 @NameProvider(PluginAttributeNameProvider.class)
+@Qualifier
 public @interface PluginAttribute {
 
     /**
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 a7b4a8b..97b4f1b 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
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.core.config.plugins;
 
 import org.apache.logging.log4j.core.config.plugins.util.PluginBuilderAttributeNameProvider;
 import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.name.NameProvider;
 import org.apache.logging.log4j.util.Strings;
@@ -38,6 +39,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD})
 @InjectorStrategy(PluginBuilderAttributeVisitor.class)
 @NameProvider(PluginBuilderAttributeNameProvider.class)
+@Qualifier
 public @interface PluginBuilderAttribute {
 
     /**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
index 0ea7221..11b2955 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
@@ -17,7 +17,13 @@
 
 package org.apache.logging.log4j.core.config.plugins;
 
-import java.lang.annotation.*;
+import org.apache.logging.log4j.plugins.di.Produces;
+
+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 method as a factory for custom Plugin builders.
@@ -26,6 +32,7 @@ import java.lang.annotation.*;
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
+@Produces
 public @interface PluginBuilderFactory {
     // empty
 }
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 dee6f67..e9bc4c5 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
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.core.config.plugins;
 
 import org.apache.logging.log4j.core.config.plugins.inject.PluginConfigurationInjector;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 
 import java.lang.annotation.Documented;
@@ -34,6 +35,7 @@ import java.lang.annotation.Target;
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
 @InjectorStrategy(PluginConfigurationInjector.class)
+@Qualifier
 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 b832da9..70b2d9e 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
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.config.plugins;
 
 import org.apache.logging.log4j.core.config.plugins.util.PluginElementNameProvider;
 import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.name.NameProvider;
 
@@ -36,6 +37,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD})
 @InjectorStrategy(PluginElementVisitor.class)
 @NameProvider(PluginElementNameProvider.class)
+@Qualifier
 public @interface PluginElement {
 
     /**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
index 2e25631..abe1cf4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
@@ -16,7 +16,13 @@
  */
 package org.apache.logging.log4j.core.config.plugins;
 
-import java.lang.annotation.*;
+import org.apache.logging.log4j.plugins.di.Produces;
+
+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 Method as the factory to create the plugin. This annotation should only be used on a {@code static}
@@ -29,6 +35,7 @@ import java.lang.annotation.*;
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
+@Produces
 public @interface PluginFactory {
     // empty
 }
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 14a136c..7765a4b 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,6 +17,7 @@
 package org.apache.logging.log4j.core.config.plugins;
 
 import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 
 import java.lang.annotation.Documented;
@@ -33,6 +34,7 @@ import java.lang.annotation.Target;
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.PARAMETER, ElementType.FIELD})
 @InjectorStrategy(PluginNodeVisitor.class)
+@Qualifier
 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 bcbadc0..0e925d0 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
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.config.plugins;
 
 import org.apache.logging.log4j.core.config.plugins.util.PluginValueNameProvider;
 import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor;
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.name.NameProvider;
 
@@ -39,6 +40,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD})
 @InjectorStrategy(PluginValueVisitor.class)
 @NameProvider(PluginValueNameProvider.class)
+@Qualifier
 public @interface PluginValue {
 
     String value();
diff --git a/log4j-plugins/pom.xml b/log4j-plugins/pom.xml
index a78d381..c562089 100644
--- a/log4j-plugins/pom.xml
+++ b/log4j-plugins/pom.xml
@@ -105,6 +105,7 @@
                   <includes>
                     <include>module-info.class</include>
                     <include>**/Log4jPlugins.class</include>
+                    <include>**/Log4jBeanInfo.class</include>
                   </includes>
                 </fileset>
                 <fileset>
@@ -142,9 +143,11 @@
               <source>${maven.compiler.source}</source>
               <target>${maven.compiler.target}</target>
               <proc>only</proc>
-              <compilerArguments>
-                <processor>org.apache.logging.log4j.plugins.processor.PluginProcessor</processor>
-              </compilerArguments>
+              <annotationProcessors>
+                <annotationProcessor>org.apache.logging.log4j.plugins.processor.PluginProcessor</annotationProcessor>
+                <annotationProcessor>org.apache.logging.log4j.plugins.processor.BeanProcessor</annotationProcessor>
+              </annotationProcessors>
+              <parameters>true</parameters>
             </configuration>
           </execution>
           <execution>
@@ -160,6 +163,7 @@
               <proc>only</proc>
               <annotationProcessors>
                 <annotationProcessor>org.apache.logging.log4j.plugins.processor.PluginProcessor</annotationProcessor>
+                <annotationProcessor>org.apache.logging.log4j.plugins.processor.BeanProcessor</annotationProcessor>
               </annotationProcessors>
               <compileSourceRoots>
                 <compileSourceRoot>${project.basedir}/src/test/java-test</compileSourceRoot>
@@ -180,7 +184,11 @@
               <proc>only</proc>
               <annotationProcessors>
                 <annotationProcessor>org.apache.logging.log4j.plugins.processor.PluginProcessor</annotationProcessor>
+                <annotationProcessor>org.apache.logging.log4j.plugins.processor.BeanProcessor</annotationProcessor>
               </annotationProcessors>
+              <compilerArguments>
+                <ApluginPackage>org.apache.logging.log4j.plugins</ApluginPackage>
+              </compilerArguments>
               <compileSourceRoots>
                 <compileSourceRoot>${project.basedir}/src/test/java</compileSourceRoot>
                 <compileSourceRoot>${project.basedir}/src/test/java-test</compileSourceRoot>
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.java
index 18f04ee..9ef058c 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.java
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.name.NameProvider;
+import org.apache.logging.log4j.plugins.name.PluginNameProvider;
 import org.apache.logging.log4j.util.Strings;
 
 import java.lang.annotation.Documented;
@@ -30,6 +32,7 @@ import java.lang.annotation.Target;
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
+@NameProvider(PluginNameProvider.class)
 public @interface Plugin {
 
     /**
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 07acea7..e3f41c8 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
@@ -16,6 +16,7 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.name.AliasesProvider;
 import org.apache.logging.log4j.plugins.name.PluginAliasesProvider;
 
@@ -33,6 +34,7 @@ import java.lang.annotation.Target;
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
 @AliasesProvider(PluginAliasesProvider.class)
+@Qualifier
 public @interface PluginAliases {
 
     /**
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 d844d02..4b5c9f0 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,6 +16,7 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.inject.PluginAttributeInjector;
 import org.apache.logging.log4j.plugins.name.NameProvider;
@@ -47,6 +48,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
 @InjectorStrategy(PluginAttributeInjector.class)
 @NameProvider(PluginAttributeNameProvider.class)
+@Qualifier
 public @interface PluginAttribute {
 
     /**
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 a0a39b5..2eebabe 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,6 +17,7 @@
 
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.inject.PluginBuilderAttributeInjector;
 import org.apache.logging.log4j.plugins.name.NameProvider;
@@ -39,6 +40,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
 @InjectorStrategy(PluginBuilderAttributeInjector.class)
 @NameProvider(PluginBuilderAttributeNameProvider.class)
+@Qualifier
 @Deprecated
 public @interface PluginBuilderAttribute {
 
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 fa296ca..6951de1 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,6 +16,7 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.inject.PluginElementInjector;
 import org.apache.logging.log4j.plugins.name.NameProvider;
@@ -38,6 +39,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
 @InjectorStrategy(PluginElementInjector.class)
 @NameProvider(PluginElementNameProvider.class)
+@Qualifier
 public @interface PluginElement {
 
     /**
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 f1d89c6..f32bc38 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
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Producer;
+
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -37,6 +39,7 @@ import java.lang.annotation.Target;
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
+@Producer
 public @interface PluginFactory {
     // empty
 }
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 dcc5b0c..eec9d8f 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,6 +16,7 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.inject.PluginNodeInjector;
 
@@ -34,6 +35,7 @@ import java.lang.annotation.Target;
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
 @InjectorStrategy(PluginNodeInjector.class)
+@Qualifier
 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 5e7a3fe..b320306 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,6 +16,7 @@
  */
 package org.apache.logging.log4j.plugins;
 
+import org.apache.logging.log4j.plugins.di.Qualifier;
 import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
 import org.apache.logging.log4j.plugins.inject.PluginValueInjector;
 import org.apache.logging.log4j.plugins.name.NameProvider;
@@ -42,6 +43,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
 @InjectorStrategy(PluginValueInjector.class)
 @NameProvider(PluginValueNameProvider.class)
+@Qualifier
 public @interface PluginValue {
 
     /**
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
index 0c7a542..b8b48d4 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
@@ -30,6 +30,7 @@ import java.lang.annotation.RetentionPolicy;
 @Documented
 @NameProvider(NamedQualifierNameProvider.class)
 @Repeatable(NamedAliases.class)
+@Qualifier
 public @interface Named {
     String value() default Strings.EMPTY;
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/NamedAliases.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/NamedAliases.java
index 6903d2b..94adf0c 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/NamedAliases.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/NamedAliases.java
@@ -32,6 +32,7 @@ import java.lang.annotation.Target;
 @Documented
 @NameProvider(NamedAliasesProvider.class)
 @AliasesProvider(NamedAliasesProvider.class)
+@Qualifier
 public @interface NamedAliases {
     Named[] value();
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/AnnotationAlias.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Producer.java
similarity index 70%
rename from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/AnnotationAlias.java
rename to log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Producer.java
index d570c3b..286723f 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/AnnotationAlias.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Producer.java
@@ -17,22 +17,14 @@
 
 package org.apache.logging.log4j.plugins.di;
 
-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 for making an annotation an alias for another annotation. Annotations with this annotation will be
- * interpreted as if they were implemented by the given annotation type instead. This applies to
- * {@linkplain ScopeType scopes}, {@link Inject}, {@link Produces}, {@link Disposes}, {@link PostConstruct}, and
- * {@link PreDestroy}.
- */
-@Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
 @Documented
-public @interface AnnotationAlias {
-    Class<? extends Annotation> value();
+public @interface Producer {
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Produces.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Produces.java
index ba641dd..2739193 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Produces.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Produces.java
@@ -45,8 +45,9 @@ import java.lang.annotation.Target;
  * @see <a href="https://docs.jboss.org/cdi/api/2.0/javax/enterprise/inject/Produces.html">CDI @Produces API Docs</a>
  * @see <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html">Spring @Bean API Docs</a>
  */
-@Target({ElementType.METHOD, ElementType.FIELD})
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
+@Producer
 public @interface Produces {
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Qualifier.java
similarity index 73%
copy from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
copy to log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Qualifier.java
index 0c7a542..85bbbb0 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Qualifier.java
@@ -17,19 +17,14 @@
 
 package org.apache.logging.log4j.plugins.di;
 
-import org.apache.logging.log4j.plugins.name.NameProvider;
-import org.apache.logging.log4j.plugins.name.NamedQualifierNameProvider;
-import org.apache.logging.log4j.util.Strings;
-
 import java.lang.annotation.Documented;
-import java.lang.annotation.Repeatable;
+import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
 @Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
 @Documented
-@NameProvider(NamedQualifierNameProvider.class)
-@Repeatable(NamedAliases.class)
-public @interface Named {
-    String value() default Strings.EMPTY;
+public @interface Qualifier {
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/spi/BeanInfoService.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/spi/BeanInfoService.java
new file mode 100644
index 0000000..f7f45e1
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/spi/BeanInfoService.java
@@ -0,0 +1,52 @@
+/*
+ * 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.di.spi;
+
+import java.util.List;
+import java.util.Map;
+
+public abstract class BeanInfoService {
+    private final List<String> injectableClassNames;
+    private final List<String> producibleClassNames;
+    private final List<String> destructibleClassNames;
+    private final Map<String, List<String>> pluginCategories;
+
+    protected BeanInfoService(final List<String> injectableClassNames, final List<String> producibleClassNames,
+                              final List<String> destructibleClassNames, final Map<String, List<String>> pluginCategories) {
+        this.injectableClassNames = injectableClassNames;
+        this.producibleClassNames = producibleClassNames;
+        this.destructibleClassNames = destructibleClassNames;
+        this.pluginCategories = pluginCategories;
+    }
+
+    public List<String> getInjectableClassNames() {
+        return injectableClassNames;
+    }
+
+    public List<String> getProducibleClassNames() {
+        return producibleClassNames;
+    }
+
+    public List<String> getDestructibleClassNames() {
+        return destructibleClassNames;
+    }
+
+    public Map<String, List<String>> getPluginCategories() {
+        return pluginCategories;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NamedAliasesProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NamedAliasesProvider.java
index a3e12c1..9471784 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NamedAliasesProvider.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NamedAliasesProvider.java
@@ -49,6 +49,6 @@ public class NamedAliasesProvider implements AnnotatedElementNameProvider<NamedA
         for (int i = 0; i < size; i++) {
             aliases.add(named[i + 1].value());
         }
-        return aliases;
+        return Collections.unmodifiableCollection(aliases);
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginNameProvider.java
similarity index 67%
copy from log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
copy to log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginNameProvider.java
index 0ea7221..7e71643 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginNameProvider.java
@@ -15,17 +15,15 @@
  * limitations under the license.
  */
 
-package org.apache.logging.log4j.core.config.plugins;
+package org.apache.logging.log4j.plugins.name;
 
-import java.lang.annotation.*;
+import org.apache.logging.log4j.plugins.Plugin;
 
-/**
- * Marks a method as a factory for custom Plugin builders.
- * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface PluginBuilderFactory {
-    // empty
+import java.util.Optional;
+
+public class PluginNameProvider implements AnnotatedElementNameProvider<Plugin> {
+    @Override
+    public Optional<String> getSpecifiedName(final Plugin annotation) {
+        return Optional.of(annotation.name());
+    }
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/BeanProcessor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/BeanProcessor.java
new file mode 100644
index 0000000..013bab6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/BeanProcessor.java
@@ -0,0 +1,342 @@
+/*
+ * 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.processor;
+
+import org.apache.logging.log4j.plugins.di.Disposes;
+import org.apache.logging.log4j.plugins.di.Inject;
+import org.apache.logging.log4j.plugins.di.Producer;
+import org.apache.logging.log4j.plugins.di.Qualifier;
+import org.apache.logging.log4j.util.Strings;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementKindVisitor9;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleAnnotationValueVisitor9;
+import javax.lang.model.util.Types;
+import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+// TODO: migrate to separate maven module between log4j-plugins and log4j-core
+@SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", "org.apache.logging.log4j.core.config.plugins.*"})
+@SupportedOptions("pluginPackage")
+public class BeanProcessor extends AbstractProcessor {
+    public static final String BEAN_INFO_SERVICE_FILE = "META-INF/services/org.apache.logging.log4j.plugins.di.spi.BeanInfoService";
+
+    public BeanProcessor() {
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latestSupported();
+    }
+
+    private static class ProducerAnnotationVisitor extends ElementKindVisitor9<Void, Void> {
+        private final Set<ExecutableElement> producerMethods = new HashSet<>();
+        private final Set<VariableElement> producerFields = new HashSet<>();
+
+        @Override
+        public Void visitVariableAsField(final VariableElement e, final Void unused) {
+            producerFields.add(e);
+            return null;
+        }
+
+        @Override
+        public Void visitExecutableAsMethod(final ExecutableElement e, final Void unused) {
+            producerMethods.add(e);
+            return null;
+        }
+    }
+
+    private static class DisposesAnnotationVisitor extends ElementKindVisitor9<Void, Void> {
+        private final Set<ExecutableElement> disposesMethods = new HashSet<>();
+
+        @Override
+        public Void visitVariableAsParameter(final VariableElement e, final Void unused) {
+            disposesMethods.add((ExecutableElement) e.getEnclosingElement());
+            return null;
+        }
+    }
+
+    private static class InjectAnnotationVisitor extends ElementKindVisitor9<Void, Void> {
+        private final Set<TypeElement> injectableClasses = new HashSet<>();
+
+        @Override
+        public Void visitVariableAsField(final VariableElement e, final Void unused) {
+            injectableClasses.add((TypeElement) e.getEnclosingElement());
+            return null;
+        }
+
+        @Override
+        public Void visitExecutableAsConstructor(final ExecutableElement e, final Void unused) {
+            injectableClasses.add((TypeElement) e.getEnclosingElement());
+            return null;
+        }
+
+        @Override
+        public Void visitExecutableAsMethod(final ExecutableElement e, final Void unused) {
+            injectableClasses.add((TypeElement) e.getEnclosingElement());
+            return null;
+        }
+    }
+
+    private static class QualifiedAnnotationVisitor extends ElementKindVisitor9<Void, Void> {
+        private final Predicate<AnnotationMirror> isProducerAnnotation;
+        private final Set<TypeElement> injectableClasses = new HashSet<>();
+
+        private QualifiedAnnotationVisitor(final Predicate<AnnotationMirror> isProducerAnnotation) {
+            this.isProducerAnnotation = isProducerAnnotation;
+        }
+
+        @Override
+        public Void visitVariableAsField(final VariableElement e, final Void unused) {
+            if (e.getAnnotationMirrors().stream().noneMatch(isProducerAnnotation)) {
+                injectableClasses.add((TypeElement) e.getEnclosingElement());
+            }
+            return null;
+        }
+
+        @Override
+        public Void visitVariableAsParameter(final VariableElement e, final Void unused) {
+            final Element enclosingExecutable = e.getEnclosingElement();
+            final TypeElement typeElement = (TypeElement) enclosingExecutable.getEnclosingElement();
+            if (enclosingExecutable.getKind() == ElementKind.CONSTRUCTOR ||
+                    enclosingExecutable.getAnnotationMirrors().stream().noneMatch(isProducerAnnotation)) {
+                injectableClasses.add(typeElement);
+            }
+            return null;
+        }
+    }
+
+    private static class PluginAnnotationVisitor extends ElementKindVisitor9<Void, Void> {
+        private final Map<String, Set<TypeElement>> pluginCategories = new HashMap<>();
+
+        @Override
+        public Void visitTypeAsClass(final TypeElement e, final Void unused) {
+            final AnnotationMirror pluginAnnotation = e.getAnnotationMirrors()
+                    .stream()
+                    .filter(ann -> ann.getAnnotationType().asElement().getSimpleName().contentEquals("Plugin"))
+                    .findAny()
+                    .orElseThrow();
+            final ExecutableElement categoryKey = pluginAnnotation.getElementValues()
+                    .keySet()
+                    .stream()
+                    .filter(element -> element.getSimpleName().contentEquals("category"))
+                    .findAny()
+                    .orElseThrow();
+
+            final String category = pluginAnnotation.getElementValues()
+                    .get(categoryKey)
+                    .accept(new SimpleAnnotationValueVisitor9<String, Void>() {
+                        @Override
+                        public String visitString(final String s, final Void unused1) {
+                            return s;
+                        }
+                    }, null);
+            pluginCategories.computeIfAbsent(category, ignored -> new HashSet<>()).add(e);
+
+            return null;
+        }
+    }
+
+    @Override
+    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
+        if (annotations.isEmpty()) {
+            return false;
+        }
+
+        final TypeElement[] producerAnnotations = annotations.stream()
+                .filter(e -> e.getAnnotation(Producer.class) != null)
+                .toArray(TypeElement[]::new);
+        final var producesAnnotationVisitor = new ProducerAnnotationVisitor();
+        roundEnv.getElementsAnnotatedWithAny(producerAnnotations).forEach(producesAnnotationVisitor::visit);
+
+        final var disposesAnnotationVisitor = new DisposesAnnotationVisitor();
+        roundEnv.getElementsAnnotatedWith(Disposes.class).forEach(disposesAnnotationVisitor::visit);
+
+        final var injectAnnotationVisitor = new InjectAnnotationVisitor();
+        roundEnv.getElementsAnnotatedWith(Inject.class).forEach(injectAnnotationVisitor::visit);
+
+        final Types types = processingEnv.getTypeUtils();
+        final var qualifiedAnnotationVisitor = new QualifiedAnnotationVisitor(annotationMirror -> {
+            for (final TypeElement producerAnnotation : producerAnnotations) {
+                if (types.isSameType(producerAnnotation.asType(), annotationMirror.getAnnotationType())) {
+                    return true;
+                }
+            }
+            return false;
+        });
+        final TypeElement[] qualifierAnnotations = annotations.stream()
+                .filter(e -> e.getAnnotation(Qualifier.class) != null)
+                .toArray(TypeElement[]::new);
+        roundEnv.getElementsAnnotatedWithAny(qualifierAnnotations).forEach(qualifiedAnnotationVisitor::visit);
+
+        final TypeElement[] pluginAnnotations = annotations.stream()
+                .filter(e -> e.getSimpleName().contentEquals("Plugin"))
+                .toArray(TypeElement[]::new);
+        final var pluginAnnotationVisitor = new PluginAnnotationVisitor();
+        roundEnv.getElementsAnnotatedWithAny(pluginAnnotations).forEach(pluginAnnotationVisitor::visit);
+
+        final Set<ExecutableElement> producerMethods = producesAnnotationVisitor.producerMethods;
+        final Set<VariableElement> producerFields = producesAnnotationVisitor.producerFields;
+        final Set<ExecutableElement> disposesMethods = disposesAnnotationVisitor.disposesMethods;
+        final Set<TypeElement> injectableClasses = injectAnnotationVisitor.injectableClasses;
+        final Set<TypeElement> implicitInjectableClasses = qualifiedAnnotationVisitor.injectableClasses;
+        final Map<String, Set<TypeElement>> pluginCategories = pluginAnnotationVisitor.pluginCategories;
+        final Set<PackageElement> packageElements = new HashSet<>();
+
+        final Elements elements = processingEnv.getElementUtils();
+        final Set<CharSequence> producibleClassNames = Stream.concat(
+                producerMethods.stream()
+                        .map(e -> (TypeElement) e.getEnclosingElement())
+                        .peek(e -> packageElements.add(elements.getPackageOf(e)))
+                        .map(elements::getBinaryName),
+                producerFields.stream()
+                        .map(e -> (TypeElement) e.getEnclosingElement())
+                        .peek(e -> packageElements.add(elements.getPackageOf(e)))
+                        .map(elements::getBinaryName))
+                .collect(Collectors.toSet());
+        final Set<CharSequence> destructibleClassNames = disposesMethods.stream()
+                .map(e -> (TypeElement) e.getEnclosingElement())
+                .peek(e -> packageElements.add(elements.getPackageOf(e)))
+                .map(elements::getBinaryName)
+                .collect(Collectors.toSet());
+        final Set<CharSequence> injectableClassNames = Stream.concat(
+                injectableClasses.stream()
+                        .peek(e -> packageElements.add(elements.getPackageOf(e)))
+                        .map(elements::getBinaryName),
+                implicitInjectableClasses.stream()
+                        .peek(e -> packageElements.add(elements.getPackageOf(e)))
+                        .map(elements::getBinaryName))
+                .collect(Collectors.toSet());
+        final Map<String, List<CharSequence>> pluginClassNames = pluginCategories.entrySet().stream().collect(
+                Collectors.toMap(Map.Entry::getKey,
+                        entry -> entry.getValue().stream()
+                                .peek(el -> packageElements.add(elements.getPackageOf(el)))
+                                .map(elements::getBinaryName)
+                                .sorted(CharSequence::compare)
+                                .collect(Collectors.toList())));
+
+        String packageName = processingEnv.getOptions().get("pluginPackage");
+        if (packageName == null) {
+            packageName = packageElements.stream()
+                    .map(PackageElement::getQualifiedName)
+                    .map(CharSequence.class::cast)
+                    .reduce(BeanProcessor::commonPrefix)
+                    .orElseThrow()
+                    .toString();
+        }
+        try {
+            writeBeanInfoServiceFile(packageName);
+            writeBeanInfoServiceClass(packageName, injectableClassNames, producibleClassNames, destructibleClassNames, pluginClassNames);
+            return false;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private void writeBeanInfoServiceFile(final String packageName) throws IOException {
+        final FileObject fileObject = processingEnv.getFiler()
+                .createResource(StandardLocation.CLASS_OUTPUT, "", BEAN_INFO_SERVICE_FILE);
+        try (PrintWriter out = new PrintWriter(fileObject.openWriter())) {
+            out.println(packageName + ".plugins.Log4jBeanInfo");
+        }
+    }
+
+    private void writeBeanInfoServiceClass(final String packageName, final Set<CharSequence> injectableClassNames,
+                                           final Set<CharSequence> producibleClassNames,
+                                           final Set<CharSequence> destructibleClassNames,
+                                           final Map<String, List<CharSequence>> pluginClassNames)
+            throws IOException {
+        final JavaFileObject sourceFile = processingEnv.getFiler()
+                .createSourceFile(packageName + ".plugins.Log4jBeanInfo");
+        try (final PrintWriter out = new PrintWriter(sourceFile.openWriter())) {
+            out.println("package " + packageName + ".plugins;");
+            out.println();
+            out.println("import java.util.List;");
+            out.println("import java.util.Map;");
+            out.println();
+            out.println("@javax.annotation.processing.Generated(\"" + getClass().getName() + "\")");
+            out.println("public class Log4jBeanInfo extends org.apache.logging.log4j.plugins.di.spi.BeanInfoService {");
+            out.println();
+            out.println("  private static final List<String> INJECTABLE = List.of(" + getListOfNames(injectableClassNames) + ");");
+            out.println();
+            out.println("  private static final List<String> PRODUCIBLE = List.of(" + getListOfNames(producibleClassNames) + ");");
+            out.println();
+            out.println("  private static final List<String> DESTRUCTIBLE = List.of(" + getListOfNames(destructibleClassNames) + ");");
+            out.println();
+            out.println("  private static final Map<String, List<String>> PLUGIN_CATEGORIES = Map.of(" + getMapOfPluginNames(pluginClassNames) + ");");
+            out.println();
+            out.println("  public Log4jBeanInfo() {");
+            out.println("    super(INJECTABLE, PRODUCIBLE, DESTRUCTIBLE, PLUGIN_CATEGORIES);");
+            out.println("  }");
+            out.println();
+            out.println("}");
+        }
+    }
+
+    private static String getListOfNames(final Set<CharSequence> names) {
+        return names.isEmpty() ? Strings.EMPTY : names.stream().sorted(CharSequence::compare).collect(
+                Collectors.joining("\",\n    \"", "\n    \"", "\"\n  "));
+    }
+
+    private static String getMapOfPluginNames(final Map<String, List<CharSequence>> pluginClassNames) {
+        return pluginClassNames.isEmpty() ? Strings.EMPTY : pluginClassNames.entrySet()
+                .stream()
+                .sorted(Map.Entry.comparingByKey())
+                .map(e -> '"' + e.getKey() + "\", List.of(" + e.getValue().stream().collect(Collectors.joining("\",\n      \"", "\n      \"", "\"\n    ")) + ')')
+                .collect(Collectors.joining(",\n    ", "\n    ", "\n  "));
+    }
+
+    private static CharSequence commonPrefix(final CharSequence str1, final CharSequence str2) {
+        final int minLength = Math.min(str1.length(), str2.length());
+        for (int i = 0; i < minLength; i++) {
+            if (str1.charAt(i) != str2.charAt(i)) {
+                if (i > 1 && str1.charAt(i - 1) == '.') {
+                    return str1.subSequence(0, i - 1);
+                } else {
+                    return str1.subSequence(0, i);
+                }
+            }
+        }
+        return str1.subSequence(0, minLength);
+    }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java
index ec18989..09bef4f 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java
@@ -32,7 +32,7 @@ import javax.lang.model.element.ElementVisitor;
 import javax.lang.model.element.Name;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
-import javax.lang.model.util.SimpleElementVisitor7;
+import javax.lang.model.util.SimpleElementVisitor8;
 import javax.tools.Diagnostic.Kind;
 import javax.tools.FileObject;
 import javax.tools.JavaFileObject;
@@ -95,12 +95,10 @@ public class PluginProcessor extends AbstractProcessor {
             writeClassFile(packageName, list);
             writeServiceFile(packageName);
             messager.printMessage(Kind.NOTE, "Annotations processed");
-            return true;
         } catch (final Exception ex) {
-            ex.printStackTrace();
             error(ex.getMessage());
-            return false;
         }
+        return false;
     }
 
     private void error(final CharSequence message) {
@@ -209,7 +207,7 @@ public class PluginProcessor extends AbstractProcessor {
     /**
      * ElementVisitor to scan the Plugin annotation.
      */
-    private static class PluginElementVisitor extends SimpleElementVisitor7<PluginEntry, Plugin> {
+    private static class PluginElementVisitor extends SimpleElementVisitor8<PluginEntry, Plugin> {
 
         private final Elements elements;
 
@@ -232,7 +230,7 @@ public class PluginProcessor extends AbstractProcessor {
     }
 
     private String commonPrefix(String str1, String str2) {
-        int minLength = str1.length() < str2.length() ? str1.length() : str2.length();
+        int minLength = Math.min(str1.length(), str2.length());
         for (int i = 0; i < minLength; i++) {
             if (str1.charAt(i) != str2.charAt(i)) {
                 if (i > 1 && str1.charAt(i-1) == '.') {
@@ -248,12 +246,12 @@ public class PluginProcessor extends AbstractProcessor {
     /**
      * ElementVisitor to scan the PluginAliases annotation.
      */
-    private static class PluginAliasesElementVisitor extends SimpleElementVisitor7<Collection<PluginEntry>, Plugin> {
+    private static class PluginAliasesElementVisitor extends SimpleElementVisitor8<Collection<PluginEntry>, Plugin> {
 
         private final Elements elements;
 
         private PluginAliasesElementVisitor(final Elements elements) {
-            super(Collections.<PluginEntry> emptyList());
+            super(Collections.emptyList());
             this.elements = elements;
         }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
index 86c068a..f6122dc 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
@@ -18,9 +18,9 @@ package org.apache.logging.log4j.plugins.processor;
 
 import org.apache.logging.log4j.plugins.util.PluginType;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -35,7 +35,7 @@ public abstract class PluginService {
         PluginEntry[] entries = getEntries();
         for (PluginEntry entry : entries) {
             String category = entry.getCategory().toLowerCase();
-            List<PluginType<?>> list = categories.computeIfAbsent(category, ignored -> new LinkedList<>());
+            List<PluginType<?>> list = categories.computeIfAbsent(category, ignored -> new ArrayList<>());
             PluginType<?> type = new PluginType<>(entry, this.getClass().getClassLoader());
             list.add(type);
         }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java
index 7bfcc3f..4b5abc4 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java
@@ -17,20 +17,14 @@
 
 package org.apache.logging.log4j.plugins.util;
 
-import org.apache.logging.log4j.plugins.di.AnnotationAlias;
-
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
 
 public final class AnnotationUtil {
 
-    public static boolean isAnnotationPresent(final AnnotatedElement element, final Class<? extends Annotation> annotationType) {
-        if (element.isAnnotationPresent(annotationType)) {
-            return true;
-        }
+    public static boolean isMetaAnnotationPresent(final AnnotatedElement element, final Class<? extends Annotation> metaAnnotation) {
         for (final Annotation annotation : element.getAnnotations()) {
-            final AnnotationAlias alias = annotation.annotationType().getAnnotation(AnnotationAlias.class);
-            if (alias != null && annotationType.equals(alias.value())) {
+            if (annotation.annotationType().isAnnotationPresent(metaAnnotation)) {
                 return true;
             }
         }
diff --git a/log4j-plugins/src/main/java9/module-info.java b/log4j-plugins/src/main/java9/module-info.java
index b4144dc..4506235 100644
--- a/log4j-plugins/src/main/java9/module-info.java
+++ b/log4j-plugins/src/main/java9/module-info.java
@@ -18,6 +18,7 @@ module org.apache.logging.log4j.plugins {
     exports org.apache.logging.log4j.plugins;
     exports org.apache.logging.log4j.plugins.convert;
     exports org.apache.logging.log4j.plugins.di;
+    exports org.apache.logging.log4j.plugins.di.spi;
     exports org.apache.logging.log4j.plugins.processor;
     exports org.apache.logging.log4j.plugins.util;
     exports org.apache.logging.log4j.plugins.validation;
@@ -27,12 +28,14 @@ module org.apache.logging.log4j.plugins {
     exports org.apache.logging.log4j.plugins.inject;
     exports org.apache.logging.log4j.plugins.name;
 
-    requires java.compiler;
-    requires org.apache.logging.log4j;
+    requires transitive java.compiler; // TODO: break out annotation processor into separate module
+    requires transitive org.apache.logging.log4j;
     requires transitive org.osgi.framework;
 
     provides org.apache.logging.log4j.plugins.processor.PluginService with org.apache.logging.log4j.plugins.convert.plugins.Log4jPlugins;
-    provides javax.annotation.processing.Processor with org.apache.logging.log4j.plugins.processor.PluginProcessor;
+    provides org.apache.logging.log4j.plugins.di.spi.BeanInfoService with org.apache.logging.log4j.plugins.convert.plugins.Log4jBeanInfo;
+    provides javax.annotation.processing.Processor with org.apache.logging.log4j.plugins.processor.PluginProcessor, org.apache.logging.log4j.plugins.processor.BeanProcessor;
 
     uses org.apache.logging.log4j.plugins.processor.PluginService;
+//    uses org.apache.logging.log4j.plugins.di.spi.BeanInfoService;
 }
diff --git a/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor
index 5d6951a..4aebe10 100644
--- a/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor
+++ b/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -14,4 +14,22 @@
 # See the license for the specific language governing permissions and
 # limitations under the license.
 #
+
+#
+# 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.
+#
 org.apache.logging.log4j.plugins.processor.PluginProcessor
+org.apache.logging.log4j.plugins.processor.BeanProcessor
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ExampleBean.java
similarity index 55%
copy from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
copy to log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ExampleBean.java
index 0c7a542..5a89264 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
+++ b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ExampleBean.java
@@ -14,22 +14,32 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+package org.apache.logging.log4j.plugins.test.validation;
 
-package org.apache.logging.log4j.plugins.di;
+import java.util.Map;
+import org.apache.logging.log4j.plugins.di.Inject;
 
-import org.apache.logging.log4j.plugins.name.NameProvider;
-import org.apache.logging.log4j.plugins.name.NamedQualifierNameProvider;
-import org.apache.logging.log4j.util.Strings;
+public class ExampleBean {
+    private final String alpha;
+    private final int beta;
+    private final Map<String, String> gamma;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+    @Inject
+    public ExampleBean(final String alpha, final int beta, final Map<String, String> gamma) {
+        this.alpha = alpha;
+        this.beta = beta;
+        this.gamma = gamma;
+    }
 
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@NameProvider(NamedQualifierNameProvider.class)
-@Repeatable(NamedAliases.class)
-public @interface Named {
-    String value() default Strings.EMPTY;
+    public String getAlpha() {
+        return alpha;
+    }
+
+    public int getBeta() {
+        return beta;
+    }
+
+    public Map<String, String> getGamma() {
+        return gamma;
+    }
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ImplicitBean.java
similarity index 55%
copy from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
copy to log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ImplicitBean.java
index 0c7a542..fd75e5f 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Named.java
+++ b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ImplicitBean.java
@@ -14,22 +14,31 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+package org.apache.logging.log4j.plugins.test.validation;
 
-package org.apache.logging.log4j.plugins.di;
+import java.util.Map;
+import org.apache.logging.log4j.plugins.di.Named;
 
-import org.apache.logging.log4j.plugins.name.NameProvider;
-import org.apache.logging.log4j.plugins.name.NamedQualifierNameProvider;
-import org.apache.logging.log4j.util.Strings;
+public class ImplicitBean {
+    private final String alpha;
+    private final int beta;
+    private final Map<String, String> gamma;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+    public ImplicitBean(@Named final String alpha, final int beta, final Map<String, String> gamma) {
+        this.alpha = alpha;
+        this.beta = beta;
+        this.gamma = gamma;
+    }
 
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@NameProvider(NamedQualifierNameProvider.class)
-@Repeatable(NamedAliases.class)
-public @interface Named {
-    String value() default Strings.EMPTY;
+    public String getAlpha() {
+        return alpha;
+    }
+
+    public int getBeta() {
+        return beta;
+    }
+
+    public Map<String, String> getGamma() {
+        return gamma;
+    }
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ImplicitMethodBean.java
similarity index 50%
copy from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java
copy to log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ImplicitMethodBean.java
index 7bfcc3f..c36b0ef 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java
+++ b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ImplicitMethodBean.java
@@ -14,29 +14,40 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+package org.apache.logging.log4j.plugins.test.validation;
 
-package org.apache.logging.log4j.plugins.util;
+import java.util.Map;
+import org.apache.logging.log4j.plugins.di.Named;
 
-import org.apache.logging.log4j.plugins.di.AnnotationAlias;
+public class ImplicitMethodBean {
+    private String alpha;
+    private int beta;
+    private Map<String, String> gamma;
 
-import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
+    public String getAlpha() {
+        return alpha;
+    }
+
+    public ImplicitMethodBean setAlpha(@Named final String alpha) {
+        this.alpha = alpha;
+        return this;
+    }
 
-public final class AnnotationUtil {
+    public int getBeta() {
+        return beta;
+    }
+
+    public ImplicitMethodBean setBeta(@Named final int beta) {
+        this.beta = beta;
+        return this;
+    }
 
-    public static boolean isAnnotationPresent(final AnnotatedElement element, final Class<? extends Annotation> annotationType) {
-        if (element.isAnnotationPresent(annotationType)) {
-            return true;
-        }
-        for (final Annotation annotation : element.getAnnotations()) {
-            final AnnotationAlias alias = annotation.annotationType().getAnnotation(AnnotationAlias.class);
-            if (alias != null && annotationType.equals(alias.value())) {
-                return true;
-            }
-        }
-        return false;
+    public Map<String, String> getGamma() {
+        return gamma;
     }
 
-    private AnnotationUtil() {
+    public ImplicitMethodBean setGamma(@Named final Map<String, String> gamma) {
+        this.gamma = gamma;
+        return this;
     }
 }
diff --git a/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ProductionBean.java b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ProductionBean.java
new file mode 100644
index 0000000..51cd722
--- /dev/null
+++ b/log4j-plugins/src/test/java-test/org/apache/logging/log4j/plugins/test/validation/ProductionBean.java
@@ -0,0 +1,56 @@
+/*
+ * 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.test.validation;
+
+import org.apache.logging.log4j.plugins.di.Disposes;
+import org.apache.logging.log4j.plugins.di.Inject;
+import org.apache.logging.log4j.plugins.di.Produces;
+import org.apache.logging.log4j.plugins.di.Provider;
+
+public class ProductionBean {
+    private final String alpha;
+
+    private ProductionBean(final String alpha) {
+        this.alpha = alpha;
+    }
+
+    public String getAlpha() {
+        return alpha;
+    }
+
+    @Produces
+    public static final String ALPHA = "hello world";
+
+    public static void destroyBean(@Disposes ProductionBean bean) {
+        System.out.println(bean.getAlpha());
+    }
+
+    public static class Builder implements Provider<ProductionBean> {
+        private String alpha;
+
+        @Inject
+        public Builder setAlpha(String alpha) {
+            this.alpha = alpha;
+            return this;
+        }
+
+        @Override
+        public ProductionBean get() {
+            return new ProductionBean(alpha);
+        }
+    }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/BeanProcessorTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/BeanProcessorTest.java
new file mode 100644
index 0000000..b63070c
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/BeanProcessorTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.processor;
+
+import org.apache.logging.log4j.plugins.di.spi.BeanInfoService;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.ServiceLoader;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class BeanProcessorTest {
+    @Test
+    void canLoadTestCategory() {
+        final BeanInfoService service = ServiceLoader.load(BeanInfoService.class, getClass().getClassLoader())
+                .findFirst()
+                .orElseThrow();
+        final List<String> testPlugins = service.getPluginCategories().get("Test");
+        assertNotNull(testPlugins);
+        assertNotEquals(0, testPlugins.size());
+        assertTrue(testPlugins.stream().anyMatch(name -> name.equals(FakePlugin.class.getName())));
+    }
+
+    @Test
+    void smokeTests() {
+        final BeanInfoService service = ServiceLoader.load(BeanInfoService.class, getClass().getClassLoader())
+                .findFirst()
+                .orElseThrow();
+        assertTrue(service.getInjectableClassNames().stream().anyMatch(name -> name.equals("org.apache.logging.log4j.plugins.test.validation.ExampleBean")));
+        assertTrue(service.getInjectableClassNames().stream().anyMatch(name -> name.equals("org.apache.logging.log4j.plugins.test.validation.ImplicitBean")));
+        assertTrue(service.getInjectableClassNames().stream().anyMatch(name -> name.equals("org.apache.logging.log4j.plugins.test.validation.ImplicitMethodBean")));
+        assertTrue(service.getInjectableClassNames().stream().anyMatch(name -> name.equals("org.apache.logging.log4j.plugins.test.validation.ProductionBean$Builder")));
+        assertTrue(service.getProducibleClassNames().stream().anyMatch(name -> name.equals("org.apache.logging.log4j.plugins.test.validation.ProductionBean")));
+        assertTrue(service.getDestructibleClassNames().stream().anyMatch(name -> name.equals("org.apache.logging.log4j.plugins.test.validation.ProductionBean")));
+    }
+}
\ No newline at end of file
diff --git a/log4j-plugins/src/test/java9/module-info.java b/log4j-plugins/src/test/java9/module-info.java
index 3ce37fc..41a87f2 100644
--- a/log4j-plugins/src/test/java9/module-info.java
+++ b/log4j-plugins/src/test/java9/module-info.java
@@ -1,9 +1,21 @@
 open module org.apache.logging.log4j.plugins {
     exports org.apache.logging.log4j.plugins;
+    exports org.apache.logging.log4j.plugins.convert;
+    exports org.apache.logging.log4j.plugins.di;
+    exports org.apache.logging.log4j.plugins.di.spi;
+    exports org.apache.logging.log4j.plugins.name;
+    exports org.apache.logging.log4j.plugins.processor;
+    exports org.apache.logging.log4j.plugins.util;
+    exports org.apache.logging.log4j.plugins.bind;
+    exports org.apache.logging.log4j.plugins.inject;
+
+    exports org.apache.logging.log4j.plugins.validation;
+    exports org.apache.logging.log4j.plugins.validation.constraints;
+    exports org.apache.logging.log4j.plugins.validation.validators;
     exports org.apache.logging.log4j.plugins.test.validation;
 
-    requires java.compiler;
-    requires org.apache.logging.log4j;
+    requires transitive java.compiler;
+    requires transitive org.apache.logging.log4j;
     requires org.apache.logging.log4j.test;
     requires org.junit.jupiter.api;
     requires org.junit.jupiter.engine;
@@ -12,7 +24,9 @@ open module org.apache.logging.log4j.plugins {
     requires junit;
 
     provides org.apache.logging.log4j.plugins.processor.PluginService with org.apache.logging.log4j.plugins.convert.plugins.Log4jPlugins;
-    provides javax.annotation.processing.Processor with org.apache.logging.log4j.plugins.processor.PluginProcessor;
+    provides org.apache.logging.log4j.plugins.di.spi.BeanInfoService with org.apache.logging.log4j.plugins.convert.plugins.Log4jBeanInfo;
+    provides javax.annotation.processing.Processor with org.apache.logging.log4j.plugins.processor.PluginProcessor, org.apache.logging.log4j.plugins.processor.BeanProcessor;
 
     uses org.apache.logging.log4j.plugins.processor.PluginService;
+    uses org.apache.logging.log4j.plugins.di.spi.BeanInfoService;
 }