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

[logging-log4j2] branch mean-bean-machine created (now 379234d)

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

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


      at 379234d  Add v3 dependency injection default implementation and tests

This branch includes the following new commits:

     new 2344d35  Add v3 annotation API
     new bc2e767  Add v3 dependency injection SPI
     new 379234d  Add v3 dependency injection default implementation and tests

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[logging-log4j2] 01/03: Add v3 annotation API

Posted by ma...@apache.org.
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 2344d35bc1a2322c099b14c4155a18e514a655e5
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sun Feb 23 17:43:12 2020 -0600

    Add v3 annotation API
    
    Signed-off-by: Matt Sicker <bo...@gmail.com>
---
 .../apache/logging/log4j/plugins/api/AliasFor.java | 33 +++++++++++++
 .../apache/logging/log4j/plugins/api/Default.java  | 56 ++++++++++++++++++++++
 .../apache/logging/log4j/plugins/api/Disposes.java | 31 ++++++++++++
 .../apache/logging/log4j/plugins/api/Ignore.java   | 33 +++++++++++++
 .../apache/logging/log4j/plugins/api/Inject.java   | 30 ++++++++++++
 .../apache/logging/log4j/plugins/api/Named.java    | 35 ++++++++++++++
 .../logging/log4j/plugins/api/PostConstruct.java   | 30 ++++++++++++
 .../logging/log4j/plugins/api/PreDestroy.java      | 30 ++++++++++++
 .../apache/logging/log4j/plugins/api/Produces.java | 30 ++++++++++++
 .../logging/log4j/plugins/api/PrototypeScoped.java | 31 ++++++++++++
 .../apache/logging/log4j/plugins/api/Provider.java | 22 +++++++++
 .../logging/log4j/plugins/api/QualifierType.java   | 31 ++++++++++++
 .../logging/log4j/plugins/api/ScopeType.java       | 31 ++++++++++++
 .../logging/log4j/plugins/api/SingletonScoped.java | 30 ++++++++++++
 .../logging/log4j/plugins/api/Stereotype.java      | 34 +++++++++++++
 15 files changed, 487 insertions(+)

diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/AliasFor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/AliasFor.java
new file mode 100644
index 0000000..b7541d8
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/AliasFor.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// TODO: documentation around where this can be applied
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+@Documented
+public @interface AliasFor {
+    Class<? extends Annotation> value();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java
new file mode 100644
index 0000000..4ec2398
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@QualifierType
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+// default qualifier unless qualifier besides @Named is present
+public @interface Default {
+    @SuppressWarnings("ClassExplicitlyAnnotation")
+    class Literal implements Default, Annotation {
+        @Override
+        public Class<? extends Annotation> annotationType() {
+            return Default.class;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            return this == o || o instanceof Default;
+        }
+
+        @Override
+        public int hashCode() {
+            return Default.class.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return "@Default";
+        }
+    }
+
+    Default INSTANCE = new Literal();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Disposes.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Disposes.java
new file mode 100644
index 0000000..d89873a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Disposes.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+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;
+
+// TODO: validate this is not allowed on static methods
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+@Documented
+public @interface Disposes {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Ignore.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Ignore.java
new file mode 100644
index 0000000..3c97846
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Ignore.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// TODO: documentation around where this can be applied
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Documented
+@Inherited
+public @interface Ignore {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Inject.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Inject.java
new file mode 100644
index 0000000..343df0c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Inject.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api;
+
+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;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
+@Documented
+public @interface Inject {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Named.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Named.java
new file mode 100644
index 0000000..b4f67e7
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Named.java
@@ -0,0 +1,35 @@
+/*
+ * 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.api;
+
+import org.apache.logging.log4j.plugins.name.NameProvider;
+import org.apache.logging.log4j.plugins.name.NamedQualifierNameProvider;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@QualifierType
+@NameProvider(NamedQualifierNameProvider.class)
+public @interface Named {
+    // TODO: consider supporting String[] for listing aliases? (or see @Alternative?)
+    String value() default Strings.EMPTY;
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PostConstruct.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PostConstruct.java
new file mode 100644
index 0000000..ad5263a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PostConstruct.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api;
+
+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;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface PostConstruct {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PreDestroy.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PreDestroy.java
new file mode 100644
index 0000000..4d6abaa
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PreDestroy.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api;
+
+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;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface PreDestroy {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Produces.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Produces.java
new file mode 100644
index 0000000..213ba26
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Produces.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api;
+
+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;
+
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Produces {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PrototypeScoped.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PrototypeScoped.java
new file mode 100644
index 0000000..43a2934
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/PrototypeScoped.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ScopeType
+// TODO: consider renaming to DefaultScoped or DependentScoped
+public @interface PrototypeScoped {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Provider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Provider.java
new file mode 100644
index 0000000..0407c5c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Provider.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.api;
+
+public interface Provider<T> {
+    T get();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/QualifierType.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/QualifierType.java
new file mode 100644
index 0000000..9fc160a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/QualifierType.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+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;
+
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface QualifierType {
+    // default is @Default
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/ScopeType.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/ScopeType.java
new file mode 100644
index 0000000..ce3a8d4
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/ScopeType.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.api;
+
+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;
+
+// essentially the same as @Scope and @NormalScope combined; no distinction necessary here
+@Target(ElementType.ANNOTATION_TYPE) // on an annotation typically with @Target(TYPE), but we use static factory methods
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ScopeType {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/SingletonScoped.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/SingletonScoped.java
new file mode 100644
index 0000000..434c03b
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/SingletonScoped.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@ScopeType
+public @interface SingletonScoped {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Stereotype.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Stereotype.java
new file mode 100644
index 0000000..280ebb3
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Stereotype.java
@@ -0,0 +1,34 @@
+/*
+ * 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.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an annotation type as a stereotype for combining annotations. This can combine zero or more
+ * {@linkplain QualifierType qualifier types} and an optional {@linkplain ScopeType scope type}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+@Documented
+public @interface Stereotype {
+}


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

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit bc2e7674e21f24a2a7cec1a5c7b95ade843ce6cc
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sun Feb 23 17:46:40 2020 -0600

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

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


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

Posted by ma...@apache.org.
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();
+}