You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:55:35 UTC

[sling-org-apache-sling-models-impl] 05/24: SLING-3716 SLING-3718 - adding support for constructor injection and self injection based on a patch from Stefan Seifert

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

rombert pushed a commit to annotated tag org.apache.sling.models.impl-1.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-impl.git

commit 76f3fae7933d72c0cbb4cd8f9e96b0c681dd94c0
Author: Justin Edelson <ju...@apache.org>
AuthorDate: Tue Aug 19 22:51:36 2014 +0000

    SLING-3716 SLING-3718 - adding support for constructor injection and self injection based on a patch from Stefan Seifert
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/impl@1619007 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   2 +-
 .../sling/models/impl/ConstructorParameter.java    |  87 ++++++
 .../models/impl/MapBackedInvocationHandler.java    |  37 +++
 .../sling/models/impl/ModelAdapterFactory.java     | 304 ++++++++++++++-------
 .../impl/ParameterCountInjectComparator.java       |  63 +++++
 .../models/impl/injectors/OSGiServiceInjector.java |   3 +-
 .../sling/models/impl/injectors/SelfInjector.java  |  87 ++++++
 .../apache/sling/models/impl/ConstructorTest.java  |  39 ++-
 .../org/apache/sling/models/impl/DefaultTest.java  |  62 ++++-
 .../impl/InjectorSpecificAnnotationTest.java       | 106 ++++++-
 .../sling/models/impl/OSGiInjectionTest.java       |  62 ++++-
 .../sling/models/impl/RequestInjectionTest.java    |  11 +-
 .../sling/models/impl/SelfDependencyTest.java      |  95 +++++++
 .../impl/injectors/BindingsInjectorTest.java       |  98 +++++++
 .../injectors/RequestAttributeInjectorTest.java    |  86 ++++++
 .../models/impl/injectors/SelfInjectorTest.java    |  86 ++++++
 .../impl/injectors/ValueMapInjectorTest.java       |  86 ++++++
 .../classes/DirectCyclicSelfDependencyModel.java   |  36 +++
 .../IndirectCyclicSelfDependencyModelA.java        |  36 +++
 .../IndirectCyclicSelfDependencyModelB.java        |  36 +++
 .../testmodels/classes/SelfDependencyModelA.java   |  36 +++
 .../testmodels/classes/SelfDependencyModelB.java   |  36 +++
 .../constructorinjection/BindingsModel.java        |  41 +++
 .../DefaultPrimitivesModel.java                    |  62 +++++
 .../constructorinjection/DefaultStringModel.java   |  48 ++++
 .../constructorinjection/DefaultWrappersModel.java |  63 +++++
 .../InjectorSpecificAnnotationModel.java           |  91 ++++++
 .../constructorinjection/ListOSGiModel.java        |  42 +++
 .../classes/constructorinjection/NoNameModel.java  |  42 +++
 .../constructorinjection/SimpleOSGiModel.java      |  40 +++
 .../WithThreeConstructorsOneInjectModel.java       |  59 ++++
 31 files changed, 1850 insertions(+), 132 deletions(-)

diff --git a/pom.xml b/pom.xml
index 94c9d3c..426fb1d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.models.api</artifactId>
-            <version>1.0.2</version>
+            <version>1.0.3-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/models/impl/ConstructorParameter.java b/src/main/java/org/apache/sling/models/impl/ConstructorParameter.java
new file mode 100644
index 0000000..eab70ae
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/ConstructorParameter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sling.models.impl;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+
+/**
+ * Constructor parameters aren't normally accessible using the
+ * AnnotatedElement. This class acts as a facade to ease
+ * compatibility with field and method injection.
+ */
+class ConstructorParameter implements AnnotatedElement {
+
+    private final Annotation[] annotations;
+    private final Class<?> type;
+    private final Type genericType;
+    private final int parameterIndex;
+
+    ConstructorParameter(Annotation[] annotations, Class<?> type, Type genericType, int parameterIndex) {
+        this.annotations = annotations;
+        this.type = type;
+        this.genericType = genericType;
+        this.parameterIndex = parameterIndex;
+    }
+
+    @Override
+    public boolean isAnnotationPresent(Class<? extends Annotation> paramClass) {
+        return getAnnotation(paramClass) != null;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> paramClass) {
+        for (Annotation annotation : this.annotations) {
+            if (paramClass.isInstance(annotation)) {
+                return (T)annotation;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Annotation[] getAnnotations() {
+        return annotations;
+    }
+
+    @Override
+    public Annotation[] getDeclaredAnnotations() {
+        return annotations;
+    }
+
+    public Class<?> getType() {
+        return this.type;
+    }
+
+    public Type getGenericType() {
+        return this.genericType;
+    }
+
+    public int getParameterIndex() {
+        return this.parameterIndex;
+    }
+    
+    @Override
+    public String toString() {
+        return "Parameter" + this.parameterIndex + "[" + this.genericType.toString() + "]";
+    }
+    
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/models/impl/MapBackedInvocationHandler.java b/src/main/java/org/apache/sling/models/impl/MapBackedInvocationHandler.java
new file mode 100644
index 0000000..c486781
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/MapBackedInvocationHandler.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.sling.models.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+class MapBackedInvocationHandler implements InvocationHandler {
+
+    private Map<Method, Object> methods;
+
+    public MapBackedInvocationHandler(Map<Method, Object> methods) {
+        this.methods = methods;
+    }
+
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        return methods.get(method);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
index 8532de4..b77cce1 100644
--- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
+++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
@@ -32,7 +32,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -69,6 +68,7 @@ import org.apache.sling.models.annotations.Source;
 import org.apache.sling.models.annotations.Via;
 import org.apache.sling.models.spi.DisposalCallback;
 import org.apache.sling.models.spi.DisposalCallbackRegistry;
+import org.apache.sling.models.spi.AcceptsNullName;
 import org.apache.sling.models.spi.Injector;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotation;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
@@ -83,23 +83,6 @@ import org.slf4j.LoggerFactory;
 @Component
 public class ModelAdapterFactory implements AdapterFactory, Runnable {
 
-    /**
-     * Comparator which sorts constructors by the number of parameters
-     * in reverse order (most params to least params).
-     */
-    public class ParameterCountComparator implements Comparator<Constructor<?>> {
-
-        @Override
-        public int compare(Constructor<?> o1, Constructor<?> o2) {
-            return compare(o2.getParameterTypes().length, o1.getParameterTypes().length);
-        }
-
-        public int compare(int x, int y) {
-            return (x < y) ? -1 : ((x == y) ? 0 : 1);
-        }
-
-    }
-
     private static class DisposalCallbackRegistryImpl implements DisposalCallbackRegistry {
 
         private List<DisposalCallback> callbacks = new ArrayList<DisposalCallback>();
@@ -125,20 +108,6 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
 
     private ConcurrentMap<java.lang.ref.Reference<Object>, DisposalCallbackRegistryImpl> disposalCallbacks;
 
-    private static class MapBackedInvocationHandler implements InvocationHandler {
-
-        private Map<Method, Object> methods;
-
-        public MapBackedInvocationHandler(Map<Method, Object> methods) {
-            this.methods = methods;
-        }
-
-        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-            return methods.get(method);
-        }
-
-    }
-
     @Override
     public void run() {
         java.lang.ref.Reference<? extends Object> ref = queue.poll();
@@ -169,39 +138,72 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
     private ServiceRegistration jobRegistration;
 
     private ServiceRegistration configPrinterRegistration;
+    
+    // Use threadlocal to count recursive invocations and break recursing if a max. limit is reached (to avoid cyclic dependencies)
+    private static final int MAX_RECURSIVE_INOVCATIONS = 20;
 
-    @SuppressWarnings("unchecked")
-    public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
-        Model modelAnnotation = type.getAnnotation(Model.class);
-        if (modelAnnotation == null) {
-            return null;
+    private static class ThreadInvocationCounter {
+        private int count;
+        public boolean isMaximumReached() {
+            return count >= MAX_RECURSIVE_INOVCATIONS;
         }
-        boolean isAdaptable = false;
-
-        Class<?>[] declaredAdaptable = modelAnnotation.adaptables();
-        for (Class<?> clazz : declaredAdaptable) {
-            if (clazz.isInstance(adaptable)) {
-                isAdaptable = true;
-            }
+        public void increase() {
+            this.count++;
         }
-        if (!isAdaptable) {
-            return null;
+        public void decrease() {
+            this.count--;
         }
+    }
 
-        if (type.isInterface()) {
-            InvocationHandler handler = createInvocationHandler(adaptable, type, modelAnnotation);
-            if (handler != null) {
-                return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, handler);
-            } else {
+    private static final ThreadLocal<ThreadInvocationCounter> INVOCATION_COUNT_THREADLOCAL = new ThreadLocal<ModelAdapterFactory.ThreadInvocationCounter>() {
+        @Override
+        protected ThreadInvocationCounter initialValue() {
+            return new ThreadInvocationCounter();
+        }
+    };
+
+    @SuppressWarnings("unchecked")
+    public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
+        if (INVOCATION_COUNT_THREADLOCAL.get().isMaximumReached()) {
+            log.error("Adapting {} to {} failed, too much recursive invocations (>={}).",
+                    new Object[] { adaptable, type, MAX_RECURSIVE_INOVCATIONS });
+            return null;
+        };
+        INVOCATION_COUNT_THREADLOCAL.get().increase();
+        try {
+            Model modelAnnotation = type.getAnnotation(Model.class);
+            if (modelAnnotation == null) {
                 return null;
             }
-        } else {
-            try {
-                return createObject(adaptable, type, modelAnnotation);
-            } catch (Exception e) {
-                log.error("unable to create object", e);
+            boolean isAdaptable = false;
+
+            Class<?>[] declaredAdaptable = modelAnnotation.adaptables();
+            for (Class<?> clazz : declaredAdaptable) {
+                if (clazz.isInstance(adaptable)) {
+                    isAdaptable = true;
+                }
+            }
+            if (!isAdaptable) {
                 return null;
             }
+
+            if (type.isInterface()) {
+                InvocationHandler handler = createInvocationHandler(adaptable, type, modelAnnotation);
+                if (handler != null) {
+                    return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, handler);
+                } else {
+                    return null;
+                }
+            } else {
+                try {
+                    return createObject(adaptable, type, modelAnnotation);
+                } catch (Exception e) {
+                    log.error("unable to create object", e);
+                    return null;
+                }
+            }
+        } finally {
+            INVOCATION_COUNT_THREADLOCAL.get().decrease();
         }
     }
 
@@ -276,14 +278,28 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
             return setMethod((Method) element, methods, value);
         }
     }
+    
+    private static class SetConstructorParameterCallback implements InjectCallback {
+
+        private final List<Object> parameterValues;
 
-    private boolean injectFieldOrMethod(final AnnotatedElement element, final Object adaptable, final Type type,
+        private SetConstructorParameterCallback(List<Object> parameterValues) {
+            this.parameterValues = parameterValues;
+        }
+
+        @Override
+        public boolean inject(AnnotatedElement element, Object value) {
+            return setConstructorParameter((ConstructorParameter)element, parameterValues, value);
+        }
+    }
+
+    private boolean injectElement(final AnnotatedElement element, final Object adaptable, final Type type,
             final Model modelAnnotation, final DisposalCallbackRegistry registry, InjectCallback callback) {
 
         InjectAnnotationProcessor annotationProcessor = null;
         String source = getSource(element);
         boolean wasInjectionSuccessful = false;
-        
+
         // find an appropriate annotation processor
         for (InjectAnnotationProcessorFactory factory : sortedInjectAnnotationProcessorFactories) {
             annotationProcessor = factory.createAnnotationProcessor(adaptable, element);
@@ -292,16 +308,19 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
             }
         }
 
+        String name = getName(element, annotationProcessor);
+        Object injectionAdaptable = getAdaptable(adaptable, element, annotationProcessor);
+
         // find the right injector
         for (Injector injector : sortedInjectors) {
             if (source == null || source.equals(injector.getName())) {
-                String name = getName(element, annotationProcessor);
-                Object injectionAdaptable = getAdaptable(adaptable, element, annotationProcessor);
-                if (injectionAdaptable != null) {
-                    Object value = injector.getValue(injectionAdaptable, name, type, element, registry);
-                    if (callback.inject(element, value)) {
-                        wasInjectionSuccessful = true;
-                        break;
+                if (name != null || injector instanceof AcceptsNullName) {
+                    if (injectionAdaptable != null) {
+                        Object value = injector.getValue(injectionAdaptable, name, type, element, registry);
+                        if (callback.inject(element, value)) {
+                            wasInjectionSuccessful = true;
+                            break;
+                        }
                     }
                 }
             }
@@ -324,12 +343,13 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
         SetMethodsCallback callback = new SetMethodsCallback(methods);
         MapBackedInvocationHandler handler = new MapBackedInvocationHandler(methods);
 
-        DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(handler);
+        DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl();
+        registerCallbackRegistry(handler, registry);
         Set<Method> requiredMethods = new HashSet<Method>();
 
         for (Method method : injectableMethods) {
             Type returnType = mapPrimitiveClasses(method.getGenericReturnType());
-            if (!injectFieldOrMethod(method, adaptable, returnType, modelAnnotation, registry, callback)) {
+            if (!injectElement(method, adaptable, returnType, modelAnnotation, registry, callback)) {
                 requiredMethods.add(method);
             }
         }
@@ -341,11 +361,9 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
         return handler;
     }
 
-    private DisposalCallbackRegistryImpl createAndRegisterCallbackRegistry(Object object) {
+    private void registerCallbackRegistry(Object object, DisposalCallbackRegistryImpl registry) {
         PhantomReference<Object> reference = new PhantomReference<Object>(object, queue);
-        DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl();
         disposalCallbacks.put(reference, registry);
-        return registry;
     }
 
     private String getSource(AnnotatedElement element) {
@@ -380,60 +398,52 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
         return null;
     }
 
-    @SuppressWarnings("unchecked")
     private <AdapterType> AdapterType createObject(Object adaptable, Class<AdapterType> type, Model modelAnnotation)
             throws InstantiationException, InvocationTargetException, IllegalAccessException {
-        Set<Field> injectableFields = collectInjectableFields(type);
+        DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl();
 
-        Constructor<?>[] constructors = type.getConstructors();
-        if (constructors.length == 0) {
-            log.warn("Model class {} does not have a public constructor.", type.getName());
+        Constructor<AdapterType> constructorToUse = getBestMatchingConstructor(adaptable, type);
+        if (constructorToUse == null) {
+            log.warn("Model class {} does not have a usable constructor", type.getName());
             return null;
         }
 
-        // sort the constructor list in order from most params to least params
-        Arrays.sort(constructors, new ParameterCountComparator());
-
-        Constructor<AdapterType> constructorToUse = null;
-        boolean constructorHasParam = false;
-        for (Constructor<?> constructor : constructors) {
-            final Class<?>[] paramTypes = constructor.getParameterTypes();
-            if (paramTypes.length == 1) {
-                Class<?> paramType = constructor.getParameterTypes()[0];
-                if (paramType.isInstance(adaptable)) {
-                    constructorToUse = (Constructor<AdapterType>) constructor;
-                    constructorHasParam = true;
-                    break;
-                }
-            }
-
-            if (constructor.getParameterTypes().length == 0) {
-                constructorToUse = (Constructor<AdapterType>) constructor;
-                constructorHasParam = false;
-                break;
+        final AdapterType object;
+        if (constructorToUse.getParameterTypes().length == 0) {
+            // no parameters for constructor injection? instantiate it right away
+            object = constructorToUse.newInstance();
+        } else {
+            // instantiate with constructor injection
+            // if this fails, make sure resources that may be claimed by injectors are cleared up again
+            try {
+                object = newInstanceWithConstructorInjection(constructorToUse, adaptable, type, modelAnnotation, registry);
+            } catch (InstantiationException ex) {
+                registry.onDisposed();
+                throw ex;
+            } catch (InvocationTargetException ex) {
+                registry.onDisposed();
+                throw ex;
+            } catch (IllegalAccessException ex) {
+                registry.onDisposed();
+                throw ex;
             }
         }
 
-        if (constructorToUse == null) {
-            log.warn("Model class {} does not have a usable constructor", type.getName());
+        if (object == null) {
+            registry.onDisposed();
             return null;
         }
 
-        final AdapterType object;
-        if (constructorHasParam) {
-            object = constructorToUse.newInstance(adaptable);
-        } else {
-            object = constructorToUse.newInstance();
-        }
-        InjectCallback callback = new SetFieldCallback(object);
+        registerCallbackRegistry(object, registry);
 
-        DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(object);
+        InjectCallback callback = new SetFieldCallback(object);
 
         Set<Field> requiredFields = new HashSet<Field>();
 
+        Set<Field> injectableFields = collectInjectableFields(type);
         for (Field field : injectableFields) {
             Type fieldType = mapPrimitiveClasses(field.getGenericType());
-            if (!injectFieldOrMethod(field, adaptable, fieldType, modelAnnotation, registry, callback)) {
+            if (!injectElement(field, adaptable, fieldType, modelAnnotation, registry, callback)) {
                 requiredFields.add(field);
             }
         }
@@ -453,6 +463,64 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
 
     }
 
+    /**
+     * Gets best matching constructor for constructor injection - or default constructor if none is found.
+     * @param adaptable Adaptable instance
+     * @param type Model type
+     * @return Constructor or null if none found
+     */
+    @SuppressWarnings("unchecked")
+    private <AdapterType> Constructor<AdapterType> getBestMatchingConstructor(Object adaptable, Class<AdapterType> type) {
+        Constructor<?>[] constructors = type.getConstructors();
+
+        // sort the constructor list in order from most params to least params, and constructors with @Inject annotation first
+        Arrays.sort(constructors, new ParameterCountInjectComparator());
+
+        for (Constructor<?> constructor : constructors) {
+            // first try to find the constructor with most parameters and @Inject annotation
+            if (constructor.isAnnotationPresent(Inject.class)) {
+                return (Constructor<AdapterType>) constructor;
+            }
+            // compatibility mode for sling models implementation <= 1.0.6:
+            // support constructor without @Inject if it has exactly one parameter matching the adaptable class
+            final Class<?>[] paramTypes = constructor.getParameterTypes();
+            if (paramTypes.length == 1) {
+                Class<?> paramType = constructor.getParameterTypes()[0];
+                if (paramType.isInstance(adaptable)) {
+                    return (Constructor<AdapterType>) constructor;
+                }
+            }
+            // if no constructor for injection found use public constructor without any params
+            if (constructor.getParameterTypes().length == 0) {
+                return (Constructor<AdapterType>) constructor;
+            }
+        }
+        return null;
+    }
+
+    private <AdapterType> AdapterType newInstanceWithConstructorInjection(Constructor<AdapterType> constructor, Object adaptable,
+            Class<AdapterType> type, Model modelAnnotation, DisposalCallbackRegistry registry)
+            throws InstantiationException, InvocationTargetException, IllegalAccessException {
+        Set<ConstructorParameter> requiredParameters = new HashSet<ConstructorParameter>();
+        Type[] parameterTypes = constructor.getGenericParameterTypes();
+        List<Object> paramValues = new ArrayList<Object>(Arrays.asList(new Object[parameterTypes.length]));
+        InjectCallback callback = new SetConstructorParameterCallback(paramValues);
+
+        for (int i = 0; i < parameterTypes.length; i++) {
+            Type genericType = mapPrimitiveClasses(parameterTypes[i]);
+            ConstructorParameter constructorParameter = new ConstructorParameter(
+                    constructor.getParameterAnnotations()[i], constructor.getParameterTypes()[i], genericType, i);
+            if (!injectElement(constructorParameter, adaptable, genericType, modelAnnotation, registry, callback)) {
+                requiredParameters.add(constructorParameter);
+            }
+        }
+        if (!requiredParameters.isEmpty()) {
+            log.warn("Required constructor parameters {} on model class {} were not able to be injected.", requiredParameters, type);
+            return null;
+        }
+        return constructor.newInstance(paramValues.toArray(new Object[paramValues.size()]));
+    }
+
     private boolean isOptional(AnnotatedElement point, Model modelAnnotation, InjectAnnotationProcessor annotationProcessor) {
         if (annotationProcessor != null) {
             Boolean isOptional = annotationProcessor.isOptional();
@@ -582,6 +650,9 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
             return getNameFromMethod((Method) element);
         } else if (element instanceof Field) {
             return getNameFromField((Field) element);
+        } else if (element instanceof ConstructorParameter) {
+            // implicit name not supported for constructor parameters - but do not throw exception because class-based injection is still possible
+            return null;
         } else {
             throw new IllegalArgumentException("The given element must be either method or field but is " + element);
         }
@@ -702,6 +773,27 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
         }
     }
 
+    private static boolean setConstructorParameter(ConstructorParameter constructorParameter, List<Object> parameterValues, Object value) {
+        if (value != null && constructorParameter.getType() instanceof Class<?>) {
+            Class<?> requestedType = (Class<?>)constructorParameter.getType();
+            if (!isAcceptableType(requestedType, constructorParameter.getGenericType(), value)) {
+                if (value instanceof Adaptable) {
+                    value = ((Adaptable) value).adaptTo(requestedType);
+                    if (value == null) {
+                        return false;
+                    }
+                }
+                else {
+                    return false;
+                }
+            }
+            parameterValues.set(constructorParameter.getParameterIndex(), value);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     private static boolean isAcceptableType(Class<?> type, Type genericType, Object value) {
         if (type.isInstance(value)) {
             if ((type == Collection.class || type == List.class) && genericType instanceof ParameterizedType &&
diff --git a/src/main/java/org/apache/sling/models/impl/ParameterCountInjectComparator.java b/src/main/java/org/apache/sling/models/impl/ParameterCountInjectComparator.java
new file mode 100644
index 0000000..afb8410
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/ParameterCountInjectComparator.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.sling.models.impl;
+
+import java.lang.reflect.Constructor;
+import java.util.Comparator;
+
+import javax.inject.Inject;
+
+/**
+ * Comparator which sorts constructors by the number of parameters
+ * in reverse order (most params to least params).
+ * If two constructors have the same number of parameters constructors with
+ * @Inject annotation are sorted first.
+ */
+class ParameterCountInjectComparator implements Comparator<Constructor<?>> {
+
+    @Override
+    public int compare(Constructor<?> o1, Constructor<?> o2) {
+        int result = compareParameterCount(o2.getParameterTypes().length, o1.getParameterTypes().length);
+        if (result==0) {
+            return compareInjectAnnotation(o1, o2);
+        }
+        else {
+            return result;
+        }
+    }
+
+    private int compareParameterCount(int x, int y) {
+        return (x < y) ? -1 : ((x == y) ? 0 : 1);
+    }
+
+    private int compareInjectAnnotation(Constructor<?> o1, Constructor<?> o2) {
+        boolean hasInject1 = o1.isAnnotationPresent(Inject.class);
+        boolean hasInject2 = o2.isAnnotationPresent(Inject.class);
+        if (hasInject1 && !hasInject2) {
+            return -1;
+        }
+        else if (hasInject2 && !hasInject1) {
+            return 1;
+        }
+        else {
+            return 0;
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java
index fb85dc0..0d1b4e8 100644
--- a/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java
+++ b/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java
@@ -38,6 +38,7 @@ import org.apache.sling.models.annotations.Filter;
 import org.apache.sling.models.annotations.injectorspecific.OSGiService;
 import org.apache.sling.models.spi.DisposalCallback;
 import org.apache.sling.models.spi.DisposalCallbackRegistry;
+import org.apache.sling.models.spi.AcceptsNullName;
 import org.apache.sling.models.spi.Injector;
 import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
@@ -53,7 +54,7 @@ import org.slf4j.LoggerFactory;
 @Component
 @Service
 @Property(name = Constants.SERVICE_RANKING, intValue = 5000)
-public class OSGiServiceInjector implements Injector, InjectAnnotationProcessorFactory {
+public class OSGiServiceInjector implements Injector, InjectAnnotationProcessorFactory, AcceptsNullName {
 
     private static final Logger log = LoggerFactory.getLogger(OSGiServiceInjector.class);
 
diff --git a/src/main/java/org/apache/sling/models/impl/injectors/SelfInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/SelfInjector.java
new file mode 100644
index 0000000..5787b46
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/injectors/SelfInjector.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sling.models.impl.injectors;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+import org.apache.sling.models.spi.DisposalCallbackRegistry;
+import org.apache.sling.models.spi.AcceptsNullName;
+import org.apache.sling.models.spi.Injector;
+import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor;
+import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
+import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
+import org.osgi.framework.Constants;
+
+/**
+ * Injects the adaptable object itself.
+ */
+@Component
+@Service
+@Property(name = Constants.SERVICE_RANKING, intValue = 100)
+public class SelfInjector implements Injector, InjectAnnotationProcessorFactory, AcceptsNullName {
+
+    @Override
+    public String getName() {
+        return "self";
+    }
+
+    public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element,
+            DisposalCallbackRegistry callbackRegistry) {
+        // if the @Self annotation is present return the adaptable to be inserted directly or to be adapted from
+        if (element.isAnnotationPresent(Self.class)) {
+            return adaptable;
+        }
+        // otherwise apply class-based injection only if class matches or is a superclass
+        else if (type instanceof Class<?>) {
+            Class<?> requestedClass = (Class<?>)type;
+            if (requestedClass.isAssignableFrom(adaptable.getClass())) {
+                return adaptable;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) {
+        // check if the element has the expected annotation
+        Self annotation = element.getAnnotation(Self.class);
+        if (annotation != null) {
+            return new SelfAnnotationProcessor(annotation);
+        }
+        return null;
+    }
+
+    private static class SelfAnnotationProcessor extends AbstractInjectAnnotationProcessor {
+
+        private final Self annotation;
+
+        public SelfAnnotationProcessor(Self annotation) {
+            this.annotation = annotation;
+        }
+
+        @Override
+        public Boolean isOptional() {
+            return annotation.optional();
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/impl/ConstructorTest.java b/src/test/java/org/apache/sling/models/impl/ConstructorTest.java
index 0f3da57..6720a16 100644
--- a/src/test/java/org/apache/sling/models/impl/ConstructorTest.java
+++ b/src/test/java/org/apache/sling/models/impl/ConstructorTest.java
@@ -21,11 +21,14 @@ import static org.mockito.Mockito.*;
 
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.models.impl.injectors.RequestAttributeInjector;
+import org.apache.sling.models.impl.injectors.SelfInjector;
 import org.apache.sling.models.testmodels.classes.InvalidConstructorModel;
 import org.apache.sling.models.testmodels.classes.SuperclassConstructorModel;
 import org.apache.sling.models.testmodels.classes.WithOneConstructorModel;
 import org.apache.sling.models.testmodels.classes.WithThreeConstructorsModel;
 import org.apache.sling.models.testmodels.classes.WithTwoConstructorsModel;
+import org.apache.sling.models.testmodels.classes.constructorinjection.NoNameModel;
+import org.apache.sling.models.testmodels.classes.constructorinjection.WithThreeConstructorsOneInjectModel;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,6 +38,7 @@ import org.osgi.framework.BundleContext;
 import org.osgi.service.component.ComponentContext;
 
 @RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
 public class ConstructorTest {
 
     @Mock
@@ -48,15 +52,21 @@ public class ConstructorTest {
     @Mock
     private SlingHttpServletRequest request;
 
+    private static final int INT_VALUE = 42;
+
+    private static final String STRING_VALUE = "myValue";
+
     @Before
     public void setup() {
         when(componentCtx.getBundleContext()).thenReturn(bundleContext);
 
-        when(request.getAttribute("attribute")).thenReturn(42);
+        when(request.getAttribute("attribute")).thenReturn(INT_VALUE);
+        when(request.getAttribute("attribute2")).thenReturn(STRING_VALUE);
 
         factory = new ModelAdapterFactory();
         factory.activate(componentCtx);
         factory.bindInjector(new RequestAttributeInjector(), new ServicePropertiesMap(1, 1));
+        factory.bindInjector(new SelfInjector(), new ServicePropertiesMap(2, 2));
     }
 
     @Test
@@ -64,7 +74,7 @@ public class ConstructorTest {
         WithOneConstructorModel model = factory.getAdapter(request, WithOneConstructorModel.class);
         assertNotNull(model);
         assertEquals(request, model.getRequest());
-        assertEquals(42, model.getAttribute());
+        assertEquals(INT_VALUE, model.getAttribute());
     }
 
     @Test
@@ -72,7 +82,7 @@ public class ConstructorTest {
         WithThreeConstructorsModel model = factory.getAdapter(request, WithThreeConstructorsModel.class);
         assertNotNull(model);
         assertEquals(request, model.getRequest());
-        assertEquals(42, model.getAttribute());
+        assertEquals(INT_VALUE, model.getAttribute());
     }
 
     @Test
@@ -80,7 +90,7 @@ public class ConstructorTest {
         WithTwoConstructorsModel model = factory.getAdapter(request, WithTwoConstructorsModel.class);
         assertNotNull(model);
         assertEquals(request, model.getRequest());
-        assertEquals(42, model.getAttribute());
+        assertEquals(INT_VALUE, model.getAttribute());
     }
 
     @Test
@@ -88,7 +98,7 @@ public class ConstructorTest {
         SuperclassConstructorModel model = factory.getAdapter(request, SuperclassConstructorModel.class);
         assertNotNull(model);
         assertEquals(request, model.getRequest());
-        assertEquals(42, model.getAttribute());
+        assertEquals(INT_VALUE, model.getAttribute());
     }
 
     @Test
@@ -97,4 +107,23 @@ public class ConstructorTest {
         assertNull(model);
     }
 
+    /**
+     * Test model object with three constructors, and make sure that one with @Inject is picked for instantiation.
+     * Test mixing of constructor injection and field injection as well.
+     */
+    @Test
+    public void testThreeConstructorsOneInjectInjection() {
+        WithThreeConstructorsOneInjectModel model = factory.getAdapter(request,
+                WithThreeConstructorsOneInjectModel.class);
+        assertNotNull(model);
+        assertNull(model.getRequest());
+        assertEquals(INT_VALUE, model.getAttribute());
+        assertEquals(STRING_VALUE, model.getAttribute2());
+    }
+
+    @Test
+    public void testNoNameModel() {
+        NoNameModel model = factory.getAdapter(request, NoNameModel.class);
+        assertNull(model);
+    }
 }
diff --git a/src/test/java/org/apache/sling/models/impl/DefaultTest.java b/src/test/java/org/apache/sling/models/impl/DefaultTest.java
index aa6de10..01a333f 100644
--- a/src/test/java/org/apache/sling/models/impl/DefaultTest.java
+++ b/src/test/java/org/apache/sling/models/impl/DefaultTest.java
@@ -39,6 +39,7 @@ import org.osgi.framework.BundleContext;
 import org.osgi.service.component.ComponentContext;
 
 @RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
 public class DefaultTest {
 
     @Mock
@@ -58,7 +59,7 @@ public class DefaultTest {
     }
 
     @Test
-    public void testDefaultStringValue() {
+    public void testDefaultStringValueField() {
         ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap());
 
         Resource res = mock(Resource.class);
@@ -71,7 +72,7 @@ public class DefaultTest {
     }
 
     @Test
-    public void testDefaultStringValueOnInterface() {
+    public void testDefaultStringValueOnInterfaceField() {
         ValueMap vm = new ValueMapDecorator(Collections.<String, Object>singletonMap("first", "first value"));
 
         Resource res = mock(Resource.class);
@@ -85,7 +86,7 @@ public class DefaultTest {
 
 
     @Test
-    public void testDefaultPrimitives() {
+    public void testDefaultPrimitivesField() {
         ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap());
 
         Resource res = mock(Resource.class);
@@ -103,7 +104,7 @@ public class DefaultTest {
     }
 
     @Test
-    public void testDefaultWrappers() {
+    public void testDefaultWrappersField() {
         ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap());
 
         Resource res = mock(Resource.class);
@@ -119,4 +120,57 @@ public class DefaultTest {
         assertEquals(Long.valueOf(1L), model.getLongWrapperProperty());
         assertArrayEquals(new Long[] { Long.valueOf(1L), Long.valueOf(1L) }, model.getLongWrapperArrayProperty());
     }
+
+    @Test
+    public void testDefaultStringValueConstructor() {
+        ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap());
+
+        Resource res = mock(Resource.class);
+        when(res.adaptTo(ValueMap.class)).thenReturn(vm);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.DefaultStringModel model
+                = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.DefaultStringModel.class);
+        assertNotNull(model);
+        assertEquals("firstDefault", model.getFirstProperty());
+        assertEquals(2, model.getSecondProperty().length);
+    }
+
+    @Test
+    public void testDefaultPrimitivesConstructor() {
+        ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap());
+
+        Resource res = mock(Resource.class);
+        when(res.adaptTo(ValueMap.class)).thenReturn(vm);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.DefaultPrimitivesModel model
+                = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.DefaultPrimitivesModel.class);
+        assertNotNull(model);
+
+        assertEquals(true, model.getBooleanProperty());
+        // we need to wait for JUnit 4.12 for this assertArrayEquals to be working on primitive boolean arrays, https://github.com/junit-team/junit/issues/86!
+        assertTrue(Arrays.equals(new boolean[] { true, true }, model.getBooleanArrayProperty()));
+
+        assertEquals(1L, model.getLongProperty());
+        assertArrayEquals(new long[] { 1L, 1L }, model.getLongArrayProperty());
+    }
+
+    @Test
+    public void testDefaultWrappersConstructor() {
+        ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap());
+
+        Resource res = mock(Resource.class);
+        when(res.adaptTo(ValueMap.class)).thenReturn(vm);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.DefaultWrappersModel model
+                = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.DefaultWrappersModel.class);
+        assertNotNull(model);
+
+        assertEquals(Boolean.valueOf(true), model.getBooleanWrapperProperty());
+        // we need to wait for JUnit 4.12 for this assertArrayEquals to be working on primitive boolean arrays, https://github.com/junit-team/junit/issues/86!
+        assertTrue(Arrays.equals(new Boolean[] { Boolean.TRUE, Boolean.TRUE }, model.getBooleanWrapperArrayProperty()));
+
+        assertEquals(Long.valueOf(1L), model.getLongWrapperProperty());
+        assertArrayEquals(new Long[] { Long.valueOf(1L), Long.valueOf(1L) }, model.getLongWrapperArrayProperty());
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java b/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java
index 8d8fea7..2a82d7d 100644
--- a/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java
+++ b/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java
@@ -50,6 +50,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
 public class InjectorSpecificAnnotationTest {
 
     @Mock
@@ -110,7 +111,7 @@ public class InjectorSpecificAnnotationTest {
     }
 
     @Test
-    public void testSimpleValueModel() {
+    public void testSimpleValueModelField() {
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("first", "first-value");
         map.put("second", "second-value");
@@ -127,7 +128,7 @@ public class InjectorSpecificAnnotationTest {
     }
 
     @Test
-    public void testOrderForValueAnnotation() {
+    public void testOrderForValueAnnotationField() {
         // make sure that that the correct injection is used
         // make sure that log is adapted from value map
         // and not coming from request attribute
@@ -149,7 +150,7 @@ public class InjectorSpecificAnnotationTest {
     }
 
     @Test
-    public void testOSGiService() throws InvalidSyntaxException {
+    public void testOSGiServiceField() throws InvalidSyntaxException {
         ServiceReference ref = mock(ServiceReference.class);
         Logger log = mock(Logger.class);
         when(bundleContext.getServiceReferences(Logger.class.getName(), null)).thenReturn(
@@ -162,7 +163,7 @@ public class InjectorSpecificAnnotationTest {
     }
 
     @Test
-    public void testScriptVariable() throws InvalidSyntaxException {
+    public void testScriptVariableField() throws InvalidSyntaxException {
         SlingBindings bindings = new SlingBindings();
         SlingScriptHelper helper = mock(SlingScriptHelper.class);
         bindings.setSling(helper);
@@ -174,7 +175,7 @@ public class InjectorSpecificAnnotationTest {
     }
 
     @Test
-    public void testRequestAttribute() throws InvalidSyntaxException {
+    public void testRequestAttributeField() throws InvalidSyntaxException {
         Object attribute = new Object();
         when(request.getAttribute("attribute")).thenReturn(attribute);
 
@@ -184,7 +185,7 @@ public class InjectorSpecificAnnotationTest {
     }
 
     @Test
-    public void testChildResource() {
+    public void testChildResourceField() {
         Resource res = mock(Resource.class);
         Resource child = mock(Resource.class);
         when(res.getChild("child1")).thenReturn(child);
@@ -194,4 +195,97 @@ public class InjectorSpecificAnnotationTest {
         assertNotNull("Could not instanciate model", model);
         assertEquals(child, model.getChildResource());
     }
+
+    @Test
+    public void testSimpleValueModelConstructor() {
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put("first", "first-value");
+        map.put("second", "second-value");
+        ValueMap vm = new ValueMapDecorator(map);
+
+        Resource res = mock(Resource.class);
+        when(res.adaptTo(ValueMap.class)).thenReturn(vm);
+        when(request.getResource()).thenReturn(res);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class);
+        assertNotNull("Could not instanciate model", model);
+        assertEquals("first-value", model.getFirst());
+        assertEquals("second-value", model.getSecond());
+    }
+
+    @Test
+    public void testOrderForValueAnnotationConstructor() {
+        // make sure that that the correct injection is used
+        // make sure that log is adapted from value map
+        // and not coming from request attribute
+        Logger logFromValueMap = LoggerFactory.getLogger(this.getClass());
+
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put("first", "first-value");
+        map.put("log", logFromValueMap);
+        ValueMap vm = new ValueMapDecorator(map);
+
+        Resource res = mock(Resource.class);
+        when(res.adaptTo(ValueMap.class)).thenReturn(vm);
+        when(request.getResource()).thenReturn(res);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class);
+        assertNotNull("Could not instanciate model", model);
+        assertEquals("first-value", model.getFirst());
+        assertEquals(logFromValueMap, model.getLog());
+    }
+
+    @Test
+    public void testOSGiServiceConstructor() throws InvalidSyntaxException {
+        ServiceReference ref = mock(ServiceReference.class);
+        Logger log = mock(Logger.class);
+        when(bundleContext.getServiceReferences(Logger.class.getName(), null)).thenReturn(
+                new ServiceReference[] { ref });
+        when(bundleContext.getService(ref)).thenReturn(log);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class);
+        assertNotNull("Could not instanciate model", model);
+        assertEquals(log, model.getService());
+    }
+
+    @Test
+    public void testScriptVariableConstructor() throws InvalidSyntaxException {
+        SlingBindings bindings = new SlingBindings();
+        SlingScriptHelper helper = mock(SlingScriptHelper.class);
+        bindings.setSling(helper);
+        when(request.getAttribute(SlingBindings.class.getName())).thenReturn(bindings);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class);
+        assertNotNull("Could not instanciate model", model);
+        assertEquals(helper, model.getHelper());
+    }
+
+    @Test
+    public void testRequestAttributeConstructor() throws InvalidSyntaxException {
+        Object attribute = new Object();
+        when(request.getAttribute("attribute")).thenReturn(attribute);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class);
+        assertNotNull("Could not instanciate model", model);
+        assertEquals(attribute, model.getRequestAttribute());
+    }
+
+    @Test
+    public void testChildResourceConstructor() {
+        Resource res = mock(Resource.class);
+        Resource child = mock(Resource.class);
+        when(res.getChild("child1")).thenReturn(child);
+        when(request.getResource()).thenReturn(res);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class);
+        assertNotNull("Could not instanciate model", model);
+        assertEquals(child, model.getChildResource());
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java b/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java
index 1058323..df09c52 100644
--- a/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java
+++ b/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java
@@ -47,6 +47,7 @@ import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentContext;
 
 @RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
 public class OSGiInjectionTest {
     private ModelAdapterFactory factory;
 
@@ -75,7 +76,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testSimpleOSGiModel() throws Exception {
+    public void testSimpleOSGiModelField() throws Exception {
         ServiceReference ref = mock(ServiceReference.class);
         ServiceInterface service = mock(ServiceInterface.class);
         when(bundleContext.getServiceReferences(ServiceInterface.class.getName(), null)).thenReturn(
@@ -93,7 +94,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testRequestOSGiModel() throws Exception {
+    public void testRequestOSGiModelField() throws Exception {
         ServiceInterface service = mock(ServiceInterface.class);
 
         SlingHttpServletRequest request = mock(SlingHttpServletRequest.class);
@@ -114,7 +115,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testListOSGiModel() throws Exception {
+    public void testListOSGiModelField() throws Exception {
         ServiceReference ref1 = mock(ServiceReference.class);
         ServiceInterface service1 = mock(ServiceInterface.class);
         when(bundleContext.getService(ref1)).thenReturn(service1);
@@ -138,7 +139,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testArrayOSGiModel() throws Exception {
+    public void testArrayOSGiModelField() throws Exception {
         ServiceReference ref1 = mock(ServiceReference.class);
         ServiceInterface service1 = mock(ServiceInterface.class);
         when(bundleContext.getService(ref1)).thenReturn(service1);
@@ -162,7 +163,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testOptionalArrayOSGiModel() throws Exception {
+    public void testOptionalArrayOSGiModelField() throws Exception {
 
         Resource res = mock(Resource.class);
 
@@ -174,7 +175,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testOptionalListOSGiModel() throws Exception {
+    public void testOptionalListOSGiModelField() throws Exception {
         Resource res = mock(Resource.class);
 
         OptionalListOSGiModel model = factory.getAdapter(res, OptionalListOSGiModel.class);
@@ -185,7 +186,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testCollectionOSGiModel() throws Exception {
+    public void testCollectionOSGiModelField() throws Exception {
         ServiceReference ref1 = mock(ServiceReference.class);
         ServiceInterface service1 = mock(ServiceInterface.class);
         when(bundleContext.getService(ref1)).thenReturn(service1);
@@ -210,7 +211,7 @@ public class OSGiInjectionTest {
     }
 
     @Test
-    public void testSetOSGiModel() throws Exception {
+    public void testSetOSGiModelField() throws Exception {
         ServiceReference ref1 = mock(ServiceReference.class);
         ServiceInterface service1 = mock(ServiceInterface.class);
         when(bundleContext.getService(ref1)).thenReturn(service1);
@@ -232,4 +233,49 @@ public class OSGiInjectionTest {
         verify(bundleContext).getBundles();
         verifyNoMoreInteractions(res, bundleContext);
     }
+
+    @Test
+    public void testSimpleOSGiModelConstructor() throws Exception {
+        ServiceReference ref = mock(ServiceReference.class);
+        ServiceInterface service = mock(ServiceInterface.class);
+        when(bundleContext.getServiceReferences(ServiceInterface.class.getName(), null)).thenReturn(
+                new ServiceReference[] { ref });
+        when(bundleContext.getService(ref)).thenReturn(service);
+
+        Resource res = mock(Resource.class);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.SimpleOSGiModel model
+                = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.SimpleOSGiModel.class);
+        assertNotNull(model);
+        assertNotNull(model.getService());
+        assertEquals(service, model.getService());
+
+        verifyNoMoreInteractions(res);
+    }
+
+    @Test
+    public void testListOSGiModelConstructor() throws Exception {
+        ServiceReference ref1 = mock(ServiceReference.class);
+        ServiceInterface service1 = mock(ServiceInterface.class);
+        when(bundleContext.getService(ref1)).thenReturn(service1);
+        ServiceReference ref2 = mock(ServiceReference.class);
+        ServiceInterface service2 = mock(ServiceInterface.class);
+        when(bundleContext.getService(ref2)).thenReturn(service2);
+
+        when(bundleContext.getServiceReferences(ServiceInterface.class.getName(), null)).thenReturn(
+                new ServiceReference[] { ref1, ref2 });
+
+        Resource res = mock(Resource.class);
+
+        org.apache.sling.models.testmodels.classes.constructorinjection.ListOSGiModel model
+                = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.ListOSGiModel.class);
+        assertNotNull(model);
+        assertNotNull(model.getServices());
+        assertEquals(2, model.getServices().size());
+        assertEquals(service1, model.getServices().get(0));
+        assertEquals(service2, model.getServices().get(1));
+
+        verifyNoMoreInteractions(res);
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java b/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java
index 9f495e3..91dd851 100644
--- a/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java
+++ b/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java
@@ -33,6 +33,7 @@ import org.osgi.framework.BundleContext;
 import org.osgi.service.component.ComponentContext;
 
 @RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
 public class RequestInjectionTest {
 
     @Mock
@@ -63,10 +64,18 @@ public class RequestInjectionTest {
     }
 
     @Test
-    public void testNamedInjection() {
+    public void testNamedInjectionField() {
         BindingsModel model = factory.getAdapter(request, BindingsModel.class);
         assertNotNull(model.getSling());
         assertEquals(sling, model.getSling());
     }
 
+    @Test
+    public void testNamedInjectionConstructor() {
+        org.apache.sling.models.testmodels.classes.constructorinjection.BindingsModel model
+                = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.BindingsModel.class);
+        assertNotNull(model.getSling());
+        assertEquals(sling, model.getSling());
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/models/impl/SelfDependencyTest.java b/src/test/java/org/apache/sling/models/impl/SelfDependencyTest.java
new file mode 100644
index 0000000..e0e9bad
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/SelfDependencyTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.sling.models.impl;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.impl.injectors.SelfInjector;
+import org.apache.sling.models.testmodels.classes.DirectCyclicSelfDependencyModel;
+import org.apache.sling.models.testmodels.classes.IndirectCyclicSelfDependencyModelA;
+import org.apache.sling.models.testmodels.classes.SelfDependencyModelA;
+import org.apache.sling.models.testmodels.classes.SelfDependencyModelB;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.ComponentContext;
+
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
+public class SelfDependencyTest {
+
+    @Mock
+    private ComponentContext componentCtx;
+
+    @Mock
+    private BundleContext bundleContext;
+
+    private ModelAdapterFactory factory;
+
+    @Mock
+    private SlingHttpServletRequest request;
+
+    @SuppressWarnings("unchecked")
+    @Before
+    public void setup() {
+        when(componentCtx.getBundleContext()).thenReturn(bundleContext);
+        when(request.adaptTo(any(Class.class))).then(new Answer<Object>() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                Class<?> clazz = (Class<?>) invocation.getArguments()[0];
+                return factory.getAdapter(request, clazz);
+            }
+        });
+
+        factory = new ModelAdapterFactory();
+        factory.activate(componentCtx);
+        factory.bindInjector(new SelfInjector(), new ServicePropertiesMap(1, 1));
+    }
+
+    @Test
+    public void testChainedSelfDependency() {
+        SelfDependencyModelA objectA = factory.getAdapter(request, SelfDependencyModelA.class);
+        assertNotNull(objectA);
+        SelfDependencyModelB objectB = objectA.getDependencyB();
+        assertNotNull(objectB);
+        assertSame(request, objectB.getRequest());
+    }
+
+    @Test
+    public void testDirectCyclicSelfDependency() {
+        DirectCyclicSelfDependencyModel object = factory.getAdapter(request, DirectCyclicSelfDependencyModel.class);
+        assertNull(object);
+    }
+
+    @Test
+    public void testInddirectCyclicSelfDependency() {
+        IndirectCyclicSelfDependencyModelA object = factory.getAdapter(request,
+                IndirectCyclicSelfDependencyModelA.class);
+        assertNull(object);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/impl/injectors/BindingsInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/BindingsInjectorTest.java
new file mode 100644
index 0000000..fa15446
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/injectors/BindingsInjectorTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.sling.models.impl.injectors;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import javax.servlet.ServletRequest;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
+public class BindingsInjectorTest {
+
+    private BindingsInjector injector = new BindingsInjector();
+
+    @Mock
+    private ServletRequest request;
+    @Mock
+    private SlingBindings bindings;
+
+    private static final String STRING_PARAM = "param1";
+    private static final String INTEGER_PARAM = "param2";
+    private static final String CLASS_PARAM = "param3";
+    private static final String STRING_VALUE = "myValue";
+    private static final int INTEGER_VALUE = 42;
+    private static final ResourceResolver CLASS_INSTANCE = mock(ResourceResolver.class);
+
+    @Before
+    public void setUp() {
+        when(request.getAttribute(SlingBindings.class.getName())).thenReturn(bindings);
+        when(bindings.get(STRING_PARAM)).thenReturn(STRING_VALUE);
+        when(bindings.get(INTEGER_PARAM)).thenReturn(INTEGER_VALUE);
+        when(bindings.get(CLASS_PARAM)).thenReturn(CLASS_INSTANCE);
+    }
+
+    @Test
+    public void testStringParam() {
+        Object result = injector.getValue(request, STRING_PARAM, String.class, null, null);
+        assertEquals(STRING_VALUE, result);
+    }
+
+    @Test
+    public void testIntegerParam() {
+        Object result = injector.getValue(request, INTEGER_PARAM, Integer.class, null, null);
+        assertEquals(INTEGER_VALUE, result);
+    }
+
+    @Test
+    public void testClassInstance() {
+        Object result = injector.getValue(request, CLASS_PARAM, ResourceResolver.class, null, null);
+        assertSame(CLASS_INSTANCE, result);
+    }
+
+    @Test
+    public void testNonMatchingClassInstance() {
+        Object result = injector.getValue(request, CLASS_PARAM, Resource.class, null, null);
+        assertNull(result);
+    }
+
+    @Test
+    public void testNonRequestAdaptable() {
+        Object result = injector.getValue(mock(ResourceResolver.class), STRING_PARAM, String.class, null, null);
+        assertNull(result);
+    }
+
+    @Test
+    public void testRequestThatDoesNotContainBindings() {
+        when(request.getAttribute(SlingBindings.class.getName())).thenReturn(null);
+        Object result = injector.getValue(request, STRING_PARAM, String.class, null, null);
+        assertNull(result);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/impl/injectors/RequestAttributeInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/RequestAttributeInjectorTest.java
new file mode 100644
index 0000000..75ac1ca
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/injectors/RequestAttributeInjectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.sling.models.impl.injectors;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import javax.servlet.ServletRequest;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
+public class RequestAttributeInjectorTest {
+
+    private RequestAttributeInjector injector = new RequestAttributeInjector();
+    
+    @Mock
+    private ServletRequest request;
+    
+    private static final String STRING_PARAM = "param1";
+    private static final String INTEGER_PARAM = "param2";
+    private static final String CLASS_PARAM = "param3";
+    private static final String STRING_VALUE = "myValue";
+    private static final int INTEGER_VALUE = 42;
+    private static final ResourceResolver CLASS_INSTANCE = mock(ResourceResolver.class);
+    
+    @Before
+    public void setUp() {
+        when(request.getAttribute(STRING_PARAM)).thenReturn(STRING_VALUE);
+        when(request.getAttribute(INTEGER_PARAM)).thenReturn(INTEGER_VALUE);
+        when(request.getAttribute(CLASS_PARAM)).thenReturn(CLASS_INSTANCE);
+    }
+
+    @Test
+    public void testStringParam() {
+        Object result = injector.getValue(request, STRING_PARAM, String.class, null, null);
+        assertEquals(STRING_VALUE, result);
+    }
+
+    @Test
+    public void testIntegerParam() {
+        Object result = injector.getValue(request, INTEGER_PARAM, Integer.class, null, null);
+        assertEquals(INTEGER_VALUE, result);
+    }
+
+    @Test
+    public void testClassInstance() {
+        Object result = injector.getValue(request, CLASS_PARAM, ResourceResolver.class, null, null);
+        assertSame(CLASS_INSTANCE, result);
+    }
+
+    @Test
+    public void testNonMatchingClassInstance() {
+        Object result = injector.getValue(request, CLASS_PARAM, Resource.class, null, null);
+        assertNull(result);
+    }
+
+    @Test
+    public void testNonRequestAdaptable() {
+        Object result = injector.getValue(mock(ResourceResolver.class), STRING_PARAM, String.class, null, null);
+        assertNull(result);
+    }
+}
diff --git a/src/test/java/org/apache/sling/models/impl/injectors/SelfInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/SelfInjectorTest.java
new file mode 100644
index 0000000..3e7251a
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/injectors/SelfInjectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.sling.models.impl.injectors;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.*;
+
+import java.lang.reflect.AnnotatedElement;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
+public class SelfInjectorTest {
+
+    private SelfInjector injector = new SelfInjector();
+    
+    @Mock
+    private SlingHttpServletRequest request;
+    @Mock
+    private AnnotatedElement annotatedElement;
+
+    @Test
+    public void testMatchingClass() {
+        Object result = injector.getValue(request, "notRelevant", SlingHttpServletRequest.class, annotatedElement, null);
+        assertSame(request, result);
+    }
+
+    @Test
+    public void testMatchingSubClass() {
+        Object result = injector.getValue(request, "notRelevant", HttpServletRequest.class, annotatedElement, null);
+        assertSame(request, result);
+    }
+
+    @Test
+    public void testNotMatchingClass() {
+        Object result = injector.getValue(request, "notRelevant", ResourceResolver.class, annotatedElement, null);
+        assertNull(result);
+    }
+
+    @Test
+    public void testWithNullName() {
+        Object result = injector.getValue(request, null, SlingHttpServletRequest.class, annotatedElement, null);
+        assertSame(request, result);
+    }
+
+    @Test
+    public void testMatchingClassWithSelfAnnotation() {
+        when(annotatedElement.isAnnotationPresent(Self.class)).thenReturn(true);
+        Object result = injector.getValue(request, "notRelevant", SlingHttpServletRequest.class, annotatedElement, null);
+        assertSame(request, result);
+    }
+
+    @Test
+    public void testNotMatchingClassWithSelfAnnotation() {
+        when(annotatedElement.isAnnotationPresent(Self.class)).thenReturn(true);
+        Object result = injector.getValue(request, "notRelevant", ResourceResolver.class, annotatedElement, null);
+        assertSame(request, result);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/impl/injectors/ValueMapInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/ValueMapInjectorTest.java
new file mode 100644
index 0000000..cf3583a
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/injectors/ValueMapInjectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.sling.models.impl.injectors;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("javadoc")
+public class ValueMapInjectorTest {
+
+    private ValueMapInjector injector = new ValueMapInjector();
+
+    @Mock
+    private ValueMap valueMap;
+
+    private static final String STRING_PARAM = "param1";
+    private static final String INTEGER_PARAM = "param2";
+    private static final String CLASS_PARAM = "param3";
+    private static final String STRING_VALUE = "myValue";
+    private static final int INTEGER_VALUE = 42;
+    private static final ResourceResolver CLASS_INSTANCE = mock(ResourceResolver.class);
+
+    @Before
+    public void setUp() {
+        when(valueMap.get(STRING_PARAM, String.class)).thenReturn(STRING_VALUE);
+        when(valueMap.get(INTEGER_PARAM, Integer.class)).thenReturn(INTEGER_VALUE);
+        when(valueMap.get(CLASS_PARAM, ResourceResolver.class)).thenReturn(CLASS_INSTANCE);
+    }
+
+    @Test
+    public void testStringParam() {
+        Object result = injector.getValue(valueMap, STRING_PARAM, String.class, null, null);
+        assertEquals(STRING_VALUE, result);
+    }
+
+    @Test
+    public void testIntegerParam() {
+        Object result = injector.getValue(valueMap, INTEGER_PARAM, Integer.class, null, null);
+        assertEquals(INTEGER_VALUE, result);
+    }
+
+    @Test
+    public void testClassInstance() {
+        Object result = injector.getValue(valueMap, CLASS_PARAM, ResourceResolver.class, null, null);
+        assertSame(CLASS_INSTANCE, result);
+    }
+
+    @Test
+    public void testNonMatchingClassInstance() {
+        Object result = injector.getValue(valueMap, CLASS_PARAM, Resource.class, null, null);
+        assertNull(result);
+    }
+
+    @Test
+    public void testNonValueMapAdaptable() {
+        Object result = injector.getValue(mock(ResourceResolver.class), STRING_PARAM, String.class, null, null);
+        assertNull(result);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/DirectCyclicSelfDependencyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/DirectCyclicSelfDependencyModel.java
new file mode 100644
index 0000000..c09603b
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/DirectCyclicSelfDependencyModel.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.sling.models.testmodels.classes;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+
+@Model(adaptables=SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class DirectCyclicSelfDependencyModel {
+
+    @Self
+    private DirectCyclicSelfDependencyModel modelSelf;
+
+    public DirectCyclicSelfDependencyModel getModelSelf() {
+        return modelSelf;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelA.java b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelA.java
new file mode 100644
index 0000000..cd329a5
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelA.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.sling.models.testmodels.classes;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+
+@Model(adaptables=SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class IndirectCyclicSelfDependencyModelA {
+
+    @Self
+    private IndirectCyclicSelfDependencyModelB dependencyB;
+
+    public IndirectCyclicSelfDependencyModelB getDependencyB() {
+        return dependencyB;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelB.java b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelB.java
new file mode 100644
index 0000000..68dfb15
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelB.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.sling.models.testmodels.classes;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+
+@Model(adaptables=SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class IndirectCyclicSelfDependencyModelB {
+
+    @Self
+    private IndirectCyclicSelfDependencyModelA dependencyA;
+
+    public IndirectCyclicSelfDependencyModelA getDependencyA() {
+        return dependencyA;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelA.java b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelA.java
new file mode 100644
index 0000000..6df4527
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelA.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.sling.models.testmodels.classes;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+
+@Model(adaptables=SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class SelfDependencyModelA {
+
+    @Self
+    private SelfDependencyModelB dependencyB;
+
+    public SelfDependencyModelB getDependencyB() {
+        return dependencyB;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelB.java b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelB.java
new file mode 100644
index 0000000..6d7757c
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelB.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.sling.models.testmodels.classes;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.Self;
+
+@Model(adaptables=SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class SelfDependencyModelB {
+
+    @Self
+    private SlingHttpServletRequest request;
+
+    public SlingHttpServletRequest getRequest() {
+        return request;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/BindingsModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/BindingsModel.java
new file mode 100644
index 0000000..595663a
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/BindingsModel.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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.models.annotations.Model;
+
+@Model(adaptables = SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class BindingsModel {
+
+    private final SlingScriptHelper sling;
+
+    @Inject
+    public BindingsModel(@Named("sling") SlingScriptHelper sling) {
+        this.sling = sling;
+    }
+
+    public SlingScriptHelper getSling() {
+        return sling;
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultPrimitivesModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultPrimitivesModel.java
new file mode 100644
index 0000000..6de8e7f
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultPrimitivesModel.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Default;
+import org.apache.sling.models.annotations.Model;
+
+@Model(adaptables = Resource.class)
+@SuppressWarnings("javadoc")
+public class DefaultPrimitivesModel {
+
+    private final boolean booleanProperty;
+    private final boolean[] booleanArrayProperty;
+    private final long longProperty;
+    private final long[] longArrayProperty;
+
+    @Inject
+    public DefaultPrimitivesModel(
+            @Default(booleanValues = true) boolean booleanProperty, 
+            @Default(booleanValues = { true, true }) boolean[] booleanArrayProperty,
+            @Default(longValues = 1L) long longProperty,
+            @Default(longValues = { 1L, 1L }) long[] longArrayProperty
+    ) {
+        this.booleanProperty = booleanProperty;
+        this.booleanArrayProperty = booleanArrayProperty;
+        this.longProperty = longProperty;
+        this.longArrayProperty = longArrayProperty;
+    }
+
+    public boolean getBooleanProperty() {
+        return booleanProperty;
+    }
+
+    public boolean[] getBooleanArrayProperty() {
+        return booleanArrayProperty;
+    }
+
+    public long getLongProperty() {
+        return longProperty;
+    }
+
+    public long[] getLongArrayProperty() {
+        return longArrayProperty;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultStringModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultStringModel.java
new file mode 100644
index 0000000..7c7accf
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultStringModel.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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Default;
+import org.apache.sling.models.annotations.Model;
+
+@Model(adaptables = Resource.class)
+@SuppressWarnings("javadoc")
+public class DefaultStringModel {
+
+    private final String firstProperty;
+    private final String[] secondProperty;
+
+    @Inject
+    public DefaultStringModel(
+            @Default(values = "firstDefault") String firstProperty,
+            @Default(values = { "firstDefault", "secondDefault" }) String[] secondProperty
+    ) {
+        this.firstProperty = firstProperty;
+        this.secondProperty = secondProperty;
+    }
+
+    public String getFirstProperty() {
+        return firstProperty;
+    }
+
+    public String[] getSecondProperty() {
+        return secondProperty;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultWrappersModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultWrappersModel.java
new file mode 100644
index 0000000..bc50f0d
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultWrappersModel.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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Default;
+import org.apache.sling.models.annotations.Model;
+
+@Model(adaptables = Resource.class)
+@SuppressWarnings("javadoc")
+public class DefaultWrappersModel {
+
+    private final Boolean booleanWrapperProperty;
+    private final Boolean[] booleanWrapperArrayProperty;
+    private final Long longWrapperProperty;
+    private final Long[] longWrapperArrayProperty;
+
+    @Inject
+    public DefaultWrappersModel(
+            @Default(booleanValues = true) Boolean booleanWrapperProperty,
+            @Default(booleanValues = { true, true }) Boolean[] booleanWrapperArrayProperty,
+            @Default(longValues = 1L) Long longWrapperProperty,
+            @Default(longValues = { 1L, 1L }) Long[] longWrapperArrayProperty
+    ) {
+        this.booleanWrapperProperty = booleanWrapperProperty;
+        this.booleanWrapperArrayProperty = booleanWrapperArrayProperty;
+        this.longWrapperProperty = longWrapperProperty;
+        this.longWrapperArrayProperty = longWrapperArrayProperty;
+    }
+
+    public Boolean getBooleanWrapperProperty() {
+        return booleanWrapperProperty;
+    }
+
+    public Boolean[] getBooleanWrapperArrayProperty() {
+        return booleanWrapperArrayProperty;
+    }
+
+    public Long getLongWrapperProperty() {
+        return longWrapperProperty;
+    }
+
+    public Long[] getLongWrapperArrayProperty() {
+        return longWrapperArrayProperty;
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/InjectorSpecificAnnotationModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/InjectorSpecificAnnotationModel.java
new file mode 100644
index 0000000..2a6873f
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/InjectorSpecificAnnotationModel.java
@@ -0,0 +1,91 @@
+/*
+ * 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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.ChildResource;
+import org.apache.sling.models.annotations.injectorspecific.OSGiService;
+import org.apache.sling.models.annotations.injectorspecific.RequestAttribute;
+import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
+import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
+import org.slf4j.Logger;
+
+@Model(adaptables = SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class InjectorSpecificAnnotationModel {
+
+    private final String first;
+    private final String secondWithOtherName;
+    private final Logger log;
+    private final SlingScriptHelper helper;
+    private final Object requestAttribute;
+    private final Logger service;
+    private final Resource childResource;
+
+    @Inject
+    public InjectorSpecificAnnotationModel(
+            @ValueMapValue(name = "first", optional = true) String first,
+            @ValueMapValue(name = "second", optional = true) String secondWithOtherName,
+            @ValueMapValue(name = "log", optional = true) Logger log,
+            @ScriptVariable(optional = true, name = "sling") SlingScriptHelper helper,
+            @RequestAttribute(optional = true, name = "attribute") Object requestAttribute,
+            @OSGiService(optional = true) Logger service,
+            @ChildResource(optional = true, name = "child1") Resource childResource
+    ) {
+        this.first = first;
+        this.secondWithOtherName = secondWithOtherName;
+        this.log = log;
+        this.helper = helper;
+        this.requestAttribute = requestAttribute;
+        this.service = service;
+        this.childResource = childResource;
+    }
+
+    public String getFirst() {
+        return first;
+    }
+
+    public String getSecond() {
+        return secondWithOtherName;
+    }
+
+    public Logger getLog() {
+        return log;
+    }
+
+    public Logger getService() {
+        return service;
+    }
+
+    public SlingScriptHelper getHelper() {
+        return helper;
+    }
+
+    public Object getRequestAttribute() {
+        return requestAttribute;
+    }
+
+    public Resource getChildResource() {
+        return childResource;
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ListOSGiModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ListOSGiModel.java
new file mode 100644
index 0000000..66e8e2f
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ListOSGiModel.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.sling.models.testmodels.classes.constructorinjection;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.testmodels.interfaces.ServiceInterface;
+
+@Model(adaptables = Resource.class)
+@SuppressWarnings("javadoc")
+public class ListOSGiModel {
+
+    private final List<ServiceInterface> services;
+
+    @Inject
+    public ListOSGiModel(List<ServiceInterface> services) {
+        this.services = services;
+    }
+
+    public List<ServiceInterface> getServices() {
+        return services;
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/NoNameModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/NoNameModel.java
new file mode 100644
index 0000000..78e2928
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/NoNameModel.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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.models.annotations.Model;
+
+@Model(adaptables = SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class NoNameModel {
+
+    private final SlingScriptHelper sling;
+
+    @Inject
+    public NoNameModel(SlingScriptHelper sling) {
+        this.sling = sling;
+    }
+
+    public SlingScriptHelper getSling() {
+        return sling;
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/SimpleOSGiModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/SimpleOSGiModel.java
new file mode 100644
index 0000000..d48c2c6
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/SimpleOSGiModel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.testmodels.interfaces.ServiceInterface;
+
+@Model(adaptables = Resource.class)
+@SuppressWarnings("javadoc")
+public class SimpleOSGiModel {
+
+    private final ServiceInterface service;
+
+    @Inject
+    public SimpleOSGiModel(ServiceInterface service) {
+        this.service = service;
+    }
+
+    public ServiceInterface getService() {
+        return service;
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/WithThreeConstructorsOneInjectModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/WithThreeConstructorsOneInjectModel.java
new file mode 100644
index 0000000..2e97072
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/WithThreeConstructorsOneInjectModel.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.sling.models.testmodels.classes.constructorinjection;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.models.annotations.Model;
+
+@Model(adaptables = SlingHttpServletRequest.class)
+@SuppressWarnings("javadoc")
+public class WithThreeConstructorsOneInjectModel {
+
+    private SlingHttpServletRequest request;
+
+    @Inject
+    private int attribute;
+
+    private String attribute2;
+
+    public WithThreeConstructorsOneInjectModel(SlingHttpServletRequest request) {
+        this.request = request;
+    }
+
+    public WithThreeConstructorsOneInjectModel() {
+    }
+
+    @Inject
+    public WithThreeConstructorsOneInjectModel(@Named("attribute2") String attribute2) {
+        this.attribute2 = attribute2;
+    }
+
+    public int getAttribute() {
+        return attribute;
+    }
+
+    public String getAttribute2() {
+        return attribute2;
+    }
+
+    public SlingHttpServletRequest getRequest() {
+        return request;
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.