Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
168: Dynamic Proxies with Reflection
A few years ago, I worked with a developer named Sarah who was tasked with adding detailed execution timing to about forty different service classes in a legacy monolith. Her first instinct was the "correct" design pattern approach: the Decorator pattern. She started writing wrapper classes for every single service. By the end of the first day, she had created twelve nearly identical classes that did nothing but start a timer, call the original method, and stop the timer. She realized that if she kept going, she'd spend her entire sprint writing boilerplate code that provided zero business value. That's when I showed her Dynamic Proxies.
In Java, a dynamic proxy allows you to create an object at runtime that implements one or more interfaces. The magic is that you don't actually write the class for the proxy; Java generates it for you. This lets you intercept every single method call made to that object and route it through a single piece of logic. It's the secret sauce behind how frameworks like Spring handle @Transactional or @Cacheable without forcing you to write the plumbing yourself.
The Engine: InvocationHandler
To make a dynamic proxy work, you need a way to tell Java what to do when a method is intercepted. This is where the InvocationHandler comes in. Think of it as a traffic controller. Every time a method is called on your proxy object, Java bundles the proxy instance, the method being called, and the arguments into a single invoke method.
Here is how I would set up a simple performance monitor to replace those dozens of decorators Sarah was writing:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TimingHandler implements InvocationHandler {
private final Object target;
public TimingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
// This is where we actually call the real method on the real object
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + " took " + (end - start) + "ms");
return result;
}
}
Notice that the invoke method is generic. It doesn't care if you're calling saveUser() or calculateTax(); it treats everything as a Method object and a set of arguments. This is the power of reflection—we're operating on the structure of the code rather than the hard-coded implementation.
Binding the Proxy to an Interface
Now that we have the handler, we need to actually instantiate the proxy. You use the Proxy.newProxyInstance method for this. This method requires three things: the class loader, an array of interfaces the proxy should implement, and your InvocationHandler.
One critical detail you need to remember: Dynamic Proxies in Java (using the JDK's built-in reflection) only work with interfaces. You cannot create a dynamic proxy for a concrete class that doesn't implement an interface. If you need to proxy a class, you'd have to look into libraries like CGLib or ByteBuddy, but for most architectural needs, interfaces are the way to go.
public interface UserService {
void createUser(String name);
}
public class UserServiceImpl implements UserService {
public void createUser(String name) {
// Simulate some heavy database work
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("User " + name + " created.");
}
}
// Putting it all together:
UserService realService = new UserServiceImpl();
UserService proxyService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class>[] { UserService.class },
new TimingHandler(realService)
);
proxyService.createUser("Alice");
// Output:
// User Alice created.
// createUser took 105ms
I've seen developers over-use this pattern, which is a trap. It's tempting to put every single piece of logic into a proxy, but remember that reflection carries a small performance overhead and makes debugging harder since the stack trace will be littered with sun.reflect.GeneratedMethodAccessor calls. Use it for cross-cutting concerns—logging, security, transactions, and profiling—not for core business logic.
📋 Practical Task
Build a Method-Level Access Control Proxy
Your task is to create a security proxy that prevents certain methods from being called based on a mock "user role."
- Create an interface called
DocumentServicewith two methods:readDocument()anddeleteDocument(). - Create a concrete implementation
DocumentServiceImplthat simply prints "Reading document..." and "Deleting document..." respectively. - Implement an
AccessControlHandler(implementingInvocationHandler). This handler should have aString userRolefield. - In the
invokemethod, add logic that:- Always allows
readDocument()to execute. - Only allows
deleteDocument()to execute if theuserRoleis equal to "ADMIN". - Throws a
SecurityExceptionif a "USER" attempts to calldeleteDocument().
- Always allows
- In your main method, instantiate the proxy twice: once with the "ADMIN" role and once with the "USER" role. Demonstrate that the Admin can delete, but the User cannot.
There are no comments for now.