diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/spi/NoAutoStartUtil.java b/logback-core/src/main/java/ch/qos/logback/core/joran/spi/NoAutoStartUtil.java
index c64019ed29..aaa595cc57 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/joran/spi/NoAutoStartUtil.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/joran/spi/NoAutoStartUtil.java
@@ -13,6 +13,10 @@
*/
package ch.qos.logback.core.joran.spi;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
import ch.qos.logback.core.spi.LifeCycle;
public class NoAutoStartUtil {
@@ -24,13 +28,83 @@ public class NoAutoStartUtil {
* @param o
* @return true for classes not marked with the NoAutoStart annotation
*/
- static public boolean notMarkedWithNoAutoStart(Object o) {
+ public static boolean notMarkedWithNoAutoStart(Object o) {
+ if (o == null) {
+ return false;
+ }
Class> clazz = o.getClass();
- NoAutoStart a = clazz.getAnnotation(NoAutoStart.class);
+ NoAutoStart a = findAnnotation(clazz, NoAutoStart.class);
return a == null;
}
- /**
+ /**
+ * Find a single {@link Annotation} of {@code annotationType} on the
+ * supplied {@link Class}, traversing its interfaces, annotations, and
+ * superclasses if the annotation is not directly present on
+ * the given class itself.
+ *
This method explicitly handles class-level annotations which are not
+ * declared as {@link java.lang.annotation.Inherited inherited} as well
+ * as meta-annotations and annotations on interfaces.
+ *
The algorithm operates as follows:
+ *
+ * - Search for the annotation on the given class and return it if found.
+ *
- Recursively search through all annotations that the given class declares.
+ *
- Recursively search through all interfaces that the given class declares.
+ *
- Recursively search through the superclass hierarchy of the given class.
+ *
+ * Note: in this context, the term recursively means that the search
+ * process continues by returning to step #1 with the current interface,
+ * annotation, or superclass as the class to look for annotations on.
+ * @param clazz the class to look for annotations on
+ * @param annotationType the type of annotation to look for
+ * @return the first matching annotation, or {@code null} if not found
+ */
+ private static A findAnnotation(Class> clazz, Class annotationType) {
+ return findAnnotation(clazz, annotationType, new HashSet<>());
+ }
+
+ /**
+ * Perform the search algorithm for {@link #findAnnotation(Class, Class)},
+ * avoiding endless recursion by tracking which annotations have already
+ * been visited.
+ * @param clazz the class to look for annotations on
+ * @param annotationType the type of annotation to look for
+ * @param visited the set of annotations that have already been visited
+ * @return the first matching annotation, or {@code null} if not found
+ */
+ @SuppressWarnings("unchecked")
+ private static A findAnnotation(Class> clazz, Class annotationType, Set visited) {
+
+ Annotation[] anns = clazz.getDeclaredAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType() == annotationType) {
+ return (A) ann;
+ }
+ }
+ for (Annotation ann : anns) {
+ if (visited.add(ann)) {
+ A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ }
+
+ for (Class> ifc : clazz.getInterfaces()) {
+ A annotation = findAnnotation(ifc, annotationType, visited);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+
+ Class> superclass = clazz.getSuperclass();
+ if (superclass == null || Object.class == superclass) {
+ return null;
+ }
+ return findAnnotation(superclass, annotationType, visited);
+ }
+
+ /**
* Is the object a {@link LifeCycle} and is it marked not marked with
* the NoAutoStart annotation.
* @param o
@@ -43,5 +117,4 @@ static public boolean shouldBeStarted(Object o) {
} else
return false;
}
-
}
diff --git a/logback-core/src/test/java/ch/qos/logback/core/joran/spi/NoAutoStartUtilTest.java b/logback-core/src/test/java/ch/qos/logback/core/joran/spi/NoAutoStartUtilTest.java
index 4cd995cd89..4a5390a446 100644
--- a/logback-core/src/test/java/ch/qos/logback/core/joran/spi/NoAutoStartUtilTest.java
+++ b/logback-core/src/test/java/ch/qos/logback/core/joran/spi/NoAutoStartUtilTest.java
@@ -15,6 +15,12 @@
import org.junit.jupiter.api.Test;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -31,4 +37,71 @@ public void markedWithNoAutoStart() {
DoNotAutoStart o = new DoNotAutoStart();
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
}
+
+
+
+ /*
+ * Annotation declared on implemented interface
+ */
+ @Test
+ public void noAutoStartOnInterface() {
+ ComponentWithNoAutoStartOnInterface o = new ComponentWithNoAutoStartOnInterface();
+ assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
+ }
+
+ @NoAutoStart
+ public interface NoAutoStartInterface {
+ }
+
+ private static class ComponentWithNoAutoStartOnInterface implements NoAutoStartInterface {
+ }
+
+
+
+ /*
+ * Annotation declared on ancestor
+ */
+ @Test
+ public void noAutoStartOnAncestor() {
+ ComponentWithNoAutoStartOnAncestor o = new ComponentWithNoAutoStartOnAncestor();
+ assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
+ }
+
+ private static class ComponentWithNoAutoStartOnAncestor extends DoNotAutoStart {
+ }
+
+
+
+ /*
+ * Annotation declared on interface implemented by an ancestor
+ */
+ @Test
+ public void noAutoStartOnInterfaceImplementedByAncestor() {
+ ComponentWithAncestorImplementingInterfaceWithNoAutoStart o = new ComponentWithAncestorImplementingInterfaceWithNoAutoStart();
+ assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
+ }
+
+ private static class ComponentWithAncestorImplementingInterfaceWithNoAutoStart extends ComponentWithNoAutoStartOnInterface {
+ }
+
+
+
+ /*
+ * Custom annotation annotated with @NoAutoStart
+ */
+ @Test
+ public void noAutoStartAsMetaAnnotation() {
+ ComponentWithMetaAnnotation o = new ComponentWithMetaAnnotation();
+ assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ @NoAutoStart
+ public @interface MetaNoAutoStart {
+ }
+
+ @MetaNoAutoStart
+ private static class ComponentWithMetaAnnotation {
+ }
}