You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2020/11/30 21:14:23 UTC

[nifi] branch main updated: NIFI-8054: Updated ReflectionUtils to use a WeakHashMap for the mapping of annotations to methods with that annotation. This way, the ReflectionUtils class will not hold a reference to Classes that are no longer referenced elsewhere. (#4694)

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

bbende pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new aaa1452  NIFI-8054: Updated ReflectionUtils to use a WeakHashMap for the mapping of annotations to methods with that annotation. This way, the ReflectionUtils class will not hold a reference to Classes that are no longer referenced elsewhere. (#4694)
aaa1452 is described below

commit aaa1452d041ff34f3647004e73a1f78f59c561d4
Author: markap14 <ma...@hotmail.com>
AuthorDate: Mon Nov 30 16:14:12 2020 -0500

    NIFI-8054: Updated ReflectionUtils to use a WeakHashMap for the mapping of annotations to methods with that annotation. This way, the ReflectionUtils class will not hold a reference to Classes that are no longer referenced elsewhere. (#4694)
---
 .../org/apache/nifi/util/ClassAnnotationPair.java  | 61 --------------------
 .../java/org/apache/nifi/util/ReflectionUtils.java | 67 ++++++++++++++++++----
 2 files changed, 57 insertions(+), 71 deletions(-)

diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java
deleted file mode 100644
index 44ffa70..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.nifi.util;
-
-import java.lang.annotation.Annotation;
-import java.util.Arrays;
-
-public class ClassAnnotationPair {
-    private final Class<?> clazz;
-    private final Class<? extends Annotation>[] annotations;
-
-    public ClassAnnotationPair(final Class<?> clazz, final Class<? extends Annotation>[] annotations) {
-        this.clazz = clazz;
-        this.annotations = annotations;
-    }
-
-    public Class<?> getDeclaredClass() {
-        return clazz;
-    }
-
-    public Class<? extends Annotation>[] getAnnotations() {
-        return annotations;
-    }
-
-    @Override
-    public int hashCode() {
-        return 41 + 47 * clazz.hashCode() + 47 * Arrays.hashCode(annotations);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-
-        if (!(obj instanceof ClassAnnotationPair)) {
-            return false;
-        }
-
-        final ClassAnnotationPair other = (ClassAnnotationPair) obj;
-        return clazz == other.clazz && Arrays.equals(annotations, other.annotations);
-    }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
index 5f163e6..420fe08 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
@@ -27,16 +27,16 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
 
 public class ReflectionUtils {
 
     private final static Logger LOG = LoggerFactory.getLogger(ReflectionUtils.class);
-    private static ConcurrentMap<ClassAnnotationPair, List<Method>> annotationCache = new ConcurrentHashMap<>();
+    private static Map<Class<?>, Map<Annotations, List<Method>>> annotationCache = new WeakHashMap<>();
 
     /**
      * Invokes all methods on the given instance that have been annotated with the given Annotation. If the signature of the method that is defined in <code>instance</code> uses 1 or more parameters,
@@ -158,19 +158,32 @@ public class ReflectionUtils {
         return isSuccess;
     }
 
-    private static List<Method> findMethodsWithAnnotations(final Class<?> clazz, final Class<? extends Annotation>[] annotations) {
+    private static List<Method> findMethodsWithAnnotations(final Class<?> clazz, final Class<? extends Annotation>[] annotationClasses) {
         // We use a cache here to store a mapping of Class & Annotation[] to those methods that contain the annotation.
         // This is done because discovering this using Reflection is fairly expensive (can take up to tens of milliseconds on laptop).
         // While this may not seem like much time, consider deleting a Process Group with thousands of Processors or instantiating
         // a Template with thousands of Processors. This can add up to several seconds very easily.
-        final ClassAnnotationPair pair = new ClassAnnotationPair(clazz, annotations);
-        List<Method> methods = annotationCache.get(pair);
-        if (methods != null) {
-            return methods;
+        final Annotations annotations = new Annotations(annotationClasses);
+
+        synchronized (annotationCache) {
+            final Map<Annotations, List<Method>> innerMap = annotationCache.get(clazz);
+            if (innerMap != null) {
+                final List<Method> methods = innerMap.get(annotations);
+                if (methods != null) {
+                    return methods;
+                }
+            }
+        }
+
+        // The methods to invoke have not been cached. Discover them via reflection.
+        final List<Method> methods = discoverMethodsWithAnnotations(clazz, annotationClasses);
+
+        // Store the discovered methods in our cache so that they are available next time.
+        synchronized (annotationCache) {
+            final Map<Annotations, List<Method>> innerMap = annotationCache.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>());
+            innerMap.putIfAbsent(annotations, methods);
         }
 
-        methods = discoverMethodsWithAnnotations(clazz, annotations);
-        annotationCache.putIfAbsent(pair, methods);
         return methods;
     }
 
@@ -305,4 +318,38 @@ public class ReflectionUtils {
             return false;
         }
     }
+
+    private static class Annotations {
+        private final Class<? extends Annotation>[] array;
+
+        public Annotations(final Class<? extends Annotation>[] array) {
+            this.array = array;
+        }
+
+        public Class<? extends Annotation>[] getArray() {
+            return array;
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(array);
+        }
+
+        @Override
+        public boolean equals(final Object other) {
+            if (other == null) {
+                return false;
+            }
+            if (other == this) {
+                return true;
+            }
+
+            if (!(other instanceof Annotations)) {
+                return false;
+            }
+
+            final Annotations otherAnnotations = (Annotations) other;
+            return Arrays.equals(this.array, otherAnnotations.array);
+        }
+    }
 }