google-site-verification: google61fe8ba583a51912.html
Dynamic Proxies in Java

Dynamic proxies provide a mechanism for creating proxy instances for interfaces at runtime. They are powerful tools for implementing cross-cutting concerns like logging, security, transaction management, and more.

1. Basic Dynamic Proxy

Simple InvocationHandler Implementation

import java.lang.reflect.*; public class BasicDynamicProxy { // Interface we want to proxy interface UserService { String getUser(String userId); void saveUser(String userId, String userData); void deleteUser(String userId); } // Real implementation static class UserServiceImpl implements UserService { @Override public String getUser(String userId) { System.out.println("Getting user: " + userId); return "User data for: " + userId; } @Override public void saveUser(String userId, String userData) { System.out.println("Saving user: " + userId + " with data: " + userData); } @Override public void deleteUser(String userId) { System.out.println("Deleting user: " + userId); } } // Dynamic proxy handler static class LoggingHandler implements InvocationHandler { private final Object target; public LoggingHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(">>> Before method: " + method.getName()); if (args != null) { System.out.println(" Arguments: " + java.util.Arrays.toString(args)); } long startTime = System.nanoTime(); Object result = method.invoke(target, args); long endTime = System.nanoTime(); System.out.println(" Result: " + result); System.out.println(" Execution time: " + (endTime - startTime) + " ns"); System.out.println("<<< After method: " + method.getName()); return result; } } public static void main(String[] args) { // Create real object UserService realService = new UserServiceImpl(); // Create proxy instance UserService proxy = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class<?>[] { UserService.class }, new LoggingHandler(realService) ); // Use proxy proxy.getUser("123"); System.out.println(); proxy.saveUser("456", "John Doe"); System.out.println(); proxy.deleteUser("789"); } }

2. Advanced Dynamic Proxy Patterns

Generic Proxy Factory

import java.lang.reflect.*; import java.util.*; public class ProxyFactory { @SuppressWarnings("unchecked") public static <T> T createProxy(T target, Class<T> interfaceType, InvocationHandler... handlers) { return (T) Proxy.newProxyInstance( interfaceType.getClassLoader(), new Class<?>[] { interfaceType }, new ChainedInvocationHandler(target, handlers) ); } // Chain multiple handlers static class ChainedInvocationHandler implements InvocationHandler { private final Object target; private final List<InvocationHandler> handlers; public ChainedInvocationHandler(Object target, InvocationHandler... handlers) { this.target = target; this.handlers = new ArrayList<>(Arrays.asList(handlers)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Create invocation context InvocationContext context = new InvocationContext(proxy, method, args, target); // Execute handlers in chain for (InvocationHandler handler : handlers) { handler.invoke(proxy, method, args); } // Finally invoke the target method return method.invoke(target, args); } } static class InvocationContext { public final Object proxy; public final Method method; public final Object[] args; public final Object target; public Object result; public Throwable exception; public InvocationContext(Object proxy, Method method, Object[] args, Object target) { this.proxy = proxy; this.method = method; this.args = args; this.target = target; } } } // Example usage with multiple handlers class AdvancedProxyExample { interface Calculator { int add(int a, int b); int multiply(int a, int b); double divide(double a, double b); } static class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { return a + b; } @Override public int multiply(int a, int b) { return a * b; } @Override public double divide(double a, double b) { if (b == 0) throw new IllegalArgumentException("Division by zero"); return a / b; } } // Logging handler static class LoggingHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.printf("[LOG] Calling %s with args: %s%n", method.getName(), Arrays.toString(args)); return null; // Continue chain } } // Timing handler static class TimingHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); try { return null; // Continue chain } finally { long duration = System.currentTimeMillis() - start; System.out.printf("[TIMING] %s took %d ms%n", method.getName(), duration); } } } // Security handler static class SecurityHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("divide")) { System.out.println("[SECURITY] Checking division operation..."); } return null; // Continue chain } } public static void main(String[] args) { Calculator realCalculator = new CalculatorImpl(); Calculator proxy = ProxyFactory.createProxy( realCalculator, Calculator.class, new LoggingHandler(), new TimingHandler(), new SecurityHandler() ); System.out.println("Result: " + proxy.add(5, 3)); System.out.println(); System.out.println("Result: " + proxy.multiply(4, 7)); System.out.println(); try { System.out.println("Result: " + proxy.divide(10.0, 2.0)); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } }

3. Real-World Use Cases

Database Connection Pool with Proxy

import java.lang.reflect.*; import java.sql.*; import java.util.*; import java.util.concurrent.*; public class ConnectionPoolProxy { interface ConnectionPool { Connection getConnection() throws SQLException; void releaseConnection(Connection connection); int getActiveConnections(); int getIdleConnections(); } static class SimpleConnectionPool implements ConnectionPool { private final BlockingQueue<Connection> pool; private final String url; private final String username; private final String password; private final int maxConnections; private final AtomicInteger activeConnections = new AtomicInteger(0); public SimpleConnectionPool(String url, String username, String password, int maxConnections) { this.url = url; this.username = username; this.password = password; this.maxConnections = maxConnections; this.pool = new LinkedBlockingQueue<>(maxConnections); } @Override public Connection getConnection() throws SQLException { Connection conn = pool.poll(); if (conn != null) { activeConnections.incrementAndGet(); return conn; } if (activeConnections.get() < maxConnections) { Connection newConn = DriverManager.getConnection(url, username, password); activeConnections.incrementAndGet(); return newConn; } throw new SQLException("Connection pool exhausted"); } @Override public void releaseConnection(Connection connection) { if (connection != null) { pool.offer(connection); activeConnections.decrementAndGet(); } } @Override public int getActiveConnections() { return activeConnections.get(); } @Override public int getIdleConnections() { return pool.size(); } } // Proxy that tracks connection usage static class TrackingConnectionProxy implements InvocationHandler { private final Connection realConnection; private final ConnectionPool pool; private boolean closed = false; private long lastUsedTime; public TrackingConnectionProxy(Connection realConnection, ConnectionPool pool) { this.realConnection = realConnection; this.pool = pool; this.lastUsedTime = System.currentTimeMillis(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Update last used time lastUsedTime = System.currentTimeMillis(); // Handle close method specially if ("close".equals(method.getName())) { if (!closed) { closed = true; pool.releaseConnection((Connection) proxy); System.out.println("Connection returned to pool"); } return null; } // Check if connection is closed if (closed) { throw new SQLException("Connection is closed"); } // Delegate to real connection return method.invoke(realConnection, args); } public long getIdleTime() { return System.currentTimeMillis() - lastUsedTime; } } // Enhanced connection pool with proxy static class ProxiedConnectionPool implements ConnectionPool { private final SimpleConnectionPool realPool; public ProxiedConnectionPool(String url, String username, String password, int maxConnections) { this.realPool = new SimpleConnectionPool(url, username, password, maxConnections); } @Override public Connection getConnection() throws SQLException { Connection realConn = realPool.getConnection(); // Create proxy for the connection return (Connection) Proxy.newProxyInstance( Connection.class.getClassLoader(), new Class<?>[] { Connection.class }, new TrackingConnectionProxy(realConn, realPool) ); } @Override public void releaseConnection(Connection connection) { // Not needed - handled by proxy } @Override public int getActiveConnections() { return realPool.getActiveConnections(); } @Override public int getIdleConnections() { return realPool.getIdleConnections(); } } public static void main(String[] args) throws Exception { // Simulate database connection pool with proxies ProxiedConnectionPool pool = new ProxiedConnectionPool( "jdbc:mysql://localhost:3306/test", "user", "pass", 5 ); // Get connections through proxy Connection conn1 = pool.getConnection(); System.out.println("Got connection 1"); // Use connection Statement stmt = conn1.createStatement(); // Close connection (returns to pool via proxy) conn1.close(); System.out.println("Active connections: " + pool.getActiveConnections()); System.out.println("Idle connections: " + pool.getIdleConnections()); } }

Caching Proxy

import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*; public class CachingProxyExample { interface DataService { String fetchData(String key); void updateData(String key, String value); void clearData(String key); } static class ExpensiveDataService implements DataService { @Override public String fetchData(String key) { // Simulate expensive operation (database call, API call, etc.) System.out.println(">>> Performing expensive fetch for: " + key); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return "Data for " + key + " at " + System.currentTimeMillis(); } @Override public void updateData(String key, String value) { System.out.println("Updating data for: " + key); // Simulate update operation try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @Override public void clearData(String key) { System.out.println("Clearing data for: " + key); } } static class CachingHandler implements InvocationHandler { private final Object target; private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>(); private final long defaultTtl; // Time to live in milliseconds public CachingHandler(Object target, long defaultTtl) { this.target = target; this.defaultTtl = defaultTtl; } static class CacheEntry { final Object value; final long expiryTime; CacheEntry(Object value, long ttl) { this.value = value; this.expiryTime = System.currentTimeMillis() + ttl; } boolean isExpired() { return System.currentTimeMillis() > expiryTime; } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if ("fetchData".equals(methodName)) { return handleFetchData(method, args); } else if ("updateData".equals(methodName) || "clearData".equals(methodName)) { return handleDataModification(method, args); } else { return method.invoke(target, args); } } private Object handleFetchData(Method method, Object[] args) throws Throwable { String key = (String) args[0]; CacheEntry entry = cache.get(key); // Return cached value if present and not expired if (entry != null && !entry.isExpired()) { System.out.println(">>> Cache HIT for: " + key); return entry.value; } // Cache miss or expired - fetch from target System.out.println(">>> Cache MISS for: " + key); Object result = method.invoke(target, args); // Cache the result cache.put(key, new CacheEntry(result, defaultTtl)); return result; } private Object handleDataModification(Method method, Object[] args) throws Throwable { // Invalidate cache for modified keys if (args.length > 0) { String key = (String) args[0]; cache.remove(key); System.out.println(">>> Cache invalidated for: " + key); } return method.invoke(target, args); } public void clearCache() { cache.clear(); System.out.println(">>> Cache cleared"); } public int getCacheSize() { return cache.size(); } } public static void main(String[] args) throws Exception { DataService realService = new ExpensiveDataService(); DataService cachedService = (DataService) Proxy.newProxyInstance( DataService.class.getClassLoader(), new Class<?>[] { DataService.class }, new CachingHandler(realService, 5000) // 5 second TTL ); // First call - cache miss System.out.println("First call:"); String result1 = cachedService.fetchData("key1"); System.out.println("Result: " + result1); System.out.println(); // Second call - cache hit System.out.println("Second call (within TTL):"); String result2 = cachedService.fetchData("key1"); System.out.println("Result: " + result2); System.out.println(); // Update data - invalidates cache System.out.println("Updating data:"); cachedService.updateData("key1", "new value"); System.out.println(); // Third call - cache miss after update System.out.println("Third call (after update):"); String result3 = cachedService.fetchData("key1"); System.out.println("Result: " + result3); } }

4. Security and Access Control Proxy

Role-Based Access Control Proxy

import java.lang.reflect.*; import java.util.*; public class SecurityProxyExample { interface SecureService { @RequiresRole("ADMIN") void deleteUser(String userId); @RequiresRole("USER") String viewProfile(String userId); @RequiresRole({"ADMIN", "MANAGER"}) void updateUser(String userId, String userData); String publicInfo(); // No role required } @interface RequiresRole { String[] value(); } static class SecureServiceImpl implements SecureService { @Override public void deleteUser(String userId) { System.out.println("Deleting user: " + userId); } @Override public String viewProfile(String userId) { return "Profile data for: " + userId; } @Override public void updateUser(String userId, String userData) { System.out.println("Updating user: " + userId + " with data: " + userData); } @Override public String publicInfo() { return "Public information"; } } static class SecurityHandler implements InvocationHandler { private final Object target; private final Set<String> userRoles; public SecurityHandler(Object target, Set<String> userRoles) { this.target = target; this.userRoles = userRoles; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Check for @RequiresRole annotation RequiresRole annotation = method.getAnnotation(RequiresRole.class); if (annotation != null) { String[] requiredRoles = annotation.value(); if (!hasRequiredRole(requiredRoles)) { throw new SecurityException( "Access denied. User lacks required roles: " + Arrays.toString(requiredRoles) + " for method: " + method.getName() ); } } // User has required role or no role required return method.invoke(target, args); } private boolean hasRequiredRole(String[] requiredRoles) { for (String requiredRole : requiredRoles) { if (userRoles.contains(requiredRole)) { return true; } } return false; } } public static void main(String[] args) { SecureService realService = new SecureServiceImpl(); // Test with different user roles testWithRoles(realService, new HashSet<>(Arrays.asList("USER"))); // Regular user testWithRoles(realService, new HashSet<>(Arrays.asList("MANAGER"))); // Manager testWithRoles(realService, new HashSet<>(Arrays.asList("ADMIN"))); // Admin testWithRoles(realService, new HashSet<>(Arrays.asList("USER", "MANAGER"))); // Multiple roles } private static void testWithRoles(SecureService realService, Set<String> roles) { System.out.println("\n=== Testing with roles: " + roles + " ==="); SecureService securedService = (SecureService) Proxy.newProxyInstance( SecureService.class.getClassLoader(), new Class<?>[] { SecureService.class }, new SecurityHandler(realService, roles) ); try { // Public method - should always work System.out.println("Public info: " + securedService.publicInfo()); // Role-specific methods System.out.println("View profile: " + securedService.viewProfile("123")); securedService.updateUser("123", "new data"); securedService.deleteUser("123"); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } }

5. Transaction Management Proxy

Transactional Method Interceptor

import java.lang.reflect.*; import java.sql.*; import java.util.*; public class TransactionProxyExample { interface UserRepository { @Transactional void saveUser(User user) throws SQLException; @Transactional void updateUser(User user) throws SQLException; User findUserById(int id) throws SQLException; // Not transactional @Transactional void transferPoints(int fromUserId, int toUserId, int points) throws SQLException; } @interface Transactional { // Marker annotation for transactional methods } static class User { private int id; private String name; private int points; public User(int id, String name, int points) { this.id = id; this.name = name; this.points = points; } // Getters and setters public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPoints() { return points; } public void setPoints(int points) { this.points = points; } @Override public String toString() { return String.format("User{id=%d, name='%s', points=%d}", id, name, points); } } static class UserRepositoryImpl implements UserRepository { private Connection connection; public UserRepositoryImpl(Connection connection) { this.connection = connection; } @Override @Transactional public void saveUser(User user) throws SQLException { System.out.println("Saving user: " + user); // Simulate database operation if (user.getName().contains("error")) { throw new SQLException("Simulated database error"); } } @Override @Transactional public void updateUser(User user) throws SQLException { System.out.println("Updating user: " + user); } @Override public User findUserById(int id) throws SQLException { System.out.println("Finding user: " + id); return new User(id, "User" + id, 100); } @Override @Transactional public void transferPoints(int fromUserId, int toUserId, int points) throws SQLException { System.out.printf("Transferring %d points from user %d to user %d%n", points, fromUserId, toUserId); // Simulate complex operation that should be atomic updateUser(new User(fromUserId, "FromUser", -points)); updateUser(new User(toUserId, "ToUser", points)); // Simulate potential error if (points < 0) { throw new SQLException("Negative points transfer not allowed"); } } } static class TransactionHandler implements InvocationHandler { private final Object target; private final Connection connection; public TransactionHandler(Object target, Connection connection) { this.target = target; this.connection = connection; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Check if method is transactional boolean isTransactional = method.isAnnotationPresent(Transactional.class); if (!isTransactional) { // Non-transactional method - just delegate return method.invoke(target, args); } // Transactional method - wrap in transaction boolean originalAutoCommit = connection.getAutoCommit(); try { // Start transaction connection.setAutoCommit(false); System.out.println(">>> Starting transaction for: " + method.getName()); // Execute method Object result = method.invoke(target, args); // Commit transaction connection.commit(); System.out.println(">>> Transaction committed for: " + method.getName()); return result; } catch (Exception e) { // Rollback transaction on error connection.rollback(); System.out.println(">>> Transaction rolled back for: " + method.getName()); throw e; } finally { // Restore original auto-commit state connection.setAutoCommit(originalAutoCommit); } } } public static void main(String[] args) throws Exception { // Simulate database connection Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:test", "sa", ""); UserRepository realRepository = new UserRepositoryImpl(connection); UserRepository transactionalRepository = (UserRepository) Proxy.newProxyInstance( UserRepository.class.getClassLoader(), new Class<?>[] { UserRepository.class }, new TransactionHandler(realRepository, connection) ); // Test successful transaction System.out.println("=== Testing successful transaction ==="); try { transactionalRepository.saveUser(new User(1, "John", 100)); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } System.out.println(); // Test failed transaction (rollback) System.out.println("=== Testing failed transaction (rollback) ==="); try { transactionalRepository.saveUser(new User(2, "error-user", 100)); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } System.out.println(); // Test complex transactional operation System.out.println("=== Testing complex transactional operation ==="); try { transactionalRepository.transferPoints(1, 2, 50); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } System.out.println(); // Test non-transactional method System.out.println("=== Testing non-transactional method ==="); User user = transactionalRepository.findUserById(1); System.out.println("Found: " + user); connection.close(); } }

6. Dynamic Proxy Limitations and Considerations

Limitations and Workarounds

import java.lang.reflect.*; public class ProxyLimitations { interface Service { void process(); } static class ServiceImpl implements Service { @Override public void process() { System.out.println("Processing..."); } public void internalMethod() { System.out.println("Internal method"); } } public static void main(String[] args) { Service realService = new ServiceImpl(); Service proxy = (Service) Proxy.newProxyInstance( Service.class.getClassLoader(), new Class<?>[] { Service.class }, (p, method, args1) -> { System.out.println("Intercepting: " + method.getName()); return method.invoke(realService, args1); } ); // This works - interface method proxy.process(); // This won't work - proxy only implements the interface // ((ServiceImpl) proxy).internalMethod(); // ClassCastException System.out.println("\nProxy class: " + proxy.getClass()); System.out.println("Proxy superclass: " + proxy.getClass().getSuperclass()); System.out.println("Proxy interfaces: " + Arrays.toString(proxy.getClass().getInterfaces())); System.out.println("Is proxy: " + Proxy.isProxyClass(proxy.getClass())); // Get invocation handler InvocationHandler handler = Proxy.getInvocationHandler(proxy); System.out.println("Invocation handler: " + handler.getClass()); } }

Key Benefits of Dynamic Proxies

  1. Aspect-Oriented Programming - Separate cross-cutting concerns
  2. Runtime Flexibility - Create proxies dynamically without source code
  3. Transparent Proxying - Clients don't know they're using proxies
  4. Interface-Based - Clean separation of interface and implementation
  5. Performance - Generally faster than reflection for repeated calls

Common Use Cases

  1. Logging and Monitoring - Method call tracing
  2. Caching - Transparent result caching
  3. Security - Access control and authorization
  4. Transactions - Automatic transaction management
  5. Lazy Loading - On-demand resource loading
  6. Remote Method Invocation - Network transparency
  7. Mocking - Test double creation

Dynamic proxies are a powerful feature that enables many advanced patterns in Java applications, particularly in frameworks like Spring, Hibernate, and other enterprise libraries.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper