octavian-nita
5/5/2017 - 8:52 AM

Caching reflection accesses in Java (from the Spring Framework code base)

Caching reflection accesses in Java (from the Spring Framework code base)

/**
 * Heavily inspired by Spring's {@link org.springframework.util.ReflectionUtils}
 * but preferring not to mess with it, as per its own documentation.
 */

void clearCache() { declaredMethodsCache.clear(); }

///////////////////////////////////////
//
//  Spring code base (more or less)
//
///////////////////////////////////////

private Method findMethod(Class<?> clazz, String name) { return findMethod(clazz, name, new Class<?>[0]); }

private Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
    required(clazz);
    required(name);

    Class<?> searchType = clazz;
    while (searchType != null) {
        Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType));
        for (Method method : methods) {
            if (name.equals(method.getName()) &&
                (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
                return method;
            }
        }
        searchType = searchType.getSuperclass();
    }

    return null;
}

private Method[] getDeclaredMethods(Class<?> clazz) {
    required(clazz);

    Method[] result = declaredMethodsCache.get(clazz);
    if (result == null) {
        try {

            Method[] declaredMethods = clazz.getDeclaredMethods();
            List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
            if (defaultMethods != null) {

                result = new Method[declaredMethods.length + defaultMethods.size()];
                arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);

                int index = declaredMethods.length;
                for (Method defaultMethod : defaultMethods) {
                    result[index] = defaultMethod;
                    index++;
                }

            } else {
                result = declaredMethods;
            }
            declaredMethodsCache.put(clazz, (result.length == 0 ? NO_METHODS : result));

        } catch (Throwable ex) {
            LOG.error("??? Failed to introspect Class [" + clazz.getName() + "] from ClassLoader [" +
                      clazz.getClassLoader() + "]", ex);
        }
    }
    return result;
}

private static List<Method> findConcreteMethodsOnInterfaces(Class<?> clazz) {
    List<Method> result = null;
    for (Class<?> ifc : clazz.getInterfaces()) {
        for (Method ifcMethod : ifc.getMethods()) {
            if (!isAbstract(ifcMethod.getModifiers())) {
                if (result == null) {
                    result = new LinkedList<>();
                }
                result.add(ifcMethod);
            }
        }
    }
    return result;
}

/** Avoiding <code>static</code> fields to rely on Garbage Collection releasing unneeded memory */
private final Map<Class<?>, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap<>(256);

private static final Method[] NO_METHODS = {};