Java › Annotations

How would you design a custom annotation-driven caching mechanism (similar to Spring's @Cacheable) using a dynamic proxy combined with reflection?

Advanced

Answer

Define a RUNTIME-retention @Cacheable annotation for methods, then use java.lang.reflect.Proxy (for interface-based classes) or a bytecode-generation library like ByteBuddy/CGLIB (for concrete classes) to create a dynamic proxy wrapping the real object -- the proxy's invocation handler checks for the @Cacheable annotation via reflection before delegating to the real method, returning a cached result from a backing Map if a matching cache key already exists, otherwise invoking the real method and storing its result, which is conceptually how Spring's caching abstraction and AOP-based cross-cutting concerns are implemented under the hood.
public class CachingInvocationHandler implements InvocationHandler {
    private final Object target;
    private final Map<String, Object> cache = new ConcurrentHashMap<>();
    public CachingInvocationHandler(Object target) { this.target = target; }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.isAnnotationPresent(Cacheable.class)) {
            String key = method.getName() + Arrays.toString(args);
            return cache.computeIfAbsent(key, k -> {
                try { return method.invoke(target, args); }
                catch (Exception e) { throw new RuntimeException(e); }
            });
        }
        return method.invoke(target, args);
    }
}
Real-world example A lightweight in-house caching layer wraps a data-access interface in a dynamic proxy that reads a custom @Cacheable annotation, transparently caching results for annotated methods without the calling code being aware caching is happening at all, illustrating the same proxy-based AOP pattern Spring uses internally.

Common follow-ups: What's the limitation of java.lang.reflect.Proxy requiring the target to implement an interface, and how do CGLIB/ByteBuddy address concrete classes instead?;How would you handle cache invalidation or expiration in this design?