Spring Proxies: A Comprehensive Guide

Introduction to Spring Proxies

Spring uses proxies to implement AOP and other features. This guide covers different types of proxies, their creation, and best practices for using them effectively.

Proxy Types

JDK Dynamic Proxies and CGLIB


// Interface for JDK dynamic proxy
public interface UserService {
    void createUser(User user);
    User getUser(Long id);
}

// Implementation
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void createUser(User user) {
        // Implementation
    }
    
    @Override
    public User getUser(Long id) {
        // Implementation
        return null;
    }
}

// CGLIB proxy (no interface required)
@Service
public class OrderService {
    public void createOrder(Order order) {
        // Implementation
    }
    
    public Order getOrder(Long id) {
        // Implementation
        return null;
    }
}
                

Proxy Modes

Configuring Proxy Creation


// Proxy mode configuration
@Configuration
public class ProxyConfig {
    @Bean
    @Scope(value = "singleton", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public OrderService orderService() {
        return new OrderService();
    }
}

// Using proxy mode in components
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
    // Implementation
}

// Interface-based proxy
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public class RequestScopedInterface implements RequestScopedInterface {
    // Implementation
}
                

Proxy Creation

How Proxies are Created


// Manual proxy creation
public class ProxyCreationExample {
    public static void main(String[] args) {
        // JDK dynamic proxy
        UserService userService = new UserServiceImpl();
        InvocationHandler handler = new LoggingInvocationHandler(userService);
        UserService proxy = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),
            new Class[] { UserService.class },
            handler
        );
        
        // CGLIB proxy
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class);
        enhancer.setCallback(new LoggingMethodInterceptor());
        OrderService cglibProxy = (OrderService) enhancer.create();
    }
}

// Invocation handler for JDK proxy
class LoggingInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}

// Method interceptor for CGLIB
class LoggingMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}
                

Proxy Limitations

Understanding Proxy Constraints


@Service
public class ProxyLimitationsService {
    // 1. Self-invocation limitation
    public void methodA() {
        methodB();  // Won't trigger proxy
    }
    
    @Transactional
    public void methodB() {
        // Transaction won't be created
    }
    
    // 2. Final methods
    public final void finalMethod() {
        // Cannot be proxied
    }
    
    // 3. Private methods
    private void privateMethod() {
        // Cannot be proxied
    }
    
    // 4. Static methods
    public static void staticMethod() {
        // Cannot be proxied
    }
}
                

Proxy Solutions

Working Around Limitations


@Service
public class ProxySolutionsService {
    @Autowired
    private ApplicationContext applicationContext;
    
    // Solution 1: Using AopContext
    public void methodA() {
        ((ProxySolutionsService) AopContext.currentProxy()).methodB();
    }
    
    // Solution 2: Using ApplicationContext
    public void methodC() {
        applicationContext.getBean(ProxySolutionsService.class).methodB();
    }
    
    // Solution 3: Using @Autowired self-reference
    @Autowired
    private ProxySolutionsService self;
    
    public void methodD() {
        self.methodB();
    }
    
    @Transactional
    public void methodB() {
        // Transaction will be created
    }
}
                

Proxy Performance

Optimizing Proxy Usage


@Service
public class ProxyPerformanceService {
    // 1. Minimize proxy creation
    @Scope(value = "singleton")  // Default scope
    public class SingletonService {
        // Implementation
    }
    
    // 2. Use appropriate proxy mode
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class RequestScopedService {
        // Implementation
    }
    
    // 3. Optimize method calls
    @Transactional
    public void batchOperation(List items) {
        // Process items in batch
        items.forEach(this::processItem);
    }
    
    private void processItem(Item item) {
        // Process single item
    }
}
                

Proxy Best Practices

  • Use appropriate proxy mode
  • Be aware of proxy limitations
  • Handle self-invocation properly
  • Optimize proxy creation
  • Use interfaces when possible
  • Monitor proxy performance
  • Test proxy behavior
  • Document proxy usage

Common Pitfalls

Issues to Avoid


// 1. Self-invocation issues
@Service
public class SelfInvocationService {
    // Bad: Self-invocation
    @Transactional
    public void methodA() {
        methodB();  // Transaction won't be created
    }
    
    @Transactional
    public void methodB() {
        // Implementation
    }
    
    // Good: Using proxy
    @Autowired
    private SelfInvocationService self;
    
    @Transactional
    public void methodC() {
        self.methodB();  // Transaction will be created
    }
}

// 2. Proxy mode issues
@Service
// Bad: Missing proxy mode
@Scope("request")
public class RequestScopedService {
    // Implementation
}

// Good: Explicit proxy mode
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedService {
    // Implementation
}

// 3. Performance issues
@Service
public class PerformanceService {
    // Bad: Too many proxy calls
    @Transactional
    public void processItems(List items) {
        items.forEach(item -> processItem(item));  // Each call creates transaction
    }
    
    // Good: Batch processing
    @Transactional
    public void processItemsBatch(List items) {
        items.forEach(item -> processItem(item));  // Single transaction
    }
}
                

Conclusion

Spring proxies are powerful tools for implementing AOP and other features. Understanding proxy types, limitations, and best practices helps in creating efficient and maintainable applications.