mirror of https://github.com/apache/nifi.git
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:
parent
fe53f8090d
commit
aaa1452d04
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue