Proxy Pattern
IntermediateProvides a surrogate or placeholder for another object to control access, add caching, logging, or lazy initialization.
Overview
A Proxy implements the same interface as the real subject and controls access to it. There are four main types: Virtual Proxy (lazy initialization — creates the expensive real object only on first use), Protection Proxy (access control — checks permissions before delegating), Remote Proxy (marshals calls across network boundaries — Java RMI), and Cache Proxy (memoizes results to avoid repeated expensive calls). Spring AOP uses JDK dynamic proxies (interface-based) or CGLIB proxies (subclass-based) to implement @Transactional, @Cacheable, and @Async transparently.
Virtual Proxy & Cache Proxy
A Virtual Proxy defers creation of an expensive object until it is first needed. A Cache Proxy stores results of expensive operations and returns cached results for identical requests.
// Subject interface
public interface ImageLoader {
byte[] loadImage(String imageId);
}
// Real Subject — expensive (hits S3)
public class S3ImageLoader implements ImageLoader {
@Override
public byte[] loadImage(String imageId) {
System.out.println("Fetching from S3: " + imageId); // slow network call
return new byte[]{1, 2, 3}; // simulated image data
}
}
// Cache Proxy — wraps real loader, caches results
public class CachedImageLoader implements ImageLoader {
private final ImageLoader delegate;
private final Map<String, byte[]> cache = new ConcurrentHashMap<>();
public CachedImageLoader(ImageLoader delegate) {
this.delegate = delegate;
}
@Override
public byte[] loadImage(String imageId) {
return cache.computeIfAbsent(imageId, id -> {
System.out.println("Cache MISS for: " + id);
return delegate.loadImage(id);
});
}
}
// Protection Proxy — checks permissions before delegating
public class SecureImageLoader implements ImageLoader {
private final ImageLoader delegate;
private final SecurityContext security;
public SecureImageLoader(ImageLoader delegate, SecurityContext security) {
this.delegate = delegate;
this.security = security;
}
@Override
public byte[] loadImage(String imageId) {
if (!security.hasPermission("IMAGE_READ")) {
throw new AccessDeniedException("No permission to read image: " + imageId);
}
return delegate.loadImage(imageId);
}
}
// Stacking proxies (Protection → Cache → Real)
ImageLoader loader = new SecureImageLoader(
new CachedImageLoader(
new S3ImageLoader()), securityCtx);
loader.loadImage("course-thumbnail.jpg"); // checks permission, then cache, then S3Spring AOP Dynamic Proxy
Spring creates JDK dynamic proxies automatically for beans annotated with @Transactional, @Cacheable, @Async. The proxy intercepts method calls, runs cross-cutting concerns (begin transaction, check cache), then delegates to the real bean.
// Spring creates a proxy around this bean transparently
@Service
public class CourseService {
@Cacheable(value = "courses", key = "#courseId")
public CourseDto getCourse(String courseId) {
// Spring proxy: check cache → if miss → run this method → store in cache
System.out.println("Loading course from DB: " + courseId);
return courseRepository.findById(courseId).map(CourseDto::from).orElseThrow();
}
@Transactional // Spring proxy: begin tx → run method → commit/rollback
public void publishCourse(String courseId) {
Course c = courseRepository.findById(courseId).orElseThrow();
c.setStatus(CourseStatus.PUBLISHED);
courseRepository.save(c);
eventPublisher.publishEvent(new CoursePublishedEvent(courseId));
}
}
// What Spring generates behind the scenes (simplified JDK proxy):
// CourseService proxy = (CourseService) Proxy.newProxyInstance(
// CourseService.class.getClassLoader(),
// new Class[]{CourseService.class},
// (proxyObj, method, args) -> {
// if (method.isAnnotationPresent(Cacheable.class)) {
// // check cache, return cached value or call real method
// }
// return method.invoke(realCourseService, args);
// });
// IMPORTANT: @Transactional only works on public methods called from OUTSIDE the bean.
// Self-invocation bypasses the proxy: this.publishCourse() inside the same bean
// does NOT start a new transaction!Key Points to Remember
- 1Four proxy types: Virtual (lazy init), Protection (access control), Remote (network), Cache (memoization).
- 2Proxy and Decorator look identical in code — intent differs: Proxy controls access; Decorator adds behavior.
- 3Spring @Transactional and @Cacheable use dynamic proxies — self-invocation bypasses them.
- 4JDK dynamic proxies require an interface; CGLIB proxies subclass the target (no interface needed).
- 5Proxy is transparent to the client — client cannot tell it is talking to a proxy.
Interview Questions
Sign in to ask AriaWhat are the four types of Proxy pattern? Give an example of each.
Why does @Transactional not work when a method calls another method in the same class?
What is the difference between JDK dynamic proxy and CGLIB proxy in Spring?
What is the difference between Proxy and Decorator patterns?
How would you implement a retry proxy for a flaky external API call?
Ask Aria about Proxy Pattern
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.