Structural 4 — Proxy
In one line
A stand-in that controls access to the real object: lazy init, access control, caching, remote calls.
Think of it like this
A security guard at an office. You can't walk straight to the CEO; the guard checks your ID first, and then lets you through. The guard stands in for the real thing and controls access.
Key ideas
- 01
Virtual proxy: build the expensive object only when used.
- 02
Protection proxy: check permissions before delegating.
- 03
Lazy-loading ORM proxies are the classic real-world example.
- 04
Proxy vs Decorator: proxy controls access/lifecycle, decorator adds behavior.
Java / Spring map
- →
java.lang.reflect.Proxy; Spring AOP proxies; Hibernate lazy proxies.
- →
Spring gotcha: @Transactional and @Cacheable work through a proxy, so a self-invocation (this.method()) bypasses them. This is a classic interview question.
Code & diagrams
A protection proxy and a lazy virtual proxy, each behind the same interface as the real object.
public interface DocumentStore { byte[] read(String docId, User u); }
public final class S3DocumentStore implements DocumentStore {
public byte[] read(String docId, User u) { /* network call */ return new byte[0]; }
}
// Protection proxy: access control before delegation.
public final class AuthorizingDocumentStore implements DocumentStore {
private final DocumentStore real;
private final Acl acl;
public AuthorizingDocumentStore(DocumentStore real, Acl acl) { this.real = real; this.acl = acl; }
public byte[] read(String docId, User u) {
if (!acl.canRead(u, docId)) throw new AccessDeniedException(docId);
return real.read(docId, u);
}
}
// Virtual proxy: defers the expensive construction until first use.
public final class LazyDocumentStore implements DocumentStore {
private final Supplier<DocumentStore> factory;
private volatile DocumentStore real;
public LazyDocumentStore(Supplier<DocumentStore> f) { this.factory = f; }
public byte[] read(String docId, User u) {
DocumentStore r = real;
if (r == null) {
synchronized (this) { if ((r = real) == null) real = r = factory.get(); }
}
return r.read(docId, u);
}
}Explain without notes
Lazy-load proxy on a User entity — which methods trigger the load?
Why doesn't @Transactional apply when a bean calls its own annotated method?
Practice
Write a CachingProxy over a slow WeatherService that caches by city.
Trade-offs
- ↔
Proxies add indirection and hide the real object's identity. Lazy proxies can also surprise you with LazyInitializationException outside a session.
Completion checklist
I can name three proxy types (virtual, protection, remote) and explain the Spring self-invocation trap.