@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		String callPrefix = "[" + interfaceType.getName() + "#" + method.getName() + "(" + StringUtils.join(args) + ")] ";
		if (LOG.isDebugEnabled()) LOG.debug(callPrefix + "intercepted call...");
		long start = System.currentTimeMillis();
		
		Class<? extends Object> delegateType = delegate.getClass();
		Method delegateMethod = delegateType.getMethod(method.getName(), method.getParameterTypes());
		saver.CacheControl cacheAnnotation = delegateMethod.getAnnotation(saver.CacheControl.class);
		if (cacheAnnotation == null) {
			Object value = method.invoke(delegate, args);
			
			if (LOG.isInfoEnabled()) LOG.info(callPrefix + "(" + elapsed(start) + " ms) no cache annotation found, returning value provided by delegate");
			return value;
		}
		
		Cache cache = getCache(delegateType, delegateMethod, cacheAnnotation);
		CallKey key = newCallKey(delegateType, delegateMethod, args);
		if (LOG.isDebugEnabled()) LOG.debug(callPrefix + "looking for a value cached with key " + key + "...");
		
		Object value = cache.get(key);
		if (value != null) {
			if (LOG.isInfoEnabled()) LOG.info(callPrefix + "(" + elapsed(start) + " ms) returning cached value: " + value);
			return value;
		}
		
		if (LOG.isDebugEnabled()) LOG.debug(callPrefix + "no value cached, calling delegate...");
		value = method.invoke(delegate, args);
		cache.put(key, value);
		
		if (LOG.isInfoEnabled()) LOG.info(callPrefix + "(" + elapsed(start) + " ms) called delegate and cached returned value");
		return value;
	}