package rocks.inspectit.agent.java.util; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; /** * Provides caching of Reflection calls. * * @author Stefan Siegl */ public class ReflectionCache { /** * The logger of this class. Initialized manually. */ private static final Logger LOG = LoggerFactory.getLogger(ReflectionCache.class); /** * Cache that holds the <code> Method </code> instances for the Class and method Names. */ Cache<Class<?>, Cache<String, Method>> cache = CacheBuilder.newBuilder().weakKeys().softValues().build(); /** * Invokes the given method on the given class. This method uses the cache to only lookup the * method if the same method was not looked up before for the same class. Same as calling * {@link #invokeMethod(Class, String, Class[], Object, Object[], Object, null)}. * * <b> Known limitation: The method name is used as key and not the whole signature, so you * cannot cache methods with the same name but different parameters. The reason is that we do * not need this feature right now and it would complicate and slow down the cache. </b> * * <b> Known limitation: Exceptions are not passed along, but will break the invocation. </b> * * @param clazz * The class on which the method should be invoked. * @param methodName * the name of the method. * @param parameterTypes * the parameters of the method or <code>null</code> if none are needed. * @param instance * the instance on which the method call should be invoked. * @param values * the parameter values or <code>null</code> if none are needed. * @param errorValue * the value that should be returned in case of an error or <code>null</code> if none * are needed. * @return the invocation result. */ public Object invokeMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object instance, Object[] values, Object errorValue) { return invokeMethod(clazz, methodName, parameterTypes, instance, values, errorValue, null); } /** * Invokes the given method on the given class. This method uses the cache to only lookup the * method if the same method was not looked up before for the same class. * * <b> Known limitation: The method name is used as key and not the whole signature, so you * cannot cache methods with the same name but different parameters. The reason is that we do * not need this feature right now and it would complicate and slow down the cache. </b> * * <b> Known limitation: Exceptions are not passed along, but will break the invocation. </b> * * @param clazz * The class on which the method should be invoked. * @param methodName * the name of the method. * @param parameterTypes * the parameters of the method or <code>null</code> if none are needed. * @param instance * the instance on which the method call should be invoked. * @param values * the parameter values or <code>null</code> if none are needed. * @param errorValue * the value that should be returned in case of an error or <code>null</code> if none * are needed. * @param interfaceName * If interface name is passed then a search will be performed to find specified * method on the given interface. If interface method is not found, the class method * will be executed. Can be <code>null</code> to directly use class method. * @return the invocation result. */ public Object invokeMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object instance, Object[] values, Object errorValue, String interfaceName) { if ((null == clazz) || (null == methodName)) { return errorValue; } Cache<String, Method> classCache = cache.getIfPresent(clazz); if (null == classCache) { // create a new cache and add it. There can be race conditions here. There is no entry // for class C and caller A and caller B // want to execute method a or b on the same class. It can thus happen that one // overwrites the other. This is not a big issue // as the only result will be that the method will not be cached for one and will be // cached with the next invocation. This // approach is way cheaper than synchronisation or copy on write. classCache = CacheBuilder.newBuilder().expireAfterAccess(20 * 60, TimeUnit.SECONDS).weakKeys().build(); cache.put(clazz, classCache); } // get method Method method = classCache.getIfPresent(methodName); if (null == method) { try { // check if we need to search with interface Class<?> interfaceClass = null; if (null != interfaceName) { interfaceClass = ClassUtil.searchInterface(clazz, interfaceName); } // if interface class is found than use method from interface, otherwise from class if (interfaceClass != null) { try { method = interfaceClass.getMethod(methodName, parameterTypes); } catch (Throwable t) { // NOPMD LOG.warn("Could not lookup method " + methodName + " on class " + clazz.getName() + ". Trying with the class.", t); } } if (null == method) { method = clazz.getMethod(methodName, parameterTypes); } method.setAccessible(true); } catch (Throwable t) { // NOPMD LOG.warn("Could not lookup method " + methodName + " on class " + clazz.getName(), t); return errorValue; } classCache.put(methodName, method); } // invoke method try { return method.invoke(instance, values); } catch (Throwable t) { // NOPMD LOG.warn("Could not invoke method " + methodName + " on instance " + instance, t); return errorValue; } } }