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 2020/02/23 23:52:04 UTC

[logging-log4j2] 02/03: Add v3 dependency injection SPI

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 bc2e7674e21f24a2a7cec1a5c7b95ade843ce6cc
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sun Feb 23 17:46:40 2020 -0600

    Add v3 dependency injection SPI
    
    Signed-off-by: Matt Sicker <bo...@gmail.com>
---
 .../log4j/plugins/spi/AmbiguousBeanException.java  |  28 ++
 .../log4j/plugins/spi/IllegalProductException.java |  28 ++
 .../log4j/plugins/spi/InitializationException.java |  28 ++
 .../log4j/plugins/spi/InjectionException.java      |  31 ++
 .../log4j/plugins/spi/ResolutionException.java     |  28 ++
 .../plugins/spi/UnsatisfiedBeanException.java      |  32 ++
 .../log4j/plugins/spi/ValidationException.java     |  27 ++
 .../logging/log4j/plugins/spi/bean/Bean.java       |  33 ++
 .../log4j/plugins/spi/bean/BeanManager.java        | 101 ++++++
 .../log4j/plugins/spi/bean/InjectionTarget.java    |  36 +++
 .../plugins/spi/bean/InjectionTargetFactory.java   |  22 ++
 .../logging/log4j/plugins/spi/bean/Producer.java   |  38 +++
 .../log4j/plugins/spi/bean/ProducerFactory.java    |  23 ++
 .../log4j/plugins/spi/model/ElementManager.java    | 159 +++++++++
 .../log4j/plugins/spi/model/InjectionPoint.java    |  59 ++++
 .../logging/log4j/plugins/spi/model/MetaClass.java |  42 +++
 .../log4j/plugins/spi/model/MetaConstructor.java   |  22 ++
 .../log4j/plugins/spi/model/MetaElement.java       |  61 ++++
 .../log4j/plugins/spi/model/MetaExecutable.java    |  24 ++
 .../logging/log4j/plugins/spi/model/MetaField.java |  24 ++
 .../log4j/plugins/spi/model/MetaMember.java        |  26 ++
 .../log4j/plugins/spi/model/MetaMethod.java        |  22 ++
 .../log4j/plugins/spi/model/MetaParameter.java     |  21 ++
 .../logging/log4j/plugins/spi/model/Qualifier.java | 103 ++++++
 .../logging/log4j/plugins/spi/model/Variable.java  |  41 +++
 .../plugins/spi/scope/InitializationContext.java   |  49 +++
 .../log4j/plugins/spi/scope/ScopeContext.java      |  63 ++++
 .../logging/log4j/plugins/spi/scope/Scoped.java    |  58 ++++
 .../logging/log4j/plugins/util/TypeUtil.java       | 359 +++++++++++++++++++--
 29 files changed, 1570 insertions(+), 18 deletions(-)

diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/AmbiguousBeanException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/AmbiguousBeanException.java
new file mode 100644
index 0000000..1342920
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/AmbiguousBeanException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.spi;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+
+import java.util.Collection;
+
+public class AmbiguousBeanException extends ResolutionException {
+    public AmbiguousBeanException(final Collection<? extends Bean<?>> beans, final String target) {
+        super("Found " + beans.size() + " ambiguous beans for " + target + ": " + beans);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/IllegalProductException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/IllegalProductException.java
new file mode 100644
index 0000000..8d1fc7d3
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/IllegalProductException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.spi;
+
+public class IllegalProductException extends InjectionException {
+    public IllegalProductException(final String message) {
+        super(message);
+    }
+
+    public IllegalProductException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InitializationException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InitializationException.java
new file mode 100644
index 0000000..8c11613
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InitializationException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.spi;
+
+public class InitializationException extends InjectionException {
+    public InitializationException(final String message) {
+        super(message);
+    }
+
+    public InitializationException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InjectionException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InjectionException.java
new file mode 100644
index 0000000..853afa9
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InjectionException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.spi;
+
+// TODO: create more exception types such as:
+//       invalid use of annotations
+//       constraint validation errors
+public class InjectionException extends RuntimeException {
+    public InjectionException(final String message) {
+        super(message);
+    }
+
+    public InjectionException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ResolutionException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ResolutionException.java
new file mode 100644
index 0000000..84617cb
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ResolutionException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.spi;
+
+public class ResolutionException extends InjectionException {
+    public ResolutionException(final String message) {
+        super(message);
+    }
+
+    public ResolutionException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/UnsatisfiedBeanException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/UnsatisfiedBeanException.java
new file mode 100644
index 0000000..3b17bf6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/UnsatisfiedBeanException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.spi;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+
+import java.lang.reflect.Type;
+
+public class UnsatisfiedBeanException extends ResolutionException {
+    public UnsatisfiedBeanException(final InjectionPoint<?> point) {
+        super("No beans found for " + point);
+    }
+
+    public UnsatisfiedBeanException(final Type type) {
+        super("No beans found for type " + type);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ValidationException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ValidationException.java
new file mode 100644
index 0000000..1498d1f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ValidationException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.spi;
+
+import java.util.List;
+
+public class ValidationException extends InjectionException {
+    public ValidationException(final List<Throwable> validationErrors) {
+        super("Found " + validationErrors.size() + " error(s) in bean deployment. See suppressed exceptions for details.");
+        validationErrors.forEach(this::addSuppressed);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Bean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Bean.java
new file mode 100644
index 0000000..831a4a8
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Bean.java
@@ -0,0 +1,33 @@
+/*
+ * 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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.util.Collection;
+
+public interface Bean<T> extends Scoped<T>, Variable<T> {
+    Collection<InjectionPoint<?>> getInjectionPoints();
+
+    // for a managed bean: that class
+    // for a producer field or producer method: the declaring class
+    MetaClass<?> getDeclaringClass();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/BeanManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/BeanManager.java
new file mode 100644
index 0000000..d767d50
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/BeanManager.java
@@ -0,0 +1,101 @@
+/*
+ * 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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.ValidationException;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Central SPI for injecting and managing beans and their instances.
+ */
+public interface BeanManager extends AutoCloseable {
+
+    /**
+     * Loads beans from the given classes. This looks for injectable classes and producers, validates them, registers
+     * them in this manager, then returns the loaded beans.
+     *
+     * @param beanClasses classes to load beans from
+     * @return beans loaded from the given classes
+     */
+    Collection<Bean<?>> loadBeans(final Collection<Class<?>> beanClasses);
+
+    /**
+     * Loads beans from the given classes. This looks for injectable classes and producers, validates them, registers
+     * them in this manager, then returns the loaded beans.
+     *
+     * @param beanClasses classes to load beans from
+     * @return beans loaded from the given classes
+     */
+    default Collection<Bean<?>> loadBeans(final Class<?>... beanClasses) {
+        return loadBeans(Arrays.asList(beanClasses));
+    }
+
+    /**
+     * Validates beans and throws a {@link ValidationException} if there are any errors.
+     *
+     * @param beans beans to check for validation errors
+     */
+    void validateBeans(final Iterable<Bean<?>> beans);
+
+    // TODO: re-add query methods for beans as needed
+//    Collection<Bean<?>> getBeans();
+
+    /**
+     * Creates an InitializationContext for a given Scoped instance for use in dependency injection SPIs.
+     *
+     * @param scoped scoped object to create an initialization context for
+     * @param <T>    type of object created by scope
+     * @return new InitializationContext for the given Scoped
+     */
+    <T> InitializationContext<T> createInitializationContext(final Scoped<T> scoped);
+
+    /**
+     * Gets or creates the value for a given bean inside a given InitializationContext.
+     *
+     * @param bean          bean to get or create value for
+     * @param parentContext which context this bean is being used in
+     * @param <T>           type of value
+     * @return value of the bean in the given context
+     */
+    <T> T getValue(final Bean<T> bean, final InitializationContext<?> parentContext);
+
+    /**
+     * Gets the value to use for injecting into a given InjectionPoint in a given InitializationContext.
+     *
+     * @param point         location where injectable value would be injected
+     * @param parentContext which context this value is being injected under
+     * @param <T>           type of injectable value
+     * @return value to inject if defined or empty otherwise
+     */
+    <T> Optional<T> getInjectableValue(final InjectionPoint<T> point, final InitializationContext<?> parentContext);
+
+    @Override
+    void close();
+
+    // TODO: integrate with constraint validators
+    // TODO: integrate with TypeConverters
+    // TODO: need some sort of default value strategy to bridge over @PluginAttribute and optional injected values
+    // TODO: need to support @PluginAliases still
+    // TODO: add support for injecting collections and arrays
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTarget.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTarget.java
new file mode 100644
index 0000000..df5c616
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTarget.java
@@ -0,0 +1,36 @@
+/*
+ * 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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+public interface InjectionTarget<T> extends Producer<T> {
+    // sets values of injected fields and calls initializer methods
+    void inject(final T instance, final InitializationContext<T> context);
+
+    // calls @PostConstruct from top to bottom
+    void postConstruct(final T instance);
+
+    // calls @PreDestroy from bottom to top
+    void preDestroy(final T instance);
+
+    @Override
+    default void dispose(T instance) {
+        // @Disposes only applies to @Produces methods and fields
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTargetFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTargetFactory.java
new file mode 100644
index 0000000..d5ee0df
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTargetFactory.java
@@ -0,0 +1,22 @@
+/*
+ * 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.spi.bean;
+
+public interface InjectionTargetFactory<T> {
+    InjectionTarget<T> createInjectionTarget(final Bean<T> bean);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Producer.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Producer.java
new file mode 100644
index 0000000..6a6d4a0
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Producer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.Collection;
+
+public interface Producer<T> {
+    // for a class: calls @Inject constructor or default constructor
+    // for a producer method, this is invoked on that instance
+    // for a producer field, the value is gotten from that instance
+    T produce(InitializationContext<T> context);
+
+    // for a class, no-op (preDestroy is relevant there instead)
+    // for a producer method or producer field, calls the disposer method
+    void dispose(T instance);
+
+    // for a class: injected fields, @Inject constructor parameters, and initializer method params
+    // for a producer method: method params
+    Collection<InjectionPoint<?>> getInjectionPoints();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/ProducerFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/ProducerFactory.java
new file mode 100644
index 0000000..c266224
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/ProducerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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.spi.bean;
+
+public interface ProducerFactory<D> {
+    // only time bean is null is static @Produces potentially?
+    <T> Producer<T> createProducer(final Bean<T> bean);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
new file mode 100644
index 0000000..cd9dea1
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
@@ -0,0 +1,159 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.api.Default;
+import org.apache.logging.log4j.plugins.api.Inject;
+import org.apache.logging.log4j.plugins.api.Named;
+import org.apache.logging.log4j.plugins.api.Produces;
+import org.apache.logging.log4j.plugins.api.QualifierType;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Manages model metadata of program elements.
+ */
+public interface ElementManager extends AutoCloseable {
+
+    /**
+     * Gets metadata about a class.
+     */
+    <T> MetaClass<T> getMetaClass(final Class<T> clazz);
+
+    /**
+     * Indicates if an annotation type is considered a {@linkplain QualifierType qualifier}.
+     */
+    boolean isQualifierType(Class<? extends Annotation> annotationType);
+
+    /**
+     * Extracts the collection of {@linkplain QualifierType qualifiers} from the annotations on an element.
+     * If no qualifiers other than {@link Named} are present, then the {@link Default} qualifier is also returned in
+     * the collection.
+     *
+     * @param element program element to extract qualifiers from
+     * @return qualifiers present on the element
+     */
+    Collection<Qualifier> getQualifiers(MetaElement<?> element);
+
+    /**
+     * 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 QualifierType qualifier annotation}; or</li>
+     *     <li>it is the lone no-arg constructor.</li>
+     * </ol>
+     *
+     * @param type class to find an injectable constructor in
+     * @return true if the class has exactly one injectable constructor or false otherwise
+     */
+    default boolean isInjectable(final MetaClass<?> type) {
+        final List<MetaConstructor<?>> injectConstructors = type.getConstructors().stream()
+                .filter(constructor -> constructor.isAnnotationPresent(Inject.class))
+                .collect(Collectors.toList());
+        if (injectConstructors.size() > 1) {
+            return false;
+        }
+        if (injectConstructors.size() == 1) {
+            return true;
+        }
+        final List<MetaConstructor<?>> implicitConstructors = type.getConstructors().stream()
+                .filter(constructor -> constructor.getParameters().stream().anyMatch(this::isInjectable))
+                .collect(Collectors.toList());
+        if (implicitConstructors.size() > 1) {
+            return false;
+        }
+        if (implicitConstructors.size() == 1) {
+            return true;
+        }
+        return type.getDefaultConstructor().isPresent();
+    }
+
+    /**
+     * 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 QualifierType qualifier annotation} and is not annotated with {@link Produces}.</li>
+     * </ol>
+     *
+     * @param element field, method, or parameter to check
+     * @return true if the element is injectable or false otherwise
+     */
+    default boolean isInjectable(final MetaElement<?> element) {
+        return element.isAnnotationPresent(Inject.class) ||
+                (element.getAnnotations().stream().map(Annotation::annotationType).anyMatch(this::isQualifierType) &&
+                        !element.isAnnotationPresent(Produces.class));
+    }
+
+    /**
+     * Creates an injection point for a field with an optional owning bean.
+     *
+     * @param field field where injection will take place
+     * @param owner bean where field is located or null for static fields
+     * @param <D>   bean type
+     * @param <T>   field type
+     * @return an injection point describing the field
+     */
+    <D, T> InjectionPoint<T> createFieldInjectionPoint(final MetaField<D, T> field, final Bean<D> owner);
+
+    /**
+     * Creates an injection point for a method or constructor parameter with an optional owning bean.
+     *
+     * @param executable method or constructor where injection will take place
+     * @param parameter  which parameter of that executable to create a point at
+     * @param owner      bean where executable is located or null for static methods
+     * @param <D>        bean type
+     * @param <P>        parameter type
+     * @return an injection point describing the parameter
+     */
+    <D, P> InjectionPoint<P> createParameterInjectionPoint(final MetaExecutable<D, ?> executable,
+                                                           final MetaParameter<P> parameter, final Bean<D> owner);
+
+    /**
+     * Creates a collection of injection points for all the parameters of a method or constructor with an optional
+     * owning bean.
+     *
+     * @param executable method or constructor where injection will take place
+     * @param owner      bean where executable is located or null for static methods
+     * @param <D>        bean type
+     * @return collection of injection points describing the executable parameters
+     */
+    default <D> Collection<InjectionPoint<?>> createExecutableInjectionPoints(final MetaExecutable<D, ?> executable, final Bean<D> owner) {
+        Objects.requireNonNull(executable);
+        return executable.getParameters().stream()
+                .map(parameter -> createParameterInjectionPoint(executable, parameter, owner))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Creates a variable for an element.
+     */
+    <T> Variable<T> createVariable(final MetaElement<T> element);
+
+    /**
+     * Creates a variable for an injection point.
+     */
+    <T> Variable<T> createVariable(final InjectionPoint<T> point);
+
+    @Override
+    void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
new file mode 100644
index 0000000..f4e1de9
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
@@ -0,0 +1,59 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Represents metadata about an element in a program where a value should be injected.
+ *
+ * @param <T> type of element
+ */
+public interface InjectionPoint<T> {
+
+    /**
+     * Gets the generic type information of this point.
+     */
+    Type getType();
+
+    /**
+     * Gets the qualifiers of this point. If no qualifiers other than {@link org.apache.logging.log4j.plugins.api.Named}
+     * are present, then these qualifiers will also include {@link org.apache.logging.log4j.plugins.api.Default}.
+     */
+    Collection<Qualifier> getQualifiers();
+
+    /**
+     * Gets the bean where this injection point is defined or empty for static methods and fields.
+     */
+    Optional<Bean<?>> getBean();
+
+    /**
+     * Gets the field, method, or constructor where injection takes place.
+     */
+    MetaMember<?, ?> getMember();
+
+    /**
+     * Gets the program element corresponding to this injection point.
+     */
+    MetaElement<T> getElement();
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaClass.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaClass.java
new file mode 100644
index 0000000..48159bf
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaClass.java
@@ -0,0 +1,42 @@
+/*
+ * 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.spi.model;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Optional;
+
+public interface MetaClass<T> extends MetaElement<T> {
+    Class<T> getJavaClass();
+
+    Collection<MetaConstructor<T>> getConstructors();
+
+    MetaConstructor<T> getMetaConstructor(final Constructor<T> constructor);
+
+    Optional<MetaConstructor<T>> getDefaultConstructor();
+
+    Collection<MetaMethod<T, ?>> getMethods();
+
+    <U> MetaMethod<T, U> getMetaMethod(final Method method);
+
+    Collection<MetaField<T, ?>> getFields();
+
+    <U> MetaField<T, U> getMetaField(final Field field);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaConstructor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaConstructor.java
new file mode 100644
index 0000000..766d353
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaConstructor.java
@@ -0,0 +1,22 @@
+/*
+ * 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.spi.model;
+
+public interface MetaConstructor<T> extends MetaExecutable<T, T> {
+    T construct(final Object... args);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaElement.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaElement.java
new file mode 100644
index 0000000..1dc9b01
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaElement.java
@@ -0,0 +1,61 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.api.AliasFor;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+public interface MetaElement<T> {
+
+    /**
+     * Returns the source code name of this element.
+     */
+    String getName();
+
+    /**
+     * Returns all the annotations present on this element.
+     */
+    Collection<Annotation> getAnnotations();
+
+    /**
+     * Indicates whether or not an annotation is present on this element taking into account
+     * {@linkplain AliasFor annotation aliasing}.
+     *
+     * @param annotationType type of annotation to look for
+     * @return whether or not the annotation is directly or indirectly present on this element
+     */
+    default boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+        for (final Annotation annotation : getAnnotations()) {
+            if (annotationType.equals(annotation.annotationType())) {
+                return true;
+            }
+            if (annotation instanceof AliasFor) {
+                return annotationType.equals(((AliasFor) annotation).value());
+            }
+        }
+        return false;
+    }
+
+    Type getBaseType();
+
+    Collection<Type> getTypeClosure();
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaExecutable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaExecutable.java
new file mode 100644
index 0000000..b2ae5e1
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaExecutable.java
@@ -0,0 +1,24 @@
+/*
+ * 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.spi.model;
+
+import java.util.List;
+
+public interface MetaExecutable<D, T> extends MetaMember<D, T> {
+    List<MetaParameter<?>> getParameters();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaField.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaField.java
new file mode 100644
index 0000000..9767eba
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaField.java
@@ -0,0 +1,24 @@
+/*
+ * 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.spi.model;
+
+public interface MetaField<D, T> extends MetaMember<D, T> {
+    T get(final D target);
+
+    void set(final D target, final T value);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMember.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMember.java
new file mode 100644
index 0000000..21601fd
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMember.java
@@ -0,0 +1,26 @@
+/*
+ * 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.spi.model;
+
+public interface MetaMember<D, T> extends MetaElement<T> {
+    MetaClass<T> getType();
+
+    MetaClass<D> getDeclaringClass();
+
+    boolean isStatic();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMethod.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMethod.java
new file mode 100644
index 0000000..80989b8
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMethod.java
@@ -0,0 +1,22 @@
+/*
+ * 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.spi.model;
+
+public interface MetaMethod<D, T> extends MetaExecutable<D, T> {
+    T invoke(final D target, final Object... args);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaParameter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaParameter.java
new file mode 100644
index 0000000..cac9b58
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaParameter.java
@@ -0,0 +1,21 @@
+/*
+ * 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.spi.model;
+
+public interface MetaParameter<T> extends MetaElement<T> {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java
new file mode 100644
index 0000000..fba378c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java
@@ -0,0 +1,103 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.api.AliasFor;
+import org.apache.logging.log4j.plugins.api.Default;
+import org.apache.logging.log4j.plugins.api.Ignore;
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+// TODO: consider a composite class for qualifier collections or other potential ways to model this
+public final class Qualifier {
+
+    public static final Qualifier DEFAULT_QUALIFIER = new Qualifier(Default.class, Collections.emptyMap());
+
+    public static Qualifier fromAnnotation(final Annotation annotation) {
+        final Class<? extends Annotation> annotationType = annotation.annotationType();
+        final Method[] elements = annotationType.getDeclaredMethods();
+        final Map<String, Object> attributes = new HashMap<>(elements.length);
+        for (final Method element : elements) {
+            if (!element.isAnnotationPresent(Ignore.class)) {
+                attributes.put(element.getName(), getAnnotationElement(annotation, element));
+            }
+        }
+        // FIXME: support default name for @Named when value is blank
+        final AliasFor alias = annotationType.getAnnotation(AliasFor.class);
+        final Class<? extends Annotation> qualifierType = alias != null ? alias.value() : annotationType;
+        return new Qualifier(qualifierType, Collections.unmodifiableMap(attributes));
+    }
+
+    private static Object getAnnotationElement(final Annotation annotation, final Method element) {
+        try {
+            return element.invoke(annotation);
+        } catch (final IllegalAccessException e) {
+            throw new InjectionException("Cannot access element " + element.getName() + " of annotation " + annotation, e);
+        } catch (final InvocationTargetException e) {
+            throw new InjectionException("Cannot access element " + element.getName() + " of annotation " + annotation,
+                    e.getCause());
+        }
+    }
+
+    private final Class<? extends Annotation> qualifierType;
+    private final Map<String, Object> attributes;
+
+    private Qualifier(final Class<? extends Annotation> qualifierType, final Map<String, Object> attributes) {
+        this.qualifierType = qualifierType;
+        this.attributes = attributes;
+    }
+
+    public Class<? extends Annotation> getQualifierType() {
+        return qualifierType;
+    }
+
+    public Map<String, Object> getAttributes() {
+        return attributes;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final Qualifier that = (Qualifier) o;
+        return qualifierType.equals(that.qualifierType) &&
+                attributes.equals(that.attributes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(qualifierType, attributes);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append('@').append(qualifierType.getSimpleName());
+        if (!attributes.isEmpty()) {
+            sb.append(attributes);
+        }
+        return sb.toString();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
new file mode 100644
index 0000000..fa7e02e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
@@ -0,0 +1,41 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+public interface Variable<T> {
+    Collection<Type> getTypes();
+
+    default boolean hasMatchingType(final Type requiredType) {
+        for (final Type type : getTypes()) {
+            if (TypeUtil.typesMatch(requiredType, type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    Collection<Qualifier> getQualifiers();
+
+    Class<? extends Annotation> getScopeType();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/InitializationContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/InitializationContext.java
new file mode 100644
index 0000000..5bf2f4a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/InitializationContext.java
@@ -0,0 +1,49 @@
+/*
+ * 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.spi.scope;
+
+import java.util.Optional;
+
+/**
+ * Provides operations used by {@link Scoped} implementations for tracking lifecycle state.
+ */
+public interface InitializationContext<T> extends AutoCloseable {
+    /**
+     * Pushes an incomplete instance state. An instance is not completely initialized until it is returned from
+     * {@link Scoped#create(InitializationContext)}.
+     *
+     * @param incompleteInstance incompletely initialized instance
+     */
+    void push(final T incompleteInstance);
+
+    <S> Optional<S> getIncompleteInstance(Scoped<S> scoped);
+
+    Optional<Scoped<T>> getScoped();
+
+    Optional<InitializationContext<?>> getParentContext();
+
+    <S> InitializationContext<S> createDependentContext(Scoped<S> scoped);
+
+    <S> InitializationContext<S> createIndependentContext(Scoped<S> scoped);
+
+    /**
+     * Destroys all dependent objects by propagating them to {@link Scoped#destroy(Object, InitializationContext)}.
+     */
+    @Override
+    void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/ScopeContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/ScopeContext.java
new file mode 100644
index 0000000..9617a7d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/ScopeContext.java
@@ -0,0 +1,63 @@
+/*
+ * 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.spi.scope;
+
+import org.apache.logging.log4j.plugins.api.ScopeType;
+
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+
+/**
+ * Manages {@link Scoped} instances within a particular {@linkplain ScopeType scope}.
+ */
+public interface ScopeContext extends AutoCloseable {
+
+    /**
+     * Returns the {@linkplain ScopeType scope type} of this context.
+     */
+    Class<? extends Annotation> getScopeType();
+
+    /**
+     * Gets or {@linkplain Scoped#create(InitializationContext) creates} a scoped instance of a specific type.
+     *
+     * @param scoped the managed type
+     * @param context the context to create a new instance in
+     * @param <T>     the instance type being managed
+     * @return the new or existing instance
+     */
+    <T> T getOrCreate(final Scoped<T> scoped, final InitializationContext<T> context);
+
+    /**
+     * Returns an existing scoped instance if it exists.
+     *
+     * @param scoped the managed type
+     * @param <T>     the instance type being managed
+     * @return the existing instance or empty
+     */
+    <T> Optional<T> getIfExists(final Scoped<T> scoped);
+
+    /**
+     * Destroys the existing scoped instance of a specified type if it exists or otherwise does nothing.
+     *
+     * @param scoped the managed type
+     */
+    void destroy(final Scoped<?> scoped);
+
+    @Override
+    void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/Scoped.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/Scoped.java
new file mode 100644
index 0000000..d214662
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/Scoped.java
@@ -0,0 +1,58 @@
+/*
+ * 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.spi.scope;
+
+import org.apache.logging.log4j.plugins.api.PrototypeScoped;
+import org.apache.logging.log4j.plugins.api.SingletonScoped;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Manages the creation and destruction of instances of a specific type within a particular scope context. Scopes
+ * provide managed life-cycles for objects such as {@linkplain SingletonScoped singleton}
+ * and {@linkplain PrototypeScoped prototype}.
+ *
+ * @param <T> type of managed instance
+ * @see org.apache.logging.log4j.plugins.api.ScopeType
+ */
+public interface Scoped<T> {
+
+    /**
+     * Creates a new instance of this managed type. The given {@link InitializationContext} should be used by implementations
+     * to track dependent objects.
+     *
+     * @param context the context in which the instance is being managed
+     * @return a managed, initialized instance
+     */
+    T create(final InitializationContext<T> context);
+
+    /**
+     * Destroys a managed instance in the given context. Implementations should call {@link InitializationContext#close()} to
+     * allow dependent objects to be destroyed.
+     *
+     * @param instance the managed instance to destroy
+     * @param context  the context in which the instance is being managed
+     */
+    void destroy(final T instance, final InitializationContext<T> context);
+
+    Class<? extends Annotation> getScopeType();
+
+    default boolean isPrototypeScoped() {
+        return getScopeType() == PrototypeScoped.class;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
index 6adf661..f27a86f 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
@@ -16,10 +16,22 @@
  */
 package org.apache.logging.log4j.plugins.util;
 
-import java.lang.reflect.*;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -36,13 +48,13 @@ import java.util.Objects;
  * @since 2.1
  */
 public final class TypeUtil {
-    
+
     private TypeUtil() {
     }
 
     /**
      * Gets all declared fields for the given class (including superclasses).
-     * 
+     *
      * @param cls the class to examine
      * @return all declared fields for the given class (including superclasses).
      * @see Class#getDeclaredFields()
@@ -50,11 +62,14 @@ public final class TypeUtil {
     public static List<Field> getAllDeclaredFields(Class<?> cls) {
         final List<Field> fields = new ArrayList<>();
         while (cls != null) {
-            fields.addAll(Arrays.asList(cls.getDeclaredFields()));
+            final Field[] declaredFields = cls.getDeclaredFields();
+            AccessibleObject.setAccessible(declaredFields, true);
+            fields.addAll(Arrays.asList(declaredFields));
             cls = cls.getSuperclass();
         }
         return fields;
     }
+
     /**
      * Indicates if two {@link Type}s are assignment compatible.
      *
@@ -79,31 +94,40 @@ public final class TypeUtil {
             if (rhs instanceof Class<?>) {
                 // no generics involved
                 final Class<?> rhsClass = (Class<?>) rhs;
-                return lhsClass.isAssignableFrom(rhsClass);
+                // possible for primitive types here
+                return isAssignable(lhsClass, rhsClass);
             }
             if (rhs instanceof ParameterizedType) {
-                // check to see if the parameterized type has the same raw type as the lhs; this is legal
-                final Type rhsRawType = ((ParameterizedType) rhs).getRawType();
-                if (rhsRawType instanceof Class<?>) {
-                    return lhsClass.isAssignableFrom((Class<?>) rhsRawType);
-                }
-            }
-            if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
+                return isAssignable(lhsClass, getRawType(rhs));
+            } else if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
                 // check for compatible array component types
                 return isAssignable(lhsClass.getComponentType(), ((GenericArrayType) rhs).getGenericComponentType());
+            } else if (rhs instanceof TypeVariable<?>) {
+                for (final Type bound : ((TypeVariable<?>) rhs).getBounds()) {
+                    if (isAssignable(lhs, bound)) {
+                        return true;
+                    }
+                }
+            } else {
+                return false;
             }
         }
         // parameterized type on left
         if (lhs instanceof ParameterizedType) {
             final ParameterizedType lhsType = (ParameterizedType) lhs;
             if (rhs instanceof Class<?>) {
-                final Type lhsRawType = lhsType.getRawType();
-                if (lhsRawType instanceof Class<?>) {
-                    return ((Class<?>) lhsRawType).isAssignableFrom((Class<?>) rhs);
-                }
+                return isAssignable(getRawType(lhs), (Class<?>) rhs);
             } else if (rhs instanceof ParameterizedType) {
                 final ParameterizedType rhsType = (ParameterizedType) rhs;
                 return isParameterizedAssignable(lhsType, rhsType);
+            } else if (rhs instanceof TypeVariable<?>) {
+                for (final Type bound : ((TypeVariable<?>) rhs).getBounds()) {
+                    if (isAssignable(lhsType, bound)) {
+                        return true;
+                    }
+                }
+            } else {
+                return false;
             }
         }
         // generic array type on left
@@ -127,6 +151,10 @@ public final class TypeUtil {
         return false;
     }
 
+    private static boolean isAssignable(final Class<?> lhs, final Class<?> rhs) {
+        return getReferenceType(lhs).isAssignableFrom(getReferenceType(rhs));
+    }
+
     private static boolean isParameterizedAssignable(final ParameterizedType lhs, final ParameterizedType rhs) {
         if (lhs.equals(rhs)) {
             // that was easy
@@ -144,8 +172,8 @@ public final class TypeUtil {
             final Type lhsArgument = lhsTypeArguments[i];
             final Type rhsArgument = rhsTypeArguments[i];
             if (!lhsArgument.equals(rhsArgument) &&
-                !(lhsArgument instanceof WildcardType &&
-                    isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
+                    !(lhsArgument instanceof WildcardType &&
+                            isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
                 return false;
             }
         }
@@ -213,4 +241,299 @@ public final class TypeUtil {
     private static boolean isBoundAssignable(final Type lhs, final Type rhs) {
         return (rhs == null) || ((lhs != null) && isAssignable(lhs, rhs));
     }
+
+    /**
+     * Checks if a type is a raw type.
+     */
+    public static boolean isRawType(final Type type) {
+        if (type instanceof Class<?>) {
+            final Class<?> clazz = (Class<?>) type;
+            if (clazz.isArray()) {
+                return isRawType(clazz.getComponentType());
+            }
+            return clazz.getTypeParameters().length > 0;
+        }
+        return false;
+    }
+
+    /**
+     * Extracts the raw type equivalent of a given type.
+     */
+    public static Class<?> getRawType(final Type type) {
+        if (type instanceof Class<?>) {
+            return (Class<?>) type;
+        }
+        if (type instanceof ParameterizedType) {
+            return getRawType(((ParameterizedType) type).getRawType());
+        }
+        if (type instanceof GenericArrayType) {
+            return Array.newInstance(getRawType(((GenericArrayType) type).getGenericComponentType()), 0).getClass();
+        }
+        if (type instanceof WildcardType) {
+            final Type[] bounds = ((WildcardType) type).getUpperBounds();
+            return bounds.length > 0 ? getRawType(bounds[0]) : Object.class;
+        }
+        if (type instanceof TypeVariable<?>) {
+            final Type[] bounds = ((TypeVariable<?>) type).getBounds();
+            return bounds.length > 0 ? getRawType(bounds[0]) : Object.class;
+        }
+        return Object.class;
+    }
+
+    /**
+     * Checks if a type matches another type.
+     */
+    public static boolean typesMatch(final Type required, final Type found) {
+        if (required instanceof Class<?>) {
+            if (found instanceof Class<?>) {
+                return required.equals(found);
+            }
+            if (found instanceof ParameterizedType) {
+                return required.equals(getRawType(found));
+            }
+        }
+        if (required instanceof ParameterizedType) {
+            if (found instanceof Class<?>) {
+                return getRawType(required).equals(found);
+            }
+            if (found instanceof ParameterizedType) {
+                if (!getRawType(required).equals(getRawType(found))) {
+                    return false;
+                }
+                final Type[] requiredArguments = ((ParameterizedType) required).getActualTypeArguments();
+                final Type[] foundArguments = ((ParameterizedType) found).getActualTypeArguments();
+                if (requiredArguments.length != foundArguments.length) {
+                    return false;
+                }
+                for (int i = 0; i < requiredArguments.length; i++) {
+                    if (!typeParametersMatch(requiredArguments[i], foundArguments[i])) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean typeParametersMatch(final Type required, final Type found) {
+        if (required instanceof Class<?> || required instanceof ParameterizedType || required instanceof GenericArrayType) {
+            if (found instanceof Class<?> || found instanceof ParameterizedType || found instanceof GenericArrayType) {
+                return typesMatch(getReferenceType(required), getReferenceType(found));
+            }
+            if (found instanceof TypeVariable<?>) {
+                return typeParametersMatch(required, (TypeVariable<?>) found);
+            }
+        }
+        if (required instanceof WildcardType) {
+            final WildcardType wildcardType = (WildcardType) required;
+            if (found instanceof Class<?> || found instanceof ParameterizedType || found instanceof GenericArrayType) {
+                return typeParametersMatch(wildcardType, found);
+            }
+            if (found instanceof TypeVariable<?>) {
+                return typeParametersMatch(wildcardType, (TypeVariable<?>) found);
+            }
+        }
+        if (required instanceof TypeVariable<?>) {
+            if (found instanceof TypeVariable<?>) {
+                final Type[] foundBounds = getTopBounds(((TypeVariable<?>) found).getBounds());
+                final Type[] requiredBounds = getTopBounds(((TypeVariable<?>) required).getBounds());
+                return areBoundsStricter(foundBounds, requiredBounds);
+            }
+        }
+        return false;
+    }
+
+    private static boolean typeParametersMatch(final Type required, final TypeVariable<?> found) {
+        for (final Type bound : getTopBounds(found.getBounds())) {
+            if (!isAssignable(bound, required)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean typeParametersMatch(final WildcardType required, final Type found) {
+        return lowerBoundsOfWildcardMatch(required, found) && upperBoundsOfWildcardMatch(required, found);
+    }
+
+    private static boolean typeParametersMatch(final WildcardType required, final TypeVariable<?> found) {
+        final Type[] bounds = getTopBounds(found.getBounds());
+        if (!lowerBoundsOfWildcardMatch(required, bounds)) {
+            return false;
+        }
+        final Type[] upperBounds = required.getUpperBounds();
+        return areBoundsStricter(bounds, upperBounds) || areBoundsStricter(upperBounds, bounds);
+    }
+
+    private static boolean lowerBoundsOfWildcardMatch(final WildcardType required, final Type... found) {
+        final Type[] lowerBounds = required.getLowerBounds();
+        return lowerBounds.length == 0 || areBoundsStricter(found, lowerBounds);
+    }
+
+    private static boolean upperBoundsOfWildcardMatch(final WildcardType required, final Type... found) {
+        return areBoundsStricter(required.getUpperBounds(), found);
+    }
+
+    private static boolean areBoundsStricter(final Type[] upperBounds, final Type[] stricterUpperBounds) {
+        final Type[] stricterBounds = getTopBounds(stricterUpperBounds);
+        for (final Type upperBound : getTopBounds(upperBounds)) {
+            if (!isAssignableFromOneOf(upperBound, stricterBounds)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static Type[] getTopBounds(final Type[] bounds) {
+        return bounds[0] instanceof TypeVariable<?> ? getTopBounds(((TypeVariable<?>) bounds[0]).getBounds()) : bounds;
+    }
+
+    private static boolean isAssignableFromOneOf(final Type type, final Type... types) {
+        for (final Type t : types) {
+            if (isAssignable(type, t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the type closure of a generic type.
+     */
+    public static Collection<Type> getTypeClosure(final Type type) {
+        // TODO: weak cache?
+        return new TypeResolver(type).types.values();
+    }
+
+    private static class TypeResolver {
+        private final Map<TypeVariable<?>, Type> resolvedTypeVariables = new HashMap<>();
+        private final Map<Class<?>, Type> types = new LinkedHashMap<>();
+
+        private TypeResolver(final Type type) {
+            loadTypes(type);
+        }
+
+        private void loadTypes(final Type type) {
+            if (type instanceof Class<?>) {
+                final Class<?> clazz = (Class<?>) type;
+                types.put(clazz, clazz);
+                loadTypes(clazz);
+            } else if (isRawType(type)) {
+                loadTypes(getRawType(type));
+            } else if (type instanceof GenericArrayType) {
+                final GenericArrayType arrayType = (GenericArrayType) type;
+                final Type componentType = arrayType.getGenericComponentType();
+                final Class<?> rawComponentType = getRawType(componentType);
+                final Class<?> arrayClass = Array.newInstance(rawComponentType, 0).getClass();
+                types.put(arrayClass, arrayType);
+                loadTypes(arrayClass);
+            } else if (type instanceof ParameterizedType) {
+                final ParameterizedType parameterizedType = (ParameterizedType) type;
+                final Type rawType = parameterizedType.getRawType();
+                if (rawType instanceof Class<?>) {
+                    final Class<?> clazz = (Class<?>) rawType;
+                    processTypeVariables(clazz.getTypeParameters(), parameterizedType.getActualTypeArguments());
+                    types.put(clazz, parameterizedType);
+                    loadTypes(clazz);
+                }
+            }
+        }
+
+        private void loadTypes(final Class<?> clazz) {
+            if (clazz.getSuperclass() != null) {
+                loadTypes(processAndResolveType(clazz.getGenericSuperclass(), clazz.getSuperclass()));
+            }
+            final Type[] genericInterfaces = clazz.getGenericInterfaces();
+            final Class<?>[] interfaces = clazz.getInterfaces();
+            for (int i = 0; i < interfaces.length; i++) {
+                loadTypes(processAndResolveType(genericInterfaces[i], interfaces[i]));
+            }
+        }
+
+        private Type processAndResolveType(final Type superclass, final Class<?> rawSuperclass) {
+            if (superclass instanceof ParameterizedType) {
+                final ParameterizedType parameterizedSuperclass = (ParameterizedType) superclass;
+                processTypeVariables(rawSuperclass.getTypeParameters(), parameterizedSuperclass.getActualTypeArguments());
+                return resolveType(parameterizedSuperclass);
+            }
+            if (superclass instanceof Class<?>) {
+                return superclass;
+            }
+            throw new IllegalArgumentException("Superclass argument must be parameterized or a class, but got: " + superclass);
+        }
+
+        private void processTypeVariables(final TypeVariable<?>[] variables, final Type[] types) {
+            for (int i = 0; i < variables.length; i++) {
+                final Type type = types[i];
+                final Type resolvedType = type instanceof TypeVariable<?> ? resolveType((TypeVariable<?>) type) : type;
+                resolvedTypeVariables.put(variables[i], resolvedType);
+            }
+        }
+
+        private Type resolveType(final TypeVariable<?> type) {
+            return resolvedTypeVariables.getOrDefault(type, type);
+        }
+
+        private Type resolveType(final ParameterizedType type) {
+            final Type[] unresolved = type.getActualTypeArguments();
+            final Type[] resolved = new Type[unresolved.length];
+            boolean modified = false; // potentially no need to re-create ParameterizedType
+            for (int i = 0; i < unresolved.length; i++) {
+                final Type unresolvedType = unresolved[i];
+                Type resolvedType = unresolvedType;
+                if (resolvedType instanceof TypeVariable<?>) {
+                    resolvedType = resolveType((TypeVariable<?>) resolvedType);
+                } // else if?
+                if (resolvedType instanceof ParameterizedType) {
+                    resolvedType = resolveType((ParameterizedType) resolvedType);
+                }
+                resolved[i] = resolvedType;
+                if (resolvedType != unresolvedType) {
+                    modified = true;
+                }
+            }
+            if (!modified) {
+                return type;
+            }
+            return new ParameterizedTypeImpl(type.getOwnerType(), type.getRawType(), resolved);
+        }
+
+    }
+
+    private static final Map<Class<?>, Class<?>> PRIMITIVE_BOXED_TYPES;
+
+    static {
+        final Map<Class<?>, Class<?>> map = new HashMap<>();
+        map.put(boolean.class, Boolean.class);
+        map.put(byte.class, Byte.class);
+        map.put(char.class, Character.class);
+        map.put(double.class, Double.class);
+        map.put(float.class, Float.class);
+        map.put(int.class, Integer.class);
+        map.put(long.class, Long.class);
+        map.put(short.class, Short.class);
+        PRIMITIVE_BOXED_TYPES = Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * Returns the reference type for a class. For primitives, this is their boxed equivalent. For other types, this is
+     * the class unchanged.
+     */
+    public static Class<?> getReferenceType(final Class<?> clazz) {
+        if (clazz.isPrimitive()) {
+            return PRIMITIVE_BOXED_TYPES.get(clazz);
+        }
+        return clazz;
+    }
+
+    private static Type getReferenceType(final Type type) {
+        return type instanceof Class<?> ? getReferenceType((Class<?>) type) : type;
+    }
+
+    public static <T> T cast(final Object o) {
+        @SuppressWarnings("unchecked") final T t = (T) o;
+        return t;
+    }
+
 }