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 commit is contained in:
markap14 2020-11-30 16:14:12 -05:00 committed by GitHub
parent fe53f8090d
commit aaa1452d04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 71 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}
}