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:05 UTC

[logging-log4j2] 03/03: Add v3 dependency injection default implementation and tests

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 379234dfa288f3215ab5d52aaa17dc85c0eed419
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sun Feb 23 17:47:28 2020 -0600

    Add v3 dependency injection default implementation and tests
    
    Signed-off-by: Matt Sicker <bo...@gmail.com>
---
 .../log4j/plugins/defaults/bean/AbstractBean.java  |  72 ++++
 .../plugins/defaults/bean/AbstractProducer.java    |  71 ++++
 .../defaults/bean/AbstractProducerFactory.java     |  42 ++
 .../defaults/bean/ConstructorInjectionContext.java |  41 ++
 .../plugins/defaults/bean/DefaultBeanManager.java  | 462 +++++++++++++++++++++
 .../bean/DefaultInjectionContextFactory.java       |  69 +++
 .../defaults/bean/DefaultInjectionTarget.java      | 135 ++++++
 .../bean/DefaultInjectionTargetFactory.java        | 110 +++++
 .../defaults/bean/ExecutableInjectionContext.java  |  59 +++
 .../defaults/bean/FieldInjectionContext.java       |  46 ++
 .../log4j/plugins/defaults/bean/FieldProducer.java |  57 +++
 .../defaults/bean/FieldProducerFactory.java        |  45 ++
 .../plugins/defaults/bean/InjectionContext.java    |  46 ++
 .../plugins/defaults/bean/InjectionTargetBean.java |  80 ++++
 .../defaults/bean/MethodInjectionContext.java      |  52 +++
 .../plugins/defaults/bean/MethodProducer.java      |  64 +++
 .../defaults/bean/MethodProducerFactory.java       |  47 +++
 .../log4j/plugins/defaults/bean/OptionalBean.java  |  38 ++
 .../log4j/plugins/defaults/bean/ProducerBean.java  |  88 ++++
 .../log4j/plugins/defaults/bean/ProvidedBean.java  |  37 ++
 .../log4j/plugins/defaults/bean/ProviderBean.java  |  38 ++
 .../log4j/plugins/defaults/bean/SystemBean.java    |  68 +++
 .../defaults/model/AbstractMetaExecutable.java     |  44 ++
 .../plugins/defaults/model/AbstractMetaMember.java |  80 ++++
 .../defaults/model/DefaultElementManager.java      | 203 +++++++++
 .../defaults/model/DefaultInjectionPoint.java      |  99 +++++
 .../plugins/defaults/model/DefaultMetaClass.java   | 143 +++++++
 .../defaults/model/DefaultMetaConstructor.java     |  50 +++
 .../plugins/defaults/model/DefaultMetaField.java   |  57 +++
 .../plugins/defaults/model/DefaultMetaMethod.java  |  51 +++
 .../defaults/model/DefaultMetaParameter.java       |  66 +++
 .../plugins/defaults/model/DefaultVariable.java    |  84 ++++
 .../scope/DefaultInitializationContext.java        | 106 +++++
 .../defaults/scope/DefaultScopeContext.java        |  70 ++++
 .../defaults/scope/DefaultScopedInstance.java      |  50 +++
 .../plugins/defaults/scope/LazyScopedInstance.java |  57 +++
 .../defaults/scope/PrototypeScopeContext.java      |  55 +++
 .../plugins/defaults/scope/ScopedInstance.java     |  29 ++
 .../apache/logging/log4j/plugins/util/Cache.java   |  25 ++
 .../logging/log4j/plugins/util/LazyValue.java      |  48 +++
 .../log4j/plugins/util/ParameterizedTypeImpl.java  |  67 +++
 .../apache/logging/log4j/plugins/util/Value.java   |  22 +
 .../logging/log4j/plugins/util/WeakCache.java      |  52 +++
 .../logging/log4j/plugins/util/WeakLazyValue.java  |  50 +++
 .../defaults/bean/DefaultBeanManagerTest.java      | 278 +++++++++++++
 .../log4j/plugins/test/BeanJUnit4Runner.java       | 144 +++++++
 .../logging/log4j/plugins/test/WithBeans.java      |  36 ++
 47 files changed, 3733 insertions(+)

diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java
new file mode 100644
index 0000000..aeaf77b
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java
@@ -0,0 +1,72 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+
+abstract class AbstractBean<D, T> implements Bean<T> {
+    private final Variable<T> variable;
+    private final MetaClass<D> declaringClass;
+
+    AbstractBean(final Variable<T> variable, final MetaClass<D> declaringClass) {
+        this.variable = Objects.requireNonNull(variable);
+        this.declaringClass = declaringClass;
+    }
+
+    @Override
+    public MetaClass<D> getDeclaringClass() {
+        return declaringClass;
+    }
+
+    @Override
+    public Collection<Qualifier> getQualifiers() {
+        return variable.getQualifiers();
+    }
+
+    @Override
+    public Collection<Type> getTypes() {
+        return variable.getTypes();
+    }
+
+    @Override
+    public Class<? extends Annotation> getScopeType() {
+        return variable.getScopeType();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final AbstractBean<?, ?> that = (AbstractBean<?, ?>) o;
+        return variable.equals(that.variable) &&
+                Objects.equals(declaringClass, that.declaringClass);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(variable, declaringClass);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractProducer.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractProducer.java
new file mode 100644
index 0000000..1ad81b5
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractProducer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.bean.Producer;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+
+abstract class AbstractProducer<D, T> implements Producer<T> {
+    private final BeanManager beanManager;
+
+    private final Bean<D> declaringBean;
+    private final MetaMethod<D, ?> disposerMethod;
+    private final Collection<InjectionPoint<?>> disposerInjectionPoints;
+    final InjectionContext.Factory injectionContextFactory;
+
+    AbstractProducer(final BeanManager beanManager, final Bean<D> declaringBean, final MetaMethod<D, ?> disposerMethod,
+                     final Collection<InjectionPoint<?>> disposerInjectionPoints) {
+        this.beanManager = beanManager;
+        this.declaringBean = declaringBean;
+        this.disposerMethod = disposerMethod;
+        this.disposerInjectionPoints = Objects.requireNonNull(disposerInjectionPoints);
+        this.injectionContextFactory = new DefaultInjectionContextFactory(beanManager);
+    }
+
+    // context is managed separately as the declaring instance is only used for producing the object and is not a dependent of the bean
+    InitializationContext<D> createContext() {
+        return beanManager.createInitializationContext(declaringBean);
+    }
+
+    D getDeclaringInstance(final InitializationContext<D> context) {
+        return context.getIncompleteInstance(declaringBean).orElseGet(() ->
+                beanManager.getValue(declaringBean, context.createIndependentContext(declaringBean)));
+    }
+
+    abstract Type getType();
+
+    @Override
+    public void dispose(final T instance) {
+        if (disposerMethod != null) {
+            // as producer and disposer bean is unrelated to this bean, we need to recreate it on demand
+            try (final InitializationContext<D> context = createContext()) {
+                final D declaringInstance = disposerMethod.isStatic() ? null : getDeclaringInstance(context);
+                injectionContextFactory.forDisposerMethod(disposerMethod, disposerInjectionPoints, declaringInstance, instance)
+                        .invoke(context);
+            }
+        }
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractProducerFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractProducerFactory.java
new file mode 100644
index 0000000..a73840f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractProducerFactory.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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.ProducerFactory;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaMember;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+
+import java.util.Collection;
+import java.util.Objects;
+
+abstract class AbstractProducerFactory<D> implements ProducerFactory<D> {
+    final Bean<D> declaringBean;
+    final MetaMember<D, ?> producerMember;
+    final MetaMethod<D, ?> disposerMethod;
+    final Collection<InjectionPoint<?>> disposerInjectionPoints;
+
+    AbstractProducerFactory(final Bean<D> declaringBean, final MetaMember<D, ?> producerMember,
+                            final MetaMethod<D, ?> disposerMethod, final Collection<InjectionPoint<?>> disposerInjectionPoints) {
+        this.declaringBean = declaringBean;
+        this.producerMember = Objects.requireNonNull(producerMember);
+        this.disposerMethod = disposerMethod;
+        this.disposerInjectionPoints = Objects.requireNonNull(disposerInjectionPoints);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ConstructorInjectionContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ConstructorInjectionContext.java
new file mode 100644
index 0000000..f7e8443
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ConstructorInjectionContext.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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaConstructor;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.Collection;
+
+class ConstructorInjectionContext<T> extends ExecutableInjectionContext<T> {
+    private final MetaConstructor<T> constructor;
+
+    ConstructorInjectionContext(final BeanManager beanManager,
+                                final Collection<InjectionPoint<?>> injectionPoints,
+                                final MetaConstructor<T> constructor) {
+        super(beanManager, injectionPoints, constructor.getParameters());
+        this.constructor = constructor;
+    }
+
+    @Override
+    public T invoke(final InitializationContext<?> context) {
+        return constructor.construct(createArguments(context));
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java
new file mode 100644
index 0000000..f431939
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java
@@ -0,0 +1,462 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.api.Disposes;
+import org.apache.logging.log4j.plugins.api.Produces;
+import org.apache.logging.log4j.plugins.api.PrototypeScoped;
+import org.apache.logging.log4j.plugins.api.Provider;
+import org.apache.logging.log4j.plugins.api.SingletonScoped;
+import org.apache.logging.log4j.plugins.defaults.model.DefaultElementManager;
+import org.apache.logging.log4j.plugins.defaults.model.DefaultVariable;
+import org.apache.logging.log4j.plugins.defaults.scope.DefaultInitializationContext;
+import org.apache.logging.log4j.plugins.defaults.scope.DefaultScopeContext;
+import org.apache.logging.log4j.plugins.defaults.scope.PrototypeScopeContext;
+import org.apache.logging.log4j.plugins.spi.AmbiguousBeanException;
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+import org.apache.logging.log4j.plugins.spi.ResolutionException;
+import org.apache.logging.log4j.plugins.spi.UnsatisfiedBeanException;
+import org.apache.logging.log4j.plugins.spi.ValidationException;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.bean.InjectionTargetFactory;
+import org.apache.logging.log4j.plugins.spi.bean.ProducerFactory;
+import org.apache.logging.log4j.plugins.spi.model.ElementManager;
+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.MetaElement;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMember;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.ScopeContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+import org.apache.logging.log4j.plugins.util.ParameterizedTypeImpl;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class DefaultBeanManager implements BeanManager {
+
+    private final ElementManager elementManager;
+    private final InjectionContext.Factory injectionContextFactory = new DefaultInjectionContextFactory(this);
+
+    private final Collection<Bean<?>> enabledBeans = ConcurrentHashMap.newKeySet();
+    private final Map<Type, Collection<Bean<?>>> beansByType = new ConcurrentHashMap<>();
+    private final Collection<Bean<?>> sharedBeans = ConcurrentHashMap.newKeySet();
+    private final Collection<DisposesMethod<?>> disposesMethods = Collections.synchronizedCollection(new ArrayList<>());
+    private final Map<Class<? extends Annotation>, ScopeContext> scopes = new ConcurrentHashMap<>();
+
+    public DefaultBeanManager() {
+        this(new DefaultElementManager());
+    }
+
+    public DefaultBeanManager(final ElementManager elementManager) {
+        this.elementManager = elementManager;
+        scopes.put(PrototypeScoped.class, new PrototypeScopeContext());
+        scopes.put(SingletonScoped.class, new DefaultScopeContext(SingletonScoped.class));
+    }
+
+    @Override
+    public Collection<Bean<?>> loadBeans(final Collection<Class<?>> beanClasses) {
+        final Collection<Bean<?>> beans = beanClasses.stream()
+                .map(elementManager::getMetaClass)
+                .flatMap(metaClass -> loadBeans(metaClass).stream())
+                .collect(Collectors.toSet());
+        validateBeans(beans);
+        return beans;
+    }
+
+    private <T> Collection<Bean<?>> loadBeans(final MetaClass<T> metaClass) {
+        final Bean<T> created;
+        if (elementManager.isInjectable(metaClass)) {
+            final Variable<T> variable = elementManager.createVariable(metaClass);
+            final InjectionTargetFactory<T> factory =
+                    new DefaultInjectionTargetFactory<>(elementManager, injectionContextFactory, metaClass);
+            created = addBean(new InjectionTargetBean<>(variable, metaClass, factory));
+        } else {
+            created = null;
+        }
+        loadDisposerMethods(metaClass, created);
+        final Collection<Bean<?>> beans = loadProducerBeans(metaClass, created);
+        if (created != null) {
+            beans.add(created);
+        }
+        return beans;
+    }
+
+    private <T> Bean<T> addBean(final Bean<T> bean) {
+        if (enabledBeans.add(bean)) {
+            addBeanTypes(bean);
+            if (!bean.isPrototypeScoped() && !isSystemBean(bean)) {
+                sharedBeans.add(bean);
+            }
+        }
+        return bean;
+    }
+
+    private void addBeanTypes(final Bean<?> bean) {
+        for (final Type type : bean.getTypes()) {
+            addBeanType(bean, type);
+            if (type instanceof ParameterizedType) {
+                final Type rawType = ((ParameterizedType) type).getRawType();
+                addBeanType(bean, rawType);
+            } else if (type instanceof Class<?>) {
+                final Class<?> clazz = (Class<?>) type;
+                if (clazz.isPrimitive()) {
+                    addBeanType(bean, TypeUtil.getReferenceType(clazz));
+                }
+            }
+        }
+    }
+
+    private void addBeanType(final Bean<?> bean, final Type type) {
+        beansByType.computeIfAbsent(type, ignored -> new HashSet<>()).add(bean);
+    }
+
+    private boolean isSystemBean(final Bean<?> bean) {
+        return bean instanceof SystemBean<?>;
+    }
+
+    private <T> void loadDisposerMethods(final MetaClass<T> metaClass, final Bean<T> bean) {
+        for (final MetaMethod<T, ?> method : metaClass.getMethods()) {
+            for (final MetaParameter<?> parameter : method.getParameters()) {
+                if (parameter.isAnnotationPresent(Disposes.class)) {
+                    disposesMethods.add(new DisposesMethod<>(
+                            parameter.getTypeClosure(), elementManager.getQualifiers(parameter), bean, method));
+                }
+            }
+        }
+    }
+
+    private <T> Collection<Bean<?>> loadProducerBeans(final MetaClass<T> metaClass, final Bean<T> bean) {
+        final Collection<Bean<?>> beans = new HashSet<>();
+        for (final MetaMethod<T, ?> method : metaClass.getMethods()) {
+            if (method.isAnnotationPresent(Produces.class)) {
+                beans.add(loadProducerBean(method, bean));
+            }
+        }
+        for (final MetaField<T, ?> field : metaClass.getFields()) {
+            if (field.isAnnotationPresent(Produces.class)) {
+                beans.add(loadProducerBean(field, bean));
+            }
+        }
+        return beans;
+    }
+
+    private <D, T> Bean<T> loadProducerBean(final MetaMember<D, T> member, final Bean<D> bean) {
+        final Variable<T> variable = elementManager.createVariable(member);
+        final MetaClass<D> declaringType = member.getDeclaringClass();
+        final ProducerFactory<D> factory = getProducerFactory(member, bean);
+        return addBean(new ProducerBean<>(variable, declaringType, factory));
+    }
+
+    private <D> ProducerFactory<D> getProducerFactory(final MetaMember<D, ?> member, final Bean<D> declaringBean) {
+        final Variable<?> variable = elementManager.createVariable(member);
+        final MetaMethod<D, ?> disposerMethod = resolveDisposerMethod(variable, declaringBean);
+        final Collection<InjectionPoint<?>> disposerIPs = disposerMethod == null ? Collections.emptySet() :
+                elementManager.createExecutableInjectionPoints(disposerMethod, declaringBean);
+        if (member instanceof MetaField<?, ?>) {
+            final MetaField<D, ?> field = TypeUtil.cast(member);
+            return new FieldProducerFactory<>(this, declaringBean, field, disposerMethod, disposerIPs);
+        } else {
+            final MetaMethod<D, ?> method = TypeUtil.cast(member);
+            final Collection<InjectionPoint<?>> producerIPs =
+                    elementManager.createExecutableInjectionPoints(method, declaringBean);
+            return new MethodProducerFactory<>(this, declaringBean, method, producerIPs, disposerMethod, disposerIPs);
+        }
+    }
+
+    private <D, T> MetaMethod<D, ?> resolveDisposerMethod(final Variable<T> variable, final Bean<D> bean) {
+        final List<MetaMethod<?, ?>> methods = disposesMethods.stream()
+                .filter(method -> method.matches(variable, bean))
+                .map(method -> method.disposesMethod)
+                .collect(Collectors.toList());
+        if (methods.isEmpty()) {
+            return null;
+        }
+        if (methods.size() == 1) {
+            return TypeUtil.cast(methods.get(0));
+        }
+        throw new ResolutionException("Ambiguous @Disposes methods for " + variable + ": " + methods);
+    }
+
+    @Override
+    public void validateBeans(final Iterable<Bean<?>> beans) {
+        final List<Throwable> errors = new ArrayList<>();
+        for (final Bean<?> bean : beans) {
+            for (final InjectionPoint<?> point : bean.getInjectionPoints()) {
+                try {
+                    validateInjectionPoint(point);
+                } catch (final InjectionException e) {
+                    errors.add(e);
+                }
+            }
+        }
+        if (!errors.isEmpty()) {
+            throw new ValidationException(errors);
+        }
+    }
+
+    private <T> void validateInjectionPoint(final InjectionPoint<T> point) {
+        final MetaElement<T> element = point.getElement();
+        if (element.isAnnotationPresent(Produces.class)) {
+            throw new InjectionException("Cannot inject into a @Produces element: " + element);
+        }
+        final Type type = point.getType();
+        if (type instanceof TypeVariable<?>) {
+            throw new InjectionException("Cannot inject into a TypeVariable: " + point);
+        }
+        final Class<?> rawType = TypeUtil.getRawType(type);
+        if (rawType.equals(InjectionPoint.class)) {
+            final Bean<?> bean = point.getBean()
+                    .orElseThrow(() -> new InjectionException("Cannot inject " + point + " into a non-bean"));
+            if (!bean.isPrototypeScoped()) {
+                // TODO: more useful error message
+                throw new InjectionException("Injection of " + point + " requires use of prototype scope");
+            }
+        }
+        if (rawType.equals(Bean.class)) {
+            final Bean<?> bean = point.getBean().orElseThrow(() -> new UnsatisfiedBeanException(point));
+            if (bean instanceof InjectionTargetBean<?>) {
+                validateBeanInjectionPoint(point, bean.getDeclaringClass().getBaseType());
+            } else if (bean instanceof ProducerBean<?, ?>) {
+                validateBeanInjectionPoint(point, ((ProducerBean<?, ?>) bean).getType());
+            }
+        }
+        final Optional<Bean<T>> bean = getInjectionPointBean(point);
+        if (!bean.isPresent() && !rawType.equals(Optional.class)) {
+            throw new UnsatisfiedBeanException(point);
+        }
+    }
+
+    private void validateBeanInjectionPoint(final InjectionPoint<?> point, final Type expectedType) {
+        final Type type = point.getType();
+        if (!(type instanceof ParameterizedType)) {
+            throw new InjectionException("Expected parameterized type for " + point + " but got " + expectedType);
+        }
+        final ParameterizedType parameterizedType = (ParameterizedType) type;
+        final Type[] typeArguments = parameterizedType.getActualTypeArguments();
+        if (typeArguments.length != 1) {
+            throw new InjectionException("Expected one type parameter argument for " + point + " but got " +
+                    Arrays.toString(typeArguments));
+        }
+        if (point.getQualifiers().contains(Qualifier.DEFAULT_QUALIFIER)) {
+            final Type typeArgument = typeArguments[0];
+            if (!typeArgument.equals(expectedType)) {
+                throw new InjectionException("Expected type " + expectedType + " but got " + typeArgument + " in " + point);
+            }
+        }
+    }
+
+    private <T> Optional<Bean<T>> getInjectionPointBean(final InjectionPoint<T> point) {
+        // TODO: this will need to allow for TypeConverter usage somehow
+        // first, look for an existing bean
+        final Type type = point.getType();
+        final Collection<Qualifier> qualifiers = point.getQualifiers();
+        final Optional<Bean<T>> existingBean = getExistingOrProvidedBean(type, qualifiers,
+                () -> elementManager.createVariable(point));
+        if (existingBean.isPresent()) {
+            return existingBean;
+        }
+        if (type instanceof ParameterizedType) {
+            final Class<?> rawType = TypeUtil.getRawType(type);
+            final Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];
+            if (rawType.equals(Provider.class)) {
+                // generics abuse ahoy
+                final InjectionPoint<Provider<T>> ip = TypeUtil.cast(point);
+                final Variable<Provider<T>> variable = elementManager.createVariable(ip);
+                // if a Provider<T> is requested, we can convert an existing Bean<T> into a Bean<Provider<T>>
+                return this.<T>getBean(actualType, qualifiers)
+                        .map(bean -> new ProviderBean<>(variable, context -> getValue(bean, context)))
+                        .map(this::addBean)
+                        .map(TypeUtil::cast);
+            } else if (rawType.equals(Optional.class)) {
+                final InjectionPoint<Optional<T>> ip = TypeUtil.cast(point);
+                final Variable<Optional<T>> optionalVariable = elementManager.createVariable(ip);
+                final Optional<Bean<T>> actualExistingBean = getExistingOrProvidedBean(actualType, qualifiers,
+                        // FIXME: remove need for DefaultVariable to be public
+                        () -> DefaultVariable.newVariable(
+                                TypeUtil.getTypeClosure(actualType), qualifiers, optionalVariable.getScopeType()));
+                final Bean<Optional<T>> optionalBean = addBean(new OptionalBean<>(optionalVariable,
+                        context -> actualExistingBean.map(bean -> getValue(bean, context))));
+                return Optional.of(TypeUtil.cast(optionalBean));
+            }
+        }
+        return Optional.empty();
+    }
+
+    private <T> Optional<Bean<T>> getExistingOrProvidedBean(final Type type, final Collection<Qualifier> qualifiers,
+                                                            final Supplier<Variable<T>> variableSupplier) {
+        final Optional<Bean<T>> existingBean = getBean(type, qualifiers);
+        if (existingBean.isPresent()) {
+            return existingBean;
+        }
+        final Variable<T> variable = variableSupplier.get();
+        final Type providerType = new ParameterizedTypeImpl(null, Provider.class, type);
+        final Optional<Bean<Provider<T>>> providerBean = getBean(providerType, qualifiers);
+        return providerBean.map(bean -> new ProvidedBean<>(variable, context -> getValue(bean, context).get()))
+                .map(this::addBean);
+    }
+
+    private <T> Optional<Bean<T>> getBean(final Type type, final Collection<Qualifier> qualifiers) {
+        final Set<Bean<T>> beans = this.<T>streamBeansMatchingType(type)
+                .filter(bean -> areCollectionsIsomorphic(qualifiers, bean.getQualifiers()))
+                .collect(Collectors.toSet());
+        if (beans.size() > 1) {
+            throw new AmbiguousBeanException(beans, "type " + type + " and qualifiers " + qualifiers);
+        }
+        return beans.isEmpty() ? Optional.empty() : Optional.of(beans.iterator().next());
+    }
+
+    private <T> Stream<Bean<T>> streamBeansMatchingType(final Type requiredType) {
+        if (beansByType.containsKey(requiredType)) {
+            return beansByType.get(requiredType).stream().map(TypeUtil::cast);
+        }
+        if (requiredType instanceof ParameterizedType) {
+            return beansByType.getOrDefault(((ParameterizedType) requiredType).getRawType(), Collections.emptySet())
+                    .stream()
+                    .filter(bean -> bean.hasMatchingType(requiredType))
+                    .map(TypeUtil::cast);
+        }
+        return Stream.empty();
+    }
+
+    @Override
+    public <T> InitializationContext<T> createInitializationContext(final Scoped<T> scoped) {
+        return new DefaultInitializationContext<>(scoped);
+    }
+
+    @Override
+    public <T> T getValue(final Bean<T> bean, final InitializationContext<?> parentContext) {
+        Objects.requireNonNull(bean);
+        Objects.requireNonNull(parentContext);
+        final ScopeContext context = getScopeContext(bean.getScopeType());
+        return context.getOrCreate(bean, parentContext.createDependentContext(bean));
+    }
+
+    private ScopeContext getScopeContext(final Class<? extends Annotation> scopeType) {
+        final ScopeContext scopeContext = scopes.get(scopeType);
+        if (scopeContext == null) {
+            throw new InjectionException("No active scope context found for scope @" + scopeType.getName());
+        }
+        return scopeContext;
+    }
+
+    @Override
+    public <T> Optional<T> getInjectableValue(final InjectionPoint<T> point, final InitializationContext<?> parentContext) {
+        final Bean<T> resolvedBean = getInjectionPointBean(point)
+                .orElseThrow(() -> new UnsatisfiedBeanException(point));
+        final Optional<T> existingValue = point.getBean()
+                .filter(bean -> !bean.equals(resolvedBean))
+                .flatMap(bean -> getExistingInjectableValue(resolvedBean, bean, parentContext));
+        if (existingValue.isPresent()) {
+            return existingValue;
+        }
+        final InitializationContext<?> context =
+                resolvedBean.isPrototypeScoped() ? parentContext : createInitializationContext(resolvedBean);
+        return Optional.of(getValue(resolvedBean, context));
+    }
+
+    private <T> Optional<T> getExistingInjectableValue(final Bean<T> resolvedBean, final Bean<?> pointBean,
+                                                       final InitializationContext<?> parentContext) {
+        if (resolvedBean.getScopeType() != SingletonScoped.class) {
+            return Optional.empty();
+        }
+        final Optional<Bean<?>> bean;
+        if (pointBean.isPrototypeScoped() && !resolvedBean.isPrototypeScoped()) {
+            bean = findNonPrototypeScopedDependent(parentContext);
+        } else {
+            bean = Optional.of(pointBean);
+        }
+        return bean.filter(b -> SingletonScoped.class == b.getScopeType())
+                .flatMap(b -> {
+                    final Optional<T> incompleteInstance = parentContext.getIncompleteInstance(resolvedBean);
+                    if (incompleteInstance.isPresent()) {
+                        return incompleteInstance;
+                    }
+                    return getScopeContext(resolvedBean.getScopeType()).getIfExists(resolvedBean);
+                });
+    }
+
+    @Override
+    public void close() {
+        beansByType.clear();
+        enabledBeans.clear();
+        sharedBeans.clear();
+        disposesMethods.clear();
+        scopes.values().forEach(ScopeContext::close);
+        scopes.clear();
+    }
+
+    private static boolean areCollectionsIsomorphic(final Collection<?> left, final Collection<?> right) {
+        // isomorphism assumes .equals() is implemented according to the normal .equals() contract
+        return (left.isEmpty() && right.isEmpty()) || (left.size() == right.size() && left.containsAll(right));
+    }
+
+    private static Optional<Bean<?>> findNonPrototypeScopedDependent(final InitializationContext<?> context) {
+        return context.getParentContext().flatMap(parentContext ->
+                parentContext.getScoped()
+                        .filter(Bean.class::isInstance)
+                        .map(scoped -> (Bean<?>) scoped)
+                        .flatMap(bean -> bean.isPrototypeScoped() ?
+                                findNonPrototypeScopedDependent(parentContext) : Optional.of(bean)));
+    }
+
+    private static class DisposesMethod<D> {
+        private final Collection<Type> types;
+        private final Collection<Qualifier> qualifiers;
+        private final Bean<D> declaringBean;
+        private final MetaMethod<D, ?> disposesMethod;
+
+        private DisposesMethod(final Collection<Type> types, final Collection<Qualifier> qualifiers,
+                               final Bean<D> declaringBean, final MetaMethod<D, ?> disposesMethod) {
+            this.types = types;
+            this.qualifiers = qualifiers;
+            this.declaringBean = declaringBean;
+            this.disposesMethod = disposesMethod;
+        }
+
+        boolean matches(final Variable<?> variable, final Bean<?> declaringBean) {
+            return Objects.equals(declaringBean, this.declaringBean) &&
+                    areCollectionsIsomorphic(types, variable.getTypes()) &&
+                    areCollectionsIsomorphic(qualifiers, variable.getQualifiers());
+        }
+    }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionContextFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionContextFactory.java
new file mode 100644
index 0000000..7d9b66e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionContextFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaConstructor;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+
+import java.util.Collection;
+import java.util.Objects;
+
+class DefaultInjectionContextFactory implements InjectionContext.Factory {
+    private final BeanManager beanManager;
+
+    DefaultInjectionContextFactory(final BeanManager beanManager) {
+        this.beanManager = beanManager;
+    }
+
+    @Override
+    public <T> InjectionContext<T> forConstructor(final MetaConstructor<T> constructor,
+                                                  final Collection<InjectionPoint<?>> injectionPoints) {
+        Objects.requireNonNull(constructor);
+        Objects.requireNonNull(injectionPoints);
+        return new ConstructorInjectionContext<>(beanManager, injectionPoints, constructor);
+    }
+
+    @Override
+    public <D, T> InjectionContext<T> forField(final MetaField<D, T> field, final InjectionPoint<T> injectionPoint,
+                                               final D declaringInstance) {
+        Objects.requireNonNull(field);
+        Objects.requireNonNull(injectionPoint);
+        return new FieldInjectionContext<>(beanManager, injectionPoint, field, declaringInstance);
+    }
+
+    @Override
+    public <D, T> InjectionContext<T> forMethod(final MetaMethod<D, T> method, final Collection<InjectionPoint<?>> injectionPoints,
+                                                final D declaringInstance) {
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(injectionPoints);
+        return new MethodInjectionContext<>(beanManager, injectionPoints, method, declaringInstance, null);
+    }
+
+    @Override
+    public <D, T> InjectionContext<T> forDisposerMethod(final MetaMethod<D, T> disposerMethod,
+                                                        final Collection<InjectionPoint<?>> injectionPoints,
+                                                        final D declaringInstance, final Object producerInstance) {
+        Objects.requireNonNull(disposerMethod);
+        Objects.requireNonNull(injectionPoints);
+        Objects.requireNonNull(producerInstance);
+        return new MethodInjectionContext<>(beanManager, injectionPoints, disposerMethod, declaringInstance, producerInstance);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionTarget.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionTarget.java
new file mode 100644
index 0000000..9c25571
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionTarget.java
@@ -0,0 +1,135 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.api.Disposes;
+import org.apache.logging.log4j.plugins.api.Inject;
+import org.apache.logging.log4j.plugins.api.Produces;
+import org.apache.logging.log4j.plugins.spi.bean.InjectionTarget;
+import org.apache.logging.log4j.plugins.spi.model.ElementManager;
+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.MetaConstructor;
+import org.apache.logging.log4j.plugins.spi.model.MetaElement;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMember;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+class DefaultInjectionTarget<T> implements InjectionTarget<T> {
+    private final InjectionContext.Factory injectionContextFactory;
+    private final ElementManager elementManager;
+    private final Collection<InjectionPoint<?>> injectionPoints;
+    private final MetaConstructor<T> constructor;
+    private final List<MetaMethod<T, ?>> postConstructMethods;
+    private final List<MetaMethod<T, ?>> preDestroyMethods;
+
+    DefaultInjectionTarget(final InjectionContext.Factory injectionContextFactory, final ElementManager elementManager,
+                           final Collection<InjectionPoint<?>> injectionPoints, final MetaConstructor<T> constructor,
+                           final List<MetaMethod<T, ?>> postConstructMethods, final List<MetaMethod<T, ?>> preDestroyMethods) {
+        this.injectionContextFactory = injectionContextFactory;
+        this.elementManager = elementManager;
+        this.injectionPoints = Objects.requireNonNull(injectionPoints);
+        this.constructor = Objects.requireNonNull(constructor);
+        this.postConstructMethods = Objects.requireNonNull(postConstructMethods);
+        this.preDestroyMethods = Objects.requireNonNull(preDestroyMethods);
+    }
+
+    @Override
+    public T produce(final InitializationContext<T> context) {
+        final Set<InjectionPoint<?>> constructorInjectionPoints = injectionPoints.stream()
+                .filter(point -> constructor.equals(point.getMember()))
+                .collect(Collectors.toSet());
+        return injectionContextFactory.forConstructor(constructor, constructorInjectionPoints).invoke(context);
+    }
+
+    @Override
+    public void inject(final T instance, final InitializationContext<T> context) {
+        // TODO: consider the reverse implementation where we check the instance type for injectable stuff (need to validate)
+        //       (check the Weld implementation for more details?)
+        injectFields(instance, context);
+        injectMethods(instance, context);
+        final Class<T> clazz = TypeUtil.cast(instance.getClass());
+        final MetaClass<T> metaClass = elementManager.getMetaClass(clazz);
+        for (final MetaMethod<T, ?> method : metaClass.getMethods()) {
+            if (method.isAnnotationPresent(Inject.class) && method.getParameters().isEmpty()) {
+                injectionContextFactory.forMethod(method, Collections.emptySet(), instance).invoke(context);
+            }
+        }
+    }
+
+    private void injectFields(final T instance, final InitializationContext<T> context) {
+        for (final InjectionPoint<?> point : injectionPoints) {
+            injectField(instance, context, point);
+        }
+    }
+
+    private <F> void injectField(final T instance, final InitializationContext<T> context, final InjectionPoint<F> point) {
+        final MetaElement<F> element = point.getElement();
+        if (element instanceof MetaField<?, ?> && ((MetaField<?, F>) element).getDeclaringClass().getJavaClass().isInstance(instance)) {
+            final MetaField<T, F> field = (MetaField<T, F>) element;
+            injectionContextFactory.forField(field, point, instance).invoke(context);
+        }
+    }
+
+    private void injectMethods(final T instance, final InitializationContext<T> context) {
+        final Set<MetaMember<?, ?>> injectedMethods = new HashSet<>();
+        for (final InjectionPoint<?> point : injectionPoints) {
+            if (point.getMember() instanceof MetaMethod<?, ?> &&
+                    point.getMember().getDeclaringClass().getJavaClass().isInstance(instance) &&
+                    !injectedMethods.contains(point.getMember()) &&
+                    !point.getElement().isAnnotationPresent(Produces.class) &&
+                    !point.getElement().isAnnotationPresent(Disposes.class)) {
+                final MetaMethod<T, ?> method = TypeUtil.cast(point.getMember());
+                final Set<InjectionPoint<?>> methodInjectionPoints = injectionPoints.stream()
+                        .filter(p -> method.equals(p.getMember()))
+                        .collect(Collectors.toSet());
+                injectionContextFactory.forMethod(method, methodInjectionPoints, instance).invoke(context);
+                injectedMethods.add(method);
+            }
+        }
+    }
+
+    @Override
+    public void postConstruct(final T instance) {
+        for (final MetaMethod<T, ?> method : postConstructMethods) {
+            method.invoke(instance);
+        }
+    }
+
+    @Override
+    public void preDestroy(final T instance) {
+        for (final MetaMethod<T, ?> method : preDestroyMethods) {
+            method.invoke(instance);
+        }
+    }
+
+    @Override
+    public Collection<InjectionPoint<?>> getInjectionPoints() {
+        return injectionPoints;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionTargetFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionTargetFactory.java
new file mode 100644
index 0000000..5b4522e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultInjectionTargetFactory.java
@@ -0,0 +1,110 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.api.Inject;
+import org.apache.logging.log4j.plugins.api.PostConstruct;
+import org.apache.logging.log4j.plugins.api.PreDestroy;
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.InjectionTarget;
+import org.apache.logging.log4j.plugins.spi.bean.InjectionTargetFactory;
+import org.apache.logging.log4j.plugins.spi.model.ElementManager;
+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.MetaConstructor;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+class DefaultInjectionTargetFactory<T> implements InjectionTargetFactory<T> {
+    private final ElementManager elementManager;
+    private final InjectionContext.Factory injectionContextFactory;
+    private final MetaClass<T> type;
+
+    DefaultInjectionTargetFactory(final ElementManager elementManager, final InjectionContext.Factory injectionContextFactory,
+                                  final MetaClass<T> type) {
+        this.elementManager = elementManager;
+        this.injectionContextFactory = injectionContextFactory;
+        this.type = type;
+    }
+
+    @Override
+    public InjectionTarget<T> createInjectionTarget(final Bean<T> bean) {
+        final MetaConstructor<T> constructor = getInjectableConstructor();
+        final Collection<InjectionPoint<?>> injectionPoints =
+                new HashSet<>(elementManager.createExecutableInjectionPoints(constructor, bean));
+        for (final MetaField<T, ?> field : type.getFields()) {
+            if (elementManager.isInjectable(field)) {
+                // TODO: if field is static, validate it's using an appropriate scope (singleton?)
+                injectionPoints.add(elementManager.createFieldInjectionPoint(field, bean));
+            }
+        }
+        final List<MetaMethod<T, ?>> methods = new ArrayList<>();
+        for (final MetaMethod<T, ?> method : type.getMethods()) {
+            methods.add(0, method);
+            if (!method.isStatic() && elementManager.isInjectable(method)) {
+                injectionPoints.addAll(elementManager.createExecutableInjectionPoints(method, bean));
+            }
+        }
+        // FIXME: verify these methods are ordered properly
+        final List<MetaMethod<T, ?>> postConstructMethods = methods.stream()
+                .filter(method -> method.isAnnotationPresent(PostConstruct.class))
+                .collect(Collectors.toList());
+        final List<MetaMethod<T, ?>> preDestroyMethods = methods.stream()
+                .filter(method -> method.isAnnotationPresent(PreDestroy.class))
+                .collect(Collectors.toList());
+        return new DefaultInjectionTarget<>(injectionContextFactory, elementManager, injectionPoints, constructor,
+                postConstructMethods, preDestroyMethods);
+    }
+
+    private MetaConstructor<T> getInjectableConstructor() {
+        final Collection<MetaConstructor<T>> allConstructors = type.getConstructors();
+        final List<MetaConstructor<T>> injectConstructors = allConstructors.stream()
+                .filter(constructor -> constructor.isAnnotationPresent(Inject.class))
+                .collect(Collectors.toList());
+        if (injectConstructors.size() > 1) {
+            throw new InjectionException("Found more than one constructor with @Inject for " + type);
+        }
+        if (injectConstructors.size() == 1) {
+            return injectConstructors.get(0);
+        }
+        final List<MetaConstructor<T>> injectParameterConstructors = allConstructors.stream()
+                .filter(constructor -> constructor.getParameters().stream().anyMatch(elementManager::isInjectable))
+                .collect(Collectors.toList());
+        if (injectParameterConstructors.size() > 1) {
+            throw new InjectionException("No @Inject constructors found and remaining constructors ambiguous for " + type);
+        }
+        if (injectParameterConstructors.size() == 1) {
+            return injectParameterConstructors.get(0);
+        }
+        if (allConstructors.size() == 1) {
+            final MetaConstructor<T> constructor = allConstructors.iterator().next();
+            if (constructor.getParameters().size() == 0) {
+                return constructor;
+            }
+        }
+        return type.getDefaultConstructor()
+                .orElseThrow(() -> new InjectionException("No candidate constructors found for " + type));
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ExecutableInjectionContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ExecutableInjectionContext.java
new file mode 100644
index 0000000..08e110a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ExecutableInjectionContext.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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+abstract class ExecutableInjectionContext<T> implements InjectionContext<T> {
+    private final BeanManager beanManager;
+
+    private final List<MetaParameter<?>> parameters;
+    private final Collection<InjectionPoint<?>> injectionPoints;
+
+    ExecutableInjectionContext(final BeanManager beanManager, final Collection<InjectionPoint<?>> injectionPoints,
+                               final List<MetaParameter<?>> parameters) {
+        this.beanManager = beanManager;
+        this.parameters = parameters;
+        this.injectionPoints = injectionPoints;
+    }
+
+    private <P> Optional<InjectionPoint<P>> getInjectionPoint(final MetaParameter<P> parameter) {
+        return injectionPoints.stream()
+                .filter(point -> parameter.equals(point.getElement()))
+                .findAny()
+                .map(TypeUtil::cast);
+    }
+
+    <P> P getInjectableReference(final MetaParameter<P> parameter, final InitializationContext<?> context) {
+        return getInjectionPoint(parameter)
+                .flatMap(point -> beanManager.getInjectableValue(point, context))
+                .orElseThrow(() -> new UnsupportedOperationException("TODO: primitives and defaults"));
+    }
+
+    Object[] createArguments(final InitializationContext<?> context) {
+        return parameters.stream().map(parameter -> getInjectableReference(parameter, context)).toArray();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldInjectionContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldInjectionContext.java
new file mode 100644
index 0000000..ee96a0a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldInjectionContext.java
@@ -0,0 +1,46 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+class FieldInjectionContext<D, T> implements InjectionContext<T> {
+    private final BeanManager beanManager;
+
+    private final InjectionPoint<T> injectionPoint;
+    private final MetaField<D, T> field;
+    private final D declaringInstance;
+
+    FieldInjectionContext(final BeanManager beanManager, final InjectionPoint<T> injectionPoint,
+                          final MetaField<D, T> field, final D declaringInstance) {
+        this.beanManager = beanManager;
+        this.injectionPoint = injectionPoint;
+        this.field = field;
+        this.declaringInstance = declaringInstance;
+    }
+
+    @Override
+    public T invoke(final InitializationContext<?> context) {
+        beanManager.getInjectableValue(injectionPoint, context)
+                .ifPresent(value -> field.set(declaringInstance, value));
+        return field.get(declaringInstance);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldProducer.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldProducer.java
new file mode 100644
index 0000000..e862adf
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldProducer.java
@@ -0,0 +1,57 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+
+class FieldProducer<D, T> extends AbstractProducer<D, T> {
+    private final MetaField<D, T> field;
+
+    FieldProducer(final BeanManager beanManager, final Bean<D> declaringBean, final MetaField<D, T> field,
+                  final MetaMethod<D, ?> disposerMethod, final Collection<InjectionPoint<?>> disposerInjectionPoints) {
+        super(beanManager, declaringBean, disposerMethod, disposerInjectionPoints);
+        this.field = field;
+    }
+
+    @Override
+    Type getType() {
+        return field.getBaseType();
+    }
+
+    @Override
+    public T produce(final InitializationContext<T> context) {
+        try (final InitializationContext<D> parentContext = createContext()) {
+            final D declaringInstance = field.isStatic() ? null : getDeclaringInstance(parentContext);
+            return field.get(declaringInstance);
+        }
+    }
+
+    @Override
+    public Collection<InjectionPoint<?>> getInjectionPoints() {
+        return Collections.emptySet();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldProducerFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldProducerFactory.java
new file mode 100644
index 0000000..8ac3097
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/FieldProducerFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.bean.Producer;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.util.Collection;
+
+class FieldProducerFactory<D> extends AbstractProducerFactory<D> {
+    private final BeanManager beanManager;
+
+    FieldProducerFactory(final BeanManager beanManager, final Bean<D> declaringBean,
+                         final MetaField<D, ?> producerField, final MetaMethod<D, ?> disposerMethod,
+                         final Collection<InjectionPoint<?>> disposerInjectionPoints) {
+        super(declaringBean, producerField, disposerMethod, disposerInjectionPoints);
+        this.beanManager = beanManager;
+    }
+
+    @Override
+    public <T> Producer<T> createProducer(final Bean<T> bean) {
+        final MetaField<D, T> field = TypeUtil.cast(producerMember);
+        return new FieldProducer<>(beanManager, declaringBean, field, disposerMethod, disposerInjectionPoints);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/InjectionContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/InjectionContext.java
new file mode 100644
index 0000000..6876c83
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/InjectionContext.java
@@ -0,0 +1,46 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaConstructor;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.Collection;
+
+interface InjectionContext<T> {
+    interface Factory {
+        <T> InjectionContext<T> forConstructor(final MetaConstructor<T> constructor,
+                                               final Collection<InjectionPoint<?>> injectionPoints);
+
+        <D, T> InjectionContext<T> forField(final MetaField<D, T> field, final InjectionPoint<T> injectionPoint,
+                                            final D declaringInstance);
+
+        <D, T> InjectionContext<T> forMethod(final MetaMethod<D, T> method, final Collection<InjectionPoint<?>> injectionPoints,
+                                             final D declaringInstance);
+
+        <D, T> InjectionContext<T> forDisposerMethod(final MetaMethod<D, T> disposerMethod,
+                                                     final Collection<InjectionPoint<?>> injectionPoints,
+                                                     final D declaringInstance,
+                                                     final Object producerInstance);
+    }
+
+    T invoke(final InitializationContext<?> context);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/InjectionTargetBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/InjectionTargetBean.java
new file mode 100644
index 0000000..0022e00
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/InjectionTargetBean.java
@@ -0,0 +1,80 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+import org.apache.logging.log4j.plugins.spi.bean.InjectionTarget;
+import org.apache.logging.log4j.plugins.spi.bean.InjectionTargetFactory;
+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.InitializationContext;
+
+import java.util.Collection;
+import java.util.Objects;
+
+class InjectionTargetBean<T> extends AbstractBean<T, T> {
+    private final InjectionTarget<T> injectionTarget;
+
+    InjectionTargetBean(final Variable<T> variable, final MetaClass<T> declaringClass,
+                        final InjectionTargetFactory<T> factory) {
+        super(variable, declaringClass);
+        Objects.requireNonNull(factory);
+        injectionTarget = factory.createInjectionTarget(this);
+    }
+
+    @Override
+    public Collection<InjectionPoint<?>> getInjectionPoints() {
+        return injectionTarget.getInjectionPoints();
+    }
+
+    @Override
+    public T create(final InitializationContext<T> context) {
+        final T instance = injectionTarget.produce(context);
+        if (instance == null) {
+            throw new InjectionException("Injection target created null instance: " + injectionTarget);
+        }
+        injectionTarget.inject(instance, context);
+        injectionTarget.postConstruct(instance);
+        if (isPrototypeScoped()) {
+            context.push(instance);
+        }
+        return instance;
+    }
+
+    @Override
+    public void destroy(final T instance, final InitializationContext<T> context) {
+        try {
+            if (isPrototypeScoped()) {
+                injectionTarget.preDestroy(instance);
+            }
+        } finally {
+            context.close();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "InjectionTargetBean{" +
+                "types=" + getTypes() +
+                ", scope=@" + getScopeType().getSimpleName() +
+                ", qualifiers=" + getQualifiers() +
+                ", declaringClass=" + getDeclaringClass() +
+                '}';
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodInjectionContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodInjectionContext.java
new file mode 100644
index 0000000..926196e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodInjectionContext.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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.api.Disposes;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.util.Collection;
+
+class MethodInjectionContext<D, T> extends ExecutableInjectionContext<T> {
+    private final MetaMethod<D, T> method;
+    private final D declaringInstance;
+    private final Object producerInstance;
+
+    MethodInjectionContext(final BeanManager beanManager, final Collection<InjectionPoint<?>> injectionPoints,
+                           final MetaMethod<D, T> method, final D declaringInstance, final Object producerInstance) {
+        super(beanManager, injectionPoints, method.getParameters());
+        this.method = method;
+        this.declaringInstance = declaringInstance;
+        this.producerInstance = producerInstance;
+    }
+
+    @Override
+    <P> P getInjectableReference(final MetaParameter<P> parameter, final InitializationContext<?> context) {
+        return parameter.isAnnotationPresent(Disposes.class) ? TypeUtil.cast(producerInstance) : super.getInjectableReference(parameter, context);
+    }
+
+    @Override
+    public T invoke(final InitializationContext<?> context) {
+        return method.invoke(declaringInstance, createArguments(context));
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodProducer.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodProducer.java
new file mode 100644
index 0000000..ab5515a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodProducer.java
@@ -0,0 +1,64 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+class MethodProducer<D, T> extends AbstractProducer<D, T> {
+    private final MetaMethod<D, T> producerMethod;
+    private final Collection<InjectionPoint<?>> producerInjectionPoints;
+
+    MethodProducer(final BeanManager beanManager, final Bean<D> declaringBean,
+                   final MetaMethod<D, T> producerMethod, final Collection<InjectionPoint<?>> producerInjectionPoints,
+                   final MetaMethod<D, ?> disposerMethod, final Collection<InjectionPoint<?>> disposerInjectionPoints) {
+        super(beanManager, declaringBean, disposerMethod, disposerInjectionPoints);
+        if (!producerMethod.isStatic() && declaringBean == null) {
+            // TODO: more informative error message
+            throw new InjectionException("Instance method annotated @Produces must be associated with a declaring bean");
+        }
+        this.producerMethod = producerMethod;
+        this.producerInjectionPoints = producerInjectionPoints;
+    }
+
+    @Override
+    Type getType() {
+        return producerMethod.getBaseType();
+    }
+
+    @Override
+    public T produce(final InitializationContext<T> context) {
+        try (final InitializationContext<D> parentContext = createContext()) {
+            final D declaringInstance = producerMethod.isStatic() ? null : getDeclaringInstance(parentContext);
+            return injectionContextFactory.forMethod(producerMethod, producerInjectionPoints, declaringInstance)
+                    .invoke(context);
+        }
+    }
+
+    @Override
+    public Collection<InjectionPoint<?>> getInjectionPoints() {
+        return producerInjectionPoints;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodProducerFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodProducerFactory.java
new file mode 100644
index 0000000..73e4b35
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/MethodProducerFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.bean.Producer;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.util.Collection;
+
+class MethodProducerFactory<D> extends AbstractProducerFactory<D> {
+    private final BeanManager beanManager;
+    private final Collection<InjectionPoint<?>> producerInjectionPoints;
+
+    MethodProducerFactory(final BeanManager beanManager, final Bean<D> declaringBean,
+                          final MetaMethod<D, ?> producerMethod, final Collection<InjectionPoint<?>> producerInjectionPoints,
+                          final MetaMethod<D, ?> disposerMethod, final Collection<InjectionPoint<?>> disposerInjectionPoints) {
+        super(declaringBean, producerMethod, disposerMethod, disposerInjectionPoints);
+        this.producerInjectionPoints = producerInjectionPoints;
+        this.beanManager = beanManager;
+    }
+
+    @Override
+    public <T> Producer<T> createProducer(final Bean<T> bean) {
+        final MetaMethod<D, T> producerMethod = TypeUtil.cast(producerMember);
+        return new MethodProducer<>(beanManager, declaringBean, producerMethod, producerInjectionPoints,
+                disposerMethod, disposerInjectionPoints);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/OptionalBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/OptionalBean.java
new file mode 100644
index 0000000..4acd865
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/OptionalBean.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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+class OptionalBean<T> extends SystemBean<Optional<T>> {
+    private final Function<InitializationContext<?>, Optional<T>> optionalValueFactory;
+
+    OptionalBean(final Variable<Optional<T>> variable, final Function<InitializationContext<?>, Optional<T>> optionalValueFactory) {
+        super(variable);
+        this.optionalValueFactory = optionalValueFactory;
+    }
+
+    @Override
+    public Optional<T> create(final InitializationContext<Optional<T>> context) {
+        return optionalValueFactory.apply(context);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProducerBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProducerBean.java
new file mode 100644
index 0000000..83c9a16
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProducerBean.java
@@ -0,0 +1,88 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.IllegalProductException;
+import org.apache.logging.log4j.plugins.spi.bean.Producer;
+import org.apache.logging.log4j.plugins.spi.bean.ProducerFactory;
+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.InitializationContext;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+
+class ProducerBean<D, T> extends AbstractBean<D, T> {
+    private final Producer<T> producer;
+    private final Type type;
+
+    ProducerBean(final Variable<T> variable, final MetaClass<D> declaringClass, final ProducerFactory<D> factory) {
+        super(variable, declaringClass);
+        Objects.requireNonNull(factory);
+        producer = factory.createProducer(this);
+        if (producer instanceof AbstractProducer<?, ?>) {
+            type = ((AbstractProducer<?, ?>) producer).getType();
+        } else {
+            type = variable.getTypes().iterator().next();
+        }
+    }
+
+    Type getType() {
+        return type;
+    }
+
+    @Override
+    public Collection<InjectionPoint<?>> getInjectionPoints() {
+        return producer.getInjectionPoints();
+    }
+
+    @Override
+    public T create(final InitializationContext<T> context) {
+        final T instance = producer.produce(context);
+        if (instance == null) {
+            throw new IllegalProductException("Producer created null instance: " + producer);
+        }
+        if (isPrototypeScoped()) {
+            context.push(instance);
+        }
+        return instance;
+    }
+
+    @Override
+    public void destroy(final T instance, final InitializationContext<T> context) {
+        try {
+            if (isPrototypeScoped()) {
+                producer.dispose(instance);
+            }
+        } finally {
+            context.close();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ProducerBean{" +
+                "types=" + getTypes() +
+                ", scope=@" + getScopeType().getSimpleName() +
+                ", qualifiers=" + getQualifiers() +
+                ", declaringClass=" + getDeclaringClass() +
+                '}';
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProvidedBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProvidedBean.java
new file mode 100644
index 0000000..cef3a9f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProvidedBean.java
@@ -0,0 +1,37 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.function.Function;
+
+class ProvidedBean<T> extends SystemBean<T> {
+    private final Function<InitializationContext<?>, T> providedValueFactory;
+
+    ProvidedBean(final Variable<T> variable, final Function<InitializationContext<?>, T> providedValueFactory) {
+        super(variable);
+        this.providedValueFactory = providedValueFactory;
+    }
+
+    @Override
+    public T create(final InitializationContext<T> context) {
+        return providedValueFactory.apply(context);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProviderBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProviderBean.java
new file mode 100644
index 0000000..b815a64
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/ProviderBean.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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.api.Provider;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.function.Function;
+
+class ProviderBean<T> extends SystemBean<Provider<T>> {
+    private final Function<InitializationContext<?>, T> providedValueFactory;
+
+    ProviderBean(final Variable<Provider<T>> variable, final Function<InitializationContext<?>, T> providedValueFactory) {
+        super(variable);
+        this.providedValueFactory = providedValueFactory;
+    }
+
+    @Override
+    public Provider<T> create(final InitializationContext<Provider<T>> context) {
+        return () -> providedValueFactory.apply(context);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java
new file mode 100644
index 0000000..91205b0
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java
@@ -0,0 +1,68 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+
+abstract class SystemBean<T> implements Bean<T> {
+    private final Variable<T> variable;
+
+    SystemBean(final Variable<T> variable) {
+        this.variable = variable;
+    }
+
+    @Override
+    public Collection<Qualifier> getQualifiers() {
+        return variable.getQualifiers();
+    }
+
+    @Override
+    public Collection<Type> getTypes() {
+        return variable.getTypes();
+    }
+
+    @Override
+    public Class<? extends Annotation> getScopeType() {
+        return variable.getScopeType();
+    }
+
+    @Override
+    public Collection<InjectionPoint<?>> getInjectionPoints() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public MetaClass<T> getDeclaringClass() {
+        throw new UnsupportedOperationException("TODO");
+    }
+
+    @Override
+    public void destroy(final T instance, final InitializationContext<T> context) {
+        context.close();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/AbstractMetaExecutable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/AbstractMetaExecutable.java
new file mode 100644
index 0000000..60a6fb7
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/AbstractMetaExecutable.java
@@ -0,0 +1,44 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaExecutable;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class AbstractMetaExecutable<D, T> extends AbstractMetaMember<D, T> implements MetaExecutable<D, T> {
+    private final List<MetaParameter<?>> parameters;
+
+    AbstractMetaExecutable(final MetaClass<D> declaringClass, final Executable executable, final MetaClass<T> type) {
+        super(declaringClass, executable, type);
+        parameters = new ArrayList<>(executable.getParameterCount());
+        for (final Parameter parameter : executable.getParameters()) {
+            parameters.add(new DefaultMetaParameter<>(parameter));
+        }
+    }
+
+    @Override
+    public List<MetaParameter<?>> getParameters() {
+        return parameters;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/AbstractMetaMember.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/AbstractMetaMember.java
new file mode 100644
index 0000000..f40d3fb
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/AbstractMetaMember.java
@@ -0,0 +1,80 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaMember;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+
+abstract class AbstractMetaMember<D, T> implements MetaMember<D, T> {
+    private final String name;
+    private final Collection<Annotation> annotations;
+    private final MetaClass<D> declaringClass;
+    private final MetaClass<T> type;
+    private final boolean isStatic;
+
+    AbstractMetaMember(final MetaClass<D> declaringClass, final Member member, final MetaClass<T> type) {
+        this.name = member.getName();
+        this.annotations = Arrays.asList(((AnnotatedElement) member).getAnnotations());
+        this.declaringClass = declaringClass;
+        this.type = type;
+        this.isStatic = Modifier.isStatic(member.getModifiers());
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Collection<Annotation> getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public Type getBaseType() {
+        return type.getBaseType();
+    }
+
+    @Override
+    public Collection<Type> getTypeClosure() {
+        return type.getTypeClosure();
+    }
+
+    @Override
+    public MetaClass<T> getType() {
+        return type;
+    }
+
+    @Override
+    public MetaClass<D> getDeclaringClass() {
+        return declaringClass;
+    }
+
+    @Override
+    public boolean isStatic() {
+        return isStatic;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java
new file mode 100644
index 0000000..58d8b28
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java
@@ -0,0 +1,203 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.api.AliasFor;
+import org.apache.logging.log4j.plugins.api.Default;
+import org.apache.logging.log4j.plugins.api.Named;
+import org.apache.logging.log4j.plugins.api.PrototypeScoped;
+import org.apache.logging.log4j.plugins.api.QualifierType;
+import org.apache.logging.log4j.plugins.api.ScopeType;
+import org.apache.logging.log4j.plugins.api.Stereotype;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.model.ElementManager;
+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.MetaElement;
+import org.apache.logging.log4j.plugins.spi.model.MetaExecutable;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.util.Cache;
+import org.apache.logging.log4j.plugins.util.LazyValue;
+import org.apache.logging.log4j.plugins.util.WeakCache;
+import org.apache.logging.log4j.plugins.util.WeakLazyValue;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class DefaultElementManager implements ElementManager {
+
+    private enum AnnotationType {
+        QUALIFIER, SCOPE, STEREOTYPE, UNKNOWN
+    }
+
+    private final Cache<Class<?>, MetaClass<?>> classCache = new WeakCache<>(clazz ->
+            WeakLazyValue.forSupplier(DefaultMetaClass.newMetaClassSupplier(clazz)));
+
+    private final Cache<Class<? extends Annotation>, AnnotationType> annotationTypeCache = new WeakCache<>(clazz ->
+            LazyValue.forSupplier(() -> {
+                for (final Annotation annotation : clazz.getAnnotations()) {
+                    Class<? extends Annotation> type = annotation.annotationType();
+                    if (type == AliasFor.class) {
+                        type = ((AliasFor) annotation).value();
+                    }
+                    if (type == QualifierType.class) {
+                        return AnnotationType.QUALIFIER;
+                    }
+                    if (type == ScopeType.class) {
+                        return AnnotationType.SCOPE;
+                    }
+                    if (type == Stereotype.class) {
+                        return AnnotationType.STEREOTYPE;
+                    }
+                }
+                return AnnotationType.UNKNOWN;
+            }));
+
+    @Override
+    public <T> MetaClass<T> getMetaClass(final Class<T> clazz) {
+        return classCache.get(clazz);
+    }
+
+    @Override
+    public boolean isQualifierType(final Class<? extends Annotation> annotationType) {
+        return getAnnotationType(annotationType) == AnnotationType.QUALIFIER;
+    }
+
+    @Override
+    public Collection<Qualifier> getQualifiers(final MetaElement<?> element) {
+        final Collection<Annotation> qualifiers = filterQualifiers(element.getAnnotations());
+        if (qualifiers.stream().noneMatch(annotation -> annotation.annotationType() != Named.class)) {
+            qualifiers.add(Default.INSTANCE);
+        }
+        return qualifiers.stream().map(Qualifier::fromAnnotation).collect(Collectors.toCollection(HashSet::new));
+    }
+
+    private Collection<Annotation> filterQualifiers(final Collection<Annotation> annotations) {
+        final Collection<Annotation> qualifiers = new LinkedHashSet<>(annotations.size());
+        for (final Annotation annotation : annotations) {
+            final Class<? extends Annotation> annotationType = annotation.annotationType();
+            if (isQualifierType(annotationType)) {
+                qualifiers.add(annotation);
+            } else if (isStereotype(annotationType)) {
+                qualifiers.addAll(filterQualifiers(getStereotypeDefinition(annotationType)));
+            }
+        }
+        return qualifiers;
+    }
+
+    /**
+     * Returns all the annotations associated with a {@linkplain Stereotype stereotype} annotation. This contains all
+     * annotation values sans the stereotype annotation values themselves.
+     */
+    private Collection<Annotation> getStereotypeDefinition(final Class<? extends Annotation> annotationType) {
+        final Stereotype stereotype = annotationType.getAnnotation(Stereotype.class);
+        if (stereotype == null) {
+            return Collections.emptySet();
+        }
+        final Annotation[] annotations = annotationType.getAnnotations();
+        final Collection<Annotation> stereotypeDefinition = new LinkedHashSet<>(annotations.length);
+        for (final Annotation annotation : annotations) {
+            if (isStereotype(annotation.annotationType())) {
+                stereotypeDefinition.addAll(getStereotypeDefinition(annotation.annotationType()));
+            } else {
+                stereotypeDefinition.add(annotation);
+            }
+        }
+        return Collections.unmodifiableCollection(stereotypeDefinition);
+    }
+
+    private Class<? extends Annotation> getScopeType(final MetaElement<?> element) {
+        final Collection<Class<? extends Annotation>> scopeTypes = filterScopeTypes(element.getAnnotations());
+        return scopeTypes.isEmpty() ? PrototypeScoped.class : scopeTypes.iterator().next();
+    }
+
+    private Collection<Class<? extends Annotation>> filterScopeTypes(final Collection<Annotation> annotations) {
+        // only expect at most one scope
+        final Collection<Class<? extends Annotation>> scopeTypes = new LinkedHashSet<>(1);
+        for (final Annotation annotation : annotations) {
+            final Class<? extends Annotation> annotationType = annotation.annotationType();
+            if (isScopeType(annotationType)) {
+                scopeTypes.add(annotationType);
+            } else if (isStereotype(annotationType)) {
+                scopeTypes.addAll(filterScopeTypes(getStereotypeDefinition(annotationType)));
+            }
+        }
+        return Collections.unmodifiableCollection(scopeTypes);
+    }
+
+    private boolean isStereotype(final Class<? extends Annotation> annotationType) {
+        return getAnnotationType(annotationType) == AnnotationType.STEREOTYPE;
+    }
+
+    private boolean isScopeType(final Class<? extends Annotation> annotationType) {
+        return getAnnotationType(annotationType) == AnnotationType.SCOPE;
+    }
+
+    private AnnotationType getAnnotationType(final Class<? extends Annotation> annotationType) {
+        return annotationTypeCache.get(annotationType);
+    }
+
+    @Override
+    public <D, T> InjectionPoint<T> createFieldInjectionPoint(final MetaField<D, T> field, final Bean<D> owner) {
+        Objects.requireNonNull(field);
+        final Collection<Qualifier> qualifiers = getQualifiers(field);
+        return new DefaultInjectionPoint<>(field.getBaseType(), qualifiers, owner, field, field);
+    }
+
+    @Override
+    public <D, P> InjectionPoint<P> createParameterInjectionPoint(final MetaExecutable<D, ?> executable,
+                                                                  final MetaParameter<P> parameter,
+                                                                  final Bean<D> owner) {
+        Objects.requireNonNull(executable);
+        Objects.requireNonNull(parameter);
+        final Collection<Qualifier> qualifiers = getQualifiers(parameter);
+        return new DefaultInjectionPoint<>(parameter.getBaseType(), qualifiers, owner, executable, parameter);
+    }
+
+    @Override
+    public <T> Variable<T> createVariable(final MetaElement<T> element) {
+        Objects.requireNonNull(element);
+        return createVariable(element, getQualifiers(element));
+    }
+
+    @Override
+    public <T> Variable<T> createVariable(final InjectionPoint<T> point) {
+        Objects.requireNonNull(point);
+        return createVariable(point.getElement(), point.getQualifiers());
+    }
+
+    private <T> Variable<T> createVariable(final MetaElement<T> element, final Collection<Qualifier> qualifiers) {
+        final Collection<Type> types = element.getTypeClosure();
+        final Class<? extends Annotation> scopeType = getScopeType(element);
+        return DefaultVariable.newVariable(types, qualifiers, scopeType);
+    }
+
+    @Override
+    public void close() {
+        classCache.close();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java
new file mode 100644
index 0000000..0ccccdf
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java
@@ -0,0 +1,99 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.model.MetaElement;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaMember;
+import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+
+class DefaultInjectionPoint<T> implements InjectionPoint<T> {
+    private final Type type;
+    private final Collection<Qualifier> qualifiers;
+    private final Bean<?> bean;
+    private final MetaMember<?, ?> member;
+    private final MetaElement<T> element;
+
+    DefaultInjectionPoint(final Type type, final Collection<Qualifier> qualifiers, final Bean<?> bean,
+                          final MetaMember<?, ?> member, final MetaElement<T> element) {
+        this.type = type;
+        this.qualifiers = qualifiers;
+        this.bean = bean;
+        this.member = member;
+        this.element = element;
+    }
+
+    @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public Collection<Qualifier> getQualifiers() {
+        return qualifiers;
+    }
+
+    @Override
+    public Optional<Bean<?>> getBean() {
+        return Optional.ofNullable(bean);
+    }
+
+    @Override
+    public MetaMember<?, ?> getMember() {
+        return member;
+    }
+
+    @Override
+    public MetaElement<T> getElement() {
+        return element;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final DefaultInjectionPoint<?> that = (DefaultInjectionPoint<?>) o;
+        return qualifiers.equals(that.qualifiers) &&
+                Objects.equals(bean, that.bean) &&
+                member.equals(that.member) &&
+                element.equals(that.element) &&
+                type.equals(that.type);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(qualifiers, bean, member, element, type);
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultInjectionPoint{" +
+                "type=" + type +
+                ", qualifiers=" + qualifiers +
+                ", bean=" + bean +
+                ", member=" + member.getName() +
+                ", element=" + element.getName() +
+                '}';
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaClass.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaClass.java
new file mode 100644
index 0000000..feb915e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaClass.java
@@ -0,0 +1,143 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaConstructor;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.util.LazyValue;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.apache.logging.log4j.plugins.util.Value;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+class DefaultMetaClass<T> implements MetaClass<T> {
+
+    static <T> Supplier<MetaClass<T>> newMetaClassSupplier(final Class<T> javaClass) {
+        return () -> newMetaClass(javaClass, javaClass, javaClass.getAnnotations());
+    }
+
+    static <T> MetaClass<T> newMetaClass(final Type baseType, final Class<T> javaClass, final Annotation... annotations) {
+        return new DefaultMetaClass<>(baseType, javaClass, TypeUtil.getTypeClosure(baseType), Arrays.asList(annotations));
+    }
+
+    private final Type baseType;
+    private final Class<T> javaClass;
+    private final Collection<Type> typeClosure;
+    private final Collection<Annotation> annotations;
+    private final Value<Collection<MetaConstructor<T>>> constructors;
+    private final Value<Collection<MetaMethod<T, ?>>> methods;
+    private final Value<Collection<MetaField<T, ?>>> fields;
+
+    private DefaultMetaClass(final Type baseType, final Class<T> javaClass, final Collection<Type> typeClosure,
+                             final Collection<Annotation> annotations) {
+        this.baseType = baseType;
+        this.javaClass = javaClass;
+        this.typeClosure = typeClosure;
+        this.annotations = annotations;
+        constructors = LazyValue.forSupplier(() -> Arrays.stream(javaClass.getConstructors())
+                .<Constructor<T>>map(TypeUtil::cast)
+                .map(this::getMetaConstructor)
+                .collect(Collectors.toList()));
+        methods = LazyValue.forSupplier(() -> Arrays.stream(javaClass.getMethods())
+                .map(this::getMetaMethod)
+                .collect(Collectors.toList()));
+        fields = LazyValue.forSupplier(() -> TypeUtil.getAllDeclaredFields(javaClass).stream()
+                .map(this::getMetaField)
+                .collect(Collectors.toList()));
+    }
+
+    @Override
+    public String getName() {
+        return baseType.getTypeName();
+    }
+
+    @Override
+    public Collection<Annotation> getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public Type getBaseType() {
+        return baseType;
+    }
+
+    @Override
+    public Collection<Type> getTypeClosure() {
+        return typeClosure;
+    }
+
+    @Override
+    public Class<T> getJavaClass() {
+        return javaClass;
+    }
+
+    @Override
+    public Collection<MetaConstructor<T>> getConstructors() {
+        return constructors.get();
+    }
+
+    @Override
+    public MetaConstructor<T> getMetaConstructor(final Constructor<T> constructor) {
+        return new DefaultMetaConstructor<>(this, constructor);
+    }
+
+    @Override
+    public Optional<MetaConstructor<T>> getDefaultConstructor() {
+        try {
+            return Optional.of(new DefaultMetaConstructor<>(this, javaClass.getConstructor()));
+        } catch (final NoSuchMethodException ignored) {
+            return Optional.empty();
+        }
+    }
+
+    @Override
+    public Collection<MetaMethod<T, ?>> getMethods() {
+        return methods.get();
+    }
+
+    @Override
+    public <U> MetaMethod<T, U> getMetaMethod(final Method method) {
+        return new DefaultMetaMethod<>(this, method);
+    }
+
+    @Override
+    public Collection<MetaField<T, ?>> getFields() {
+        return fields.get();
+    }
+
+    @Override
+    public <U> MetaField<T, U> getMetaField(final Field field) {
+        return new DefaultMetaField<>(this, field);
+    }
+
+    @Override
+    public String toString() {
+        return baseType.getTypeName();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaConstructor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaConstructor.java
new file mode 100644
index 0000000..53bc32a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaConstructor.java
@@ -0,0 +1,50 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.InitializationException;
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaConstructor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+class DefaultMetaConstructor<T> extends AbstractMetaExecutable<T, T> implements MetaConstructor<T> {
+    private final Constructor<T> constructor;
+
+    DefaultMetaConstructor(final MetaClass<T> metaClass, final Constructor<T> constructor) {
+        super(metaClass, constructor, metaClass);
+        this.constructor = constructor;
+    }
+
+    @Override
+    public T construct(final Object... args) {
+        try {
+            return constructor.newInstance(args);
+        } catch (final IllegalAccessException | InstantiationException e) {
+            throw new InitializationException("Error invoking constructor " + constructor, e);
+        } catch (final InvocationTargetException e) {
+            throw new InitializationException("Error invoking constructor " + constructor, e.getCause());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return constructor.toGenericString();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaField.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaField.java
new file mode 100644
index 0000000..81f2e47
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaField.java
@@ -0,0 +1,57 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.InitializationException;
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaField;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.reflect.Field;
+
+public class DefaultMetaField<D, T> extends AbstractMetaMember<D, T> implements MetaField<D, T> {
+    private final Field field;
+
+    DefaultMetaField(final MetaClass<D> declaringClass, final Field field) {
+        super(declaringClass, field, DefaultMetaClass.newMetaClass(field.getGenericType(), TypeUtil.cast(field.getType())));
+        this.field = field;
+    }
+
+    @Override
+    public T get(final D target) {
+        try {
+            return TypeUtil.cast(field.get(target));
+        } catch (final IllegalAccessException e) {
+            throw new InitializationException("Error getting field value of " + field + " from target " + target, e);
+        }
+    }
+
+    @Override
+    public void set(final D target, final T value) {
+        try {
+            field.set(target, value);
+        } catch (final IllegalAccessException e) {
+            throw new InitializationException("Error setting field value of " + field + " on target " + target, e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return field.toGenericString();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaMethod.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaMethod.java
new file mode 100644
index 0000000..c92fac6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaMethod.java
@@ -0,0 +1,51 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.InitializationException;
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class DefaultMetaMethod<D, T> extends AbstractMetaExecutable<D, T> implements MetaMethod<D, T> {
+    private final Method method;
+
+    DefaultMetaMethod(final MetaClass<D> declaringClass, final Method method) {
+        super(declaringClass, method, DefaultMetaClass.newMetaClass(method.getGenericReturnType(), TypeUtil.cast(method.getReturnType())));
+        this.method = method;
+    }
+
+    @Override
+    public T invoke(final D target, final Object... args) {
+        try {
+            return TypeUtil.cast(method.invoke(target, args));
+        } catch (final IllegalAccessException e) {
+            throw new InitializationException("Error invoking method: " + method + " on target " + target, e);
+        } catch (final InvocationTargetException e) {
+            throw new InitializationException("Error invoking method: " + method + " on target " + target, e.getCause());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return method.toGenericString();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaParameter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaParameter.java
new file mode 100644
index 0000000..2ea34d2
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultMetaParameter.java
@@ -0,0 +1,66 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+class DefaultMetaParameter<T> implements MetaParameter<T> {
+    private final String name;
+    private final MetaClass<T> parameterClass;
+    private final String toString;
+
+    DefaultMetaParameter(final Parameter parameter) {
+        name = parameter.getName();
+        final Type type = parameter.getParameterizedType();
+        final Class<T> javaClass = TypeUtil.cast(parameter.getType());
+        parameterClass = DefaultMetaClass.newMetaClass(type, javaClass, parameter.getAnnotations());
+        toString = parameter.toString();
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Collection<Annotation> getAnnotations() {
+        return parameterClass.getAnnotations();
+    }
+
+    @Override
+    public Type getBaseType() {
+        return parameterClass.getBaseType();
+    }
+
+    @Override
+    public Collection<Type> getTypeClosure() {
+        return parameterClass.getTypeClosure();
+    }
+
+    @Override
+    public String toString() {
+        return toString;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java
new file mode 100644
index 0000000..db5f6a5
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java
@@ -0,0 +1,84 @@
+/*
+ * 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.defaults.model;
+
+import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+
+public class DefaultVariable<T> implements Variable<T> {
+
+    public static <T> DefaultVariable<T> newVariable(final Collection<Type> types, final Collection<Qualifier> qualifiers,
+                                                     final Class<? extends Annotation> scopeType) {
+        return new DefaultVariable<>(types, qualifiers, scopeType);
+    }
+
+    private final Collection<Type> types;
+    private final Collection<Qualifier> qualifiers;
+    private final Class<? extends Annotation> scopeType;
+
+    private DefaultVariable(final Collection<Type> types, final Collection<Qualifier> qualifiers,
+                            final Class<? extends Annotation> scopeType) {
+        this.types = Objects.requireNonNull(types);
+        this.qualifiers = Objects.requireNonNull(qualifiers);
+        this.scopeType = Objects.requireNonNull(scopeType);
+    }
+
+    @Override
+    public Collection<Type> getTypes() {
+        return types;
+    }
+
+    @Override
+    public Collection<Qualifier> getQualifiers() {
+        return qualifiers;
+    }
+
+    @Override
+    public Class<? extends Annotation> getScopeType() {
+        return scopeType;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final DefaultVariable<?> that = (DefaultVariable<?>) o;
+        return types.equals(that.types) &&
+                qualifiers.equals(that.qualifiers) &&
+                scopeType.equals(that.scopeType);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(types, qualifiers, scopeType);
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultVariable{" +
+                "types=" + types +
+                ", qualifiers=" + qualifiers +
+                ", scope=" + scopeType +
+                '}';
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultInitializationContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultInitializationContext.java
new file mode 100644
index 0000000..dc255ed
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultInitializationContext.java
@@ -0,0 +1,106 @@
+/*
+ * 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.defaults.scope;
+
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DefaultInitializationContext<T> implements InitializationContext<T> {
+
+    private final Scoped<T> scoped;
+    private final Map<Scoped<?>, Object> incompleteInstances;
+    private final List<ScopedInstance<?>> dependentInstances;
+    private final List<ScopedInstance<?>> parentDependentInstances;
+    private final InitializationContext<?> parentContext;
+
+    public DefaultInitializationContext(final Scoped<T> scoped) {
+        this(scoped, null);
+    }
+
+    private DefaultInitializationContext(final Scoped<T> scoped, final Map<Scoped<?>, Object> incompleteInstances) {
+        this(scoped, incompleteInstances, null, null);
+    }
+
+    private DefaultInitializationContext(final Scoped<T> scoped, final Map<Scoped<?>, Object> incompleteInstances,
+                                         final List<ScopedInstance<?>> parentDependentInstances,
+                                         final InitializationContext<?> parentContext) {
+        this.scoped = scoped;
+        this.incompleteInstances = incompleteInstances != null ? incompleteInstances : new ConcurrentHashMap<>();
+        this.dependentInstances = newSynchronizedList();
+        this.parentDependentInstances = parentDependentInstances != null ? parentDependentInstances : newSynchronizedList();
+        this.parentContext = parentContext;
+    }
+
+    @Override
+    public void push(final T incompleteInstance) {
+        if (scoped != null) {
+            incompleteInstances.put(scoped, incompleteInstance);
+        }
+    }
+
+    @Override
+    public Optional<Scoped<T>> getScoped() {
+        return Optional.ofNullable(scoped);
+    }
+
+    @Override
+    public Optional<InitializationContext<?>> getParentContext() {
+        return Optional.ofNullable(parentContext);
+    }
+
+    @Override
+    public <S> InitializationContext<S> createDependentContext(final Scoped<S> scoped) {
+        return new DefaultInitializationContext<>(scoped, incompleteInstances, dependentInstances, this);
+    }
+
+    @Override
+    public <S> InitializationContext<S> createIndependentContext(final Scoped<S> scoped) {
+        return new DefaultInitializationContext<>(scoped, new ConcurrentHashMap<>(incompleteInstances));
+    }
+
+    @Override
+    public <S> Optional<S> getIncompleteInstance(final Scoped<S> scoped) {
+        return Optional.ofNullable(TypeUtil.cast(incompleteInstances.get(scoped)));
+    }
+
+    void addDependentInstance(final T dependentInstance) {
+        // TODO: why does this use the parent dependent when remove uses these dependent?
+        parentDependentInstances.add(new DefaultScopedInstance<>(scoped, dependentInstance, this));
+    }
+
+    @Override
+    public void close() {
+        for (final ScopedInstance<?> dependentInstance : dependentInstances) {
+            if (!(scoped != null && scoped.equals(dependentInstance.getScoped()))) {
+                dependentInstance.close();
+            }
+        }
+    }
+
+    private static <T> List<T> newSynchronizedList() {
+        return Collections.synchronizedList(new ArrayList<>());
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultScopeContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultScopeContext.java
new file mode 100644
index 0000000..c8faa1c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultScopeContext.java
@@ -0,0 +1,70 @@
+/*
+ * 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.defaults.scope;
+
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.ScopeContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DefaultScopeContext implements ScopeContext {
+    private final Map<Scoped<?>, ScopedInstance<?>> cache = new ConcurrentHashMap<>();
+    private final Class<? extends Annotation> scopeType;
+
+    public DefaultScopeContext(final Class<? extends Annotation> scopeType) {
+        this.scopeType = Objects.requireNonNull(scopeType);
+    }
+
+    @Override
+    public Class<? extends Annotation> getScopeType() {
+        return scopeType;
+    }
+
+    @Override
+    public <T> T getOrCreate(final Scoped<T> scoped, final InitializationContext<T> context) {
+        final ScopedInstance<T> scopedInstance =
+                TypeUtil.cast(cache.computeIfAbsent(scoped, ignored -> new LazyScopedInstance<>(scoped, context)));
+        return scopedInstance.getInstance();
+    }
+
+    @Override
+    public <T> Optional<T> getIfExists(final Scoped<T> scoped) {
+        final ScopedInstance<T> scopedInstance = TypeUtil.cast(cache.get(scoped));
+        return scopedInstance == null ? Optional.empty() : Optional.of(scopedInstance.getInstance());
+    }
+
+    @Override
+    public void destroy(final Scoped<?> scoped) {
+        final ScopedInstance<?> scopedInstance = cache.get(scoped);
+        if (scopedInstance != null) {
+            scopedInstance.close();
+            cache.remove(scoped, scopedInstance);
+        }
+    }
+
+    @Override
+    public void close() {
+        cache.keySet().forEach(this::destroy);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultScopedInstance.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultScopedInstance.java
new file mode 100644
index 0000000..01594a6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/DefaultScopedInstance.java
@@ -0,0 +1,50 @@
+/*
+ * 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.defaults.scope;
+
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.util.Objects;
+
+class DefaultScopedInstance<T> implements ScopedInstance<T> {
+    private final Scoped<T> scoped;
+    private final T instance;
+    private final InitializationContext<T> context;
+
+    DefaultScopedInstance(final Scoped<T> scoped, final T instance, final InitializationContext<T> context) {
+        this.scoped = Objects.requireNonNull(scoped);
+        this.instance = Objects.requireNonNull(instance);
+        this.context = Objects.requireNonNull(context);
+    }
+
+    @Override
+    public Scoped<T> getScoped() {
+        return scoped;
+    }
+
+    @Override
+    public T getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void close() {
+        scoped.destroy(instance, context);
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/LazyScopedInstance.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/LazyScopedInstance.java
new file mode 100644
index 0000000..aa09204
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/LazyScopedInstance.java
@@ -0,0 +1,57 @@
+/*
+ * 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.defaults.scope;
+
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.util.Objects;
+
+class LazyScopedInstance<T> implements ScopedInstance<T> {
+    private final Scoped<T> scoped;
+    private final InitializationContext<T> context;
+    private volatile T instance;
+
+    LazyScopedInstance(final Scoped<T> scoped, final InitializationContext<T> context) {
+        this.scoped = Objects.requireNonNull(scoped);
+        this.context = Objects.requireNonNull(context);
+    }
+
+    @Override
+    public Scoped<T> getScoped() {
+        return scoped;
+    }
+
+    @Override
+    public T getInstance() {
+        if (instance == null) {
+            synchronized (this) {
+                if (instance == null) {
+                    instance = scoped.create(context);
+                }
+            }
+        }
+        return instance;
+    }
+
+    @Override
+    public void close() {
+        scoped.destroy(instance, context);
+        instance = null;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/PrototypeScopeContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/PrototypeScopeContext.java
new file mode 100644
index 0000000..51f8ced
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/PrototypeScopeContext.java
@@ -0,0 +1,55 @@
+/*
+ * 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.defaults.scope;
+
+import org.apache.logging.log4j.plugins.api.PrototypeScoped;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.ScopeContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+
+public class PrototypeScopeContext implements ScopeContext {
+    @Override
+    public Class<? extends Annotation> getScopeType() {
+        return PrototypeScoped.class;
+    }
+
+    @Override
+    public <T> T getOrCreate(final Scoped<T> scoped, final InitializationContext<T> context) {
+        final T instance = scoped.create(context);
+        if (context instanceof DefaultInitializationContext<?>) {
+            ((DefaultInitializationContext<T>) context).addDependentInstance(instance);
+        }
+        return instance;
+    }
+
+    @Override
+    public <T> Optional<T> getIfExists(final Scoped<T> scoped) {
+        return Optional.empty();
+    }
+
+    @Override
+    public void destroy(final Scoped<?> scoped) {
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/ScopedInstance.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/ScopedInstance.java
new file mode 100644
index 0000000..c03eeed
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/scope/ScopedInstance.java
@@ -0,0 +1,29 @@
+/*
+ * 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.defaults.scope;
+
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+interface ScopedInstance<T> extends AutoCloseable {
+    Scoped<T> getScoped();
+
+    T getInstance();
+
+    @Override
+    void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Cache.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Cache.java
new file mode 100644
index 0000000..f3c7dbd
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Cache.java
@@ -0,0 +1,25 @@
+/*
+ * 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.util;
+
+public interface Cache<K, V> extends AutoCloseable {
+    <U extends V> U get(final K key);
+
+    @Override
+    void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/LazyValue.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/LazyValue.java
new file mode 100644
index 0000000..385997f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/LazyValue.java
@@ -0,0 +1,48 @@
+/*
+ * 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.util;
+
+import java.util.function.Supplier;
+
+public class LazyValue<T> implements Value<T> {
+
+    public static <T> LazyValue<T> forSupplier(final Supplier<T> valueSupplier) {
+        return new LazyValue<>(valueSupplier);
+    }
+
+    private final Supplier<T> valueSupplier;
+    private volatile T value;
+
+    private LazyValue(final Supplier<T> valueSupplier) {
+        this.valueSupplier = valueSupplier;
+    }
+
+    @Override
+    public T get() {
+        T value = this.value;
+        if (value == null) {
+            synchronized (this) {
+                value = this.value;
+                if (value == null) {
+                    this.value = value = valueSupplier.get();
+                }
+            }
+        }
+        return value;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/ParameterizedTypeImpl.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/ParameterizedTypeImpl.java
new file mode 100644
index 0000000..682a16a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/ParameterizedTypeImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class ParameterizedTypeImpl implements ParameterizedType {
+    private final Type ownerType;
+    private final Type rawType;
+    private final Type[] actualTypeArguments;
+
+    public ParameterizedTypeImpl(final Type ownerType, final Type rawType, final Type... actualTypeArguments) {
+        this.ownerType = ownerType;
+        this.rawType = rawType;
+        this.actualTypeArguments = actualTypeArguments;
+    }
+
+    @Override
+    public Type getOwnerType() {
+        return ownerType;
+    }
+
+    @Override
+    public Type getRawType() {
+        return rawType;
+    }
+
+    @Override
+    public Type[] getActualTypeArguments() {
+        return Arrays.copyOf(actualTypeArguments, actualTypeArguments.length);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ParameterizedType)) return false;
+        final ParameterizedType that = (ParameterizedType) o;
+        return Objects.equals(ownerType, that.getOwnerType()) &&
+                Objects.equals(rawType, that.getRawType()) &&
+                Arrays.equals(actualTypeArguments, that.getActualTypeArguments());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(ownerType, rawType);
+        result = 31 * result + Arrays.hashCode(actualTypeArguments);
+        return result;
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Value.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Value.java
new file mode 100644
index 0000000..b59b66d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Value.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.util;
+
+public interface Value<T> {
+    T get();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/WeakCache.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/WeakCache.java
new file mode 100644
index 0000000..d44c14f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/WeakCache.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.util;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Function;
+
+public class WeakCache<K, V> implements Cache<K, V> {
+
+    private final long maxSize;
+    private final Function<K, Value<? extends V>> valueFunction;
+    private final Map<K, Value<? extends V>> map = new WeakHashMap<>();
+
+    public WeakCache(final Function<K, Value<? extends V>> valueFunction) {
+        this(0, valueFunction);
+    }
+
+    public WeakCache(final long maxSize, final Function<K, Value<? extends V>> valueFunction) {
+        this.valueFunction = valueFunction;
+        this.maxSize = maxSize;
+    }
+
+    @Override
+    public synchronized <U extends V> U get(final K key) {
+        final Value<? extends V> value = map.computeIfAbsent(key, valueFunction);
+        if (maxSize > 0 && map.size() > maxSize) {
+            map.clear();
+        }
+        return TypeUtil.cast(value.get());
+    }
+
+    @Override
+    public void close() {
+        map.clear();
+    }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/WeakLazyValue.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/WeakLazyValue.java
new file mode 100644
index 0000000..6f6cd07
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/WeakLazyValue.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util;
+
+import java.lang.ref.WeakReference;
+import java.util.function.Supplier;
+
+public class WeakLazyValue<T> implements Value<T> {
+
+    public static <T> WeakLazyValue<T> forSupplier(final Supplier<T> valueSupplier) {
+        return new WeakLazyValue<T>(valueSupplier);
+    }
+
+    private final Supplier<T> valueSupplier;
+    private volatile WeakReference<T> reference;
+
+    private WeakLazyValue(final Supplier<T> valueSupplier) {
+        this.valueSupplier = valueSupplier;
+    }
+
+    @Override
+    public T get() {
+        final WeakReference<T> reference = this.reference;
+        T value = reference == null ? null : reference.get();
+        if (value == null) {
+            synchronized (this) {
+                if (this.reference == reference) {
+                    value = valueSupplier.get();
+                    this.reference = new WeakReference<>(value);
+                }
+            }
+        }
+        return value;
+    }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManagerTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManagerTest.java
new file mode 100644
index 0000000..315b5d8
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManagerTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.defaults.bean;
+
+import org.apache.logging.log4j.plugins.api.Default;
+import org.apache.logging.log4j.plugins.api.Inject;
+import org.apache.logging.log4j.plugins.api.PostConstruct;
+import org.apache.logging.log4j.plugins.api.Produces;
+import org.apache.logging.log4j.plugins.api.Provider;
+import org.apache.logging.log4j.plugins.api.QualifierType;
+import org.apache.logging.log4j.plugins.api.SingletonScoped;
+import org.apache.logging.log4j.plugins.test.BeanJUnit4Runner;
+import org.apache.logging.log4j.plugins.test.WithBeans;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.*;
+
+@RunWith(BeanJUnit4Runner.class)
+public class DefaultBeanManagerTest {
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @QualifierType
+    public @interface Run {
+    }
+
+    @Produces
+    @SingletonScoped
+    public String globalString = "global string value";
+
+    @Produces
+    @SingletonScoped
+    @Run
+    public String testString() {
+        return "test string value";
+    }
+
+    @Test
+    public void testParameterInjection(final String unqualified, @Run final String qualified) {
+        assertEquals(globalString, unqualified);
+        assertEquals(testString(), qualified);
+    }
+
+    public static class FieldInjection {
+        @Inject
+        private String unqualified;
+        @Run
+        private String implicitQualified;
+        @Default
+        private String implicitDefault;
+    }
+
+    @WithBeans(FieldInjection.class)
+    @Test
+    public void testFieldInjection(final FieldInjection instance) {
+        assertEquals(globalString, instance.unqualified);
+        assertEquals(testString(), instance.implicitQualified);
+        assertEquals(globalString, instance.implicitDefault);
+    }
+
+    public static class ExplicitConstructorInjection {
+        private final String first;
+        private final String second;
+
+        @Inject
+        public ExplicitConstructorInjection(@Default final String first, @Run final String second) {
+            this.first = first;
+            this.second = second;
+        }
+    }
+
+    @WithBeans(ExplicitConstructorInjection.class)
+    @Test
+    public void testExplicitConstructorInjection(final ExplicitConstructorInjection instance) {
+        assertEquals(globalString, instance.first);
+        assertEquals(testString(), instance.second);
+    }
+
+    public static class ImplicitConstructorInjection {
+        private final String first;
+        private final String second;
+
+        public ImplicitConstructorInjection(final String first, @Run final String second) {
+            this.first = first;
+            this.second = second;
+        }
+    }
+
+    @WithBeans(ImplicitConstructorInjection.class)
+    @Test
+    public void testImplicitConstructorInjection(final ImplicitConstructorInjection instance) {
+        assertEquals(globalString, instance.first);
+        assertEquals(testString(), instance.second);
+    }
+
+    public static class DefaultConstructorInjection {
+    }
+
+    @WithBeans(DefaultConstructorInjection.class)
+    @Test
+    public void testNoArgsConstructorInjection(final DefaultConstructorInjection instance) {
+        assertNotNull(instance);
+    }
+
+    @WithBeans(DefaultConstructorInjection.class)
+    @Test
+    public void testPrototypeScopeDifferentInstances(final DefaultConstructorInjection first,
+                                                     final DefaultConstructorInjection second) {
+        assertNotSame(first, second);
+    }
+
+    @SingletonScoped
+    public static class SingletonInjection {
+    }
+
+    @WithBeans(SingletonInjection.class)
+    @Test
+    public void testSingletonScopeSameInstances(final SingletonInjection first, final SingletonInjection second) {
+        assertSame(first, second);
+    }
+
+    public static class StaticMethodProduction {
+        private final String first;
+        private final String second;
+
+        private StaticMethodProduction(final String first, final String second) {
+            this.first = first;
+            this.second = second;
+        }
+
+        @Produces
+        public static StaticMethodProduction produce(final String first, @Run final String second) {
+            return new StaticMethodProduction(first, second);
+        }
+    }
+
+    @WithBeans(StaticMethodProduction.class)
+    @Test
+    public void testStaticMethodProduction(final StaticMethodProduction instance) {
+        assertEquals(globalString, instance.first);
+        assertEquals(testString(), instance.second);
+    }
+
+    public static class ProviderProvidedProduction {
+        private final String first;
+        private final String second;
+
+        private ProviderProvidedProduction(final String first, final String second) {
+            this.first = first;
+            this.second = second;
+        }
+
+        public static class Builder implements Provider<ProviderProvidedProduction> {
+            private String first;
+            private String second;
+
+            @Inject
+            public Builder withFirst(final String first) {
+                this.first = first;
+                return this;
+            }
+
+            @Inject
+            public Builder withSecond(@Run final String second) {
+                this.second = second;
+                return this;
+            }
+
+            @Override
+            public ProviderProvidedProduction get() {
+                return new ProviderProvidedProduction(first, second);
+            }
+        }
+    }
+
+    @WithBeans(ProviderProvidedProduction.Builder.class)
+    @Test
+    public void testProviderProvidedProduction(final ProviderProvidedProduction instance) {
+        assertEquals(globalString, instance.first);
+        assertEquals(testString(), instance.second);
+    }
+
+    @Test
+    public void testProviderInjection(final Provider<String> stringProvider) {
+        assertEquals(globalString, stringProvider.get());
+    }
+
+    @Test
+    public void testQualifiedProviderInjection(@Run final Provider<String> stringProvider) {
+        assertEquals(testString(), stringProvider.get());
+    }
+
+    @Test
+    public void testOptionalInjectionWhenNoBeanProvided(final Optional<DefaultConstructorInjection> instance) {
+        assertFalse(instance.isPresent());
+    }
+
+    @WithBeans(DefaultConstructorInjection.class)
+    @Test
+    public void testOptionalInjectionWhenBeanProvided(final Optional<DefaultConstructorInjection> instance) {
+        assertTrue(instance.isPresent());
+    }
+
+    @SingletonScoped
+    public static class IdGenerator {
+        private final AtomicInteger current = new AtomicInteger();
+
+        @Produces
+        public int nextId() {
+            return current.incrementAndGet();
+        }
+    }
+
+    public static class PostConstructInjection {
+        private int one;
+        private int two;
+        @Inject
+        private int three;
+        private int four;
+        private int five;
+        private int six;
+        @Inject
+        private IdGenerator idGenerator;
+
+        @Inject
+        public PostConstructInjection(final int a, final int b) {
+            one = a;
+            two = b;
+        }
+
+        @PostConstruct
+        public void init() {
+            six = idGenerator.nextId();
+        }
+
+        @Inject
+        public void setValue(final int value, final Integer otherValue) {
+            four = value;
+            five = otherValue;
+        }
+    }
+
+    @WithBeans({IdGenerator.class, PostConstructInjection.class})
+    @Test
+    public void testPostConstructInjection(final PostConstructInjection instance) {
+        assertEquals(1, instance.one);
+        assertEquals(2, instance.two);
+        assertEquals(3, instance.three);
+        assertEquals(4, instance.four);
+        assertEquals(5, instance.five);
+        assertEquals(6, instance.six);
+    }
+
+    // TODO: add tests for other supported injection scenarios
+    // TODO: add tests for hierarchical scopes
+    // TODO: add tests for default @Named value calculation
+    // TODO: add tests for @Named alias annotations like @PluginAttribute == @Named
+}
\ No newline at end of file
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/test/BeanJUnit4Runner.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/test/BeanJUnit4Runner.java
new file mode 100644
index 0000000..2e2d2d9
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/test/BeanJUnit4Runner.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.plugins.defaults.bean.DefaultBeanManager;
+import org.apache.logging.log4j.plugins.defaults.model.DefaultElementManager;
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+import org.apache.logging.log4j.plugins.spi.UnsatisfiedBeanException;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.bean.BeanManager;
+import org.apache.logging.log4j.plugins.spi.model.ElementManager;
+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.MetaMethod;
+import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.junit.Test;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * JUnit 4 test runner that integrates with {@link BeanManager} to create test instances and inject test parameters.
+ * Beans to load can be specified with the {@link WithBeans} annotation on the class or method. The test class itself
+ * must be a bean. Each test method creates a new BeanManager, loads beans annotated on the test class, creates a
+ * test instance, loads beans annotated on the test method, then injects values into the test method parameters, invoking
+ * that method.
+ */
+public class BeanJUnit4Runner extends BlockJUnit4ClassRunner {
+    private ElementManager elementManager;
+    private MetaClass<?> testMetaClass;
+
+    private BeanManager beanManager;
+    private Bean<?> testClassBean;
+    private InitializationContext<?> initializationContext;
+
+    public BeanJUnit4Runner(final Class<?> clazz) throws InitializationError {
+        super(clazz);
+    }
+
+    @Override
+    protected void collectInitializationErrors(final List<Throwable> errors) {
+        // this must come before call to super to allow use in validateConstructor()
+        elementManager = new DefaultElementManager();
+        testMetaClass = elementManager.getMetaClass(getTestClass().getJavaClass());
+        super.collectInitializationErrors(errors);
+    }
+
+    @Override
+    protected void validateConstructor(final List<Throwable> errors) {
+        if (!elementManager.isInjectable(testMetaClass)) {
+            errors.add(new InjectionException(testMetaClass + " does not have any injectable constructors"));
+        }
+    }
+
+    @Override
+    protected void validateTestMethods(final List<Throwable> errors) {
+        for (final FrameworkMethod method : getTestClass().getAnnotatedMethods(Test.class)) {
+            method.validatePublicVoid(false, errors);
+        }
+    }
+
+    @Override
+    protected Object createTest() throws Exception {
+        return createTestInstance();
+    }
+
+    private <T> T createTestInstance() {
+        beanManager = new DefaultBeanManager(elementManager);
+        final WithBeans testClassBeans = getTestClass().getAnnotation(WithBeans.class);
+        if (testClassBeans != null) {
+            beanManager.loadBeans(testClassBeans.value());
+        }
+        final Class<T> testClass = TypeUtil.cast(getTestClass().getJavaClass());
+        final Optional<Bean<T>> testBean = beanManager.loadBeans(testClass).stream()
+                .filter(bean -> bean.hasMatchingType(testClass))
+                .findAny()
+                .map(TypeUtil::cast);
+        final Bean<T> testClassBean = testBean
+                .orElseThrow(() -> new UnsatisfiedBeanException(testClass));
+        this.testClassBean = testClassBean;
+        // FIXME: should we use a null bean or not? I'm still confused by the init context thing
+        initializationContext = beanManager.createInitializationContext(testClassBean);
+        return beanManager.getValue(testClassBean, initializationContext);
+    }
+
+    @Override
+    protected Statement methodInvoker(final FrameworkMethod method, final Object test) {
+        return genericMethodInvoker(method, test);
+    }
+
+    private <T> Statement genericMethodInvoker(final FrameworkMethod method, final T testInstance) {
+        final Bean<T> testClassBean = TypeUtil.cast(this.testClassBean);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    final WithBeans methodBeans = method.getAnnotation(WithBeans.class);
+                    if (methodBeans != null) {
+                        beanManager.loadBeans(methodBeans.value());
+                    }
+                    final Class<T> testClass = TypeUtil.cast(getTestClass().getJavaClass());
+                    final MetaClass<T> metaClass = elementManager.getMetaClass(testClass);
+                    final MetaMethod<T, Void> metaMethod = metaClass.getMetaMethod(method.getMethod());
+                    final List<MetaParameter<?>> parameters = metaMethod.getParameters();
+                    final Object[] args = new Object[parameters.size()];
+                    for (int i = 0; i < args.length; i++) {
+                        final MetaParameter<?> parameter = parameters.get(i);
+                        final InjectionPoint<?> point =
+                                elementManager.createParameterInjectionPoint(metaMethod, parameter, testClassBean);
+                        args[i] = beanManager.getInjectableValue(point, initializationContext)
+                                .orElseThrow(() -> new UnsatisfiedBeanException(point));
+                    }
+                    metaMethod.invoke(testInstance, args);
+                } finally {
+                    initializationContext.close();
+                    initializationContext = null;
+                    beanManager.close();
+                    beanManager = null;
+                }
+            }
+        };
+    }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/test/WithBeans.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/test/WithBeans.java
new file mode 100644
index 0000000..fcaef5e
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/test/WithBeans.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.test;
+
+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;
+
+/**
+ * Lists bean classes to load for a unit test.
+ *
+ * @see BeanJUnit4Runner
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Documented
+public @interface WithBeans {
+    Class<?>[] value();
+}