Spring Core - How Spring proxies add behavior at runtime
Spring uses proxies to add additional behavior to beans at runtime. This is done primarily for implementing cross-cutting concerns such as transaction management, logging, security, caching, etc. Proxies are dynamically created and act as wrappers around the actual bean. Calls to the bean pass through the proxy, which can intercept, modify, or enhance the behavior before delegating to the original bean.
Key Concepts of Spring Proxies
Spring proxies are a powerful mechanism for dynamically adding behaviors to beans at runtime. They form the backbone of features like transaction management, AOP, security, and caching. By wrapping beans with proxies, Spring enables clean, modular, and extensible cross-cutting logic.
- What is a Proxy?
- A proxy is an object that controls access to another object. In Spring, the proxy wraps the target bean and intercepts method calls to add additional behavior.
- Why Use Proxies?
- To apply functionality like transactions, security, or logging without modifying the business logic in the bean itself.
- Enables AOP (Aspect-Oriented Programming) in Spring, where cross-cutting concerns are modularized as aspects.
- Types of Proxies Used in Spring:
- JDK Dynamic Proxies: Used when the target object implements an interface. The proxy implements the same interface and delegates calls to the original bean.
- CGLIB Proxies: Used when the target object does not implement any interface. CGLIB creates a subclass of the target class to act as a proxy.
How Proxies Work
- Proxy Creation:
- When a bean is eligible for proxying (e.g., annotated with @Transactional or matched by an AOP pointcut), Spring creates a proxy object instead of directly instantiating the bean.
- The proxy wraps the target bean.
- Intercepting Method Calls:
- When a method is called on the proxy, the proxy intercepts the call.
- The proxy can then:
- Execute custom logic (e.g., start a transaction).
- Delegate the call to the actual method on the target bean.
- Perform additional actions after the method execution (e.g., commit/rollback the transaction).
Implementation Examples
1. Using JDK Dynamic Proxies
Using Java's Proxy API with a predefined interface and corresponding implementation class. We “wrap” around the method performTask of our MyService class. This lets us for example handle errors, logging or caching.
public interface MyService {
void performTask();
}
public class MyServiceImpl implements MyService {
@Override
public void performTask() {
System.out.println("Executing task...");
}
}
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyProxyDemo {
public static void main(String[] args) {
MyService target = new MyServiceImpl();
// Create a proxy for the target object
MyService proxy = (MyService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{MyService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method execution...");
Object result = method.invoke(target, args);
System.out.println("After method execution...");
return result;
}
}
);
proxy.performTask();
}
}
2. Using Spring Proxies for @Transactional
Another approach is the annotation based proxying. For this to work we need to add the annotation @EnableTransactionManagement to the configuration class. Afterwards, using the @Transactional annotation we can define the proxy. The proxy starts a transaction before calling the method and commits/rolls back the transaction afterward.
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyServiceImpl implements MyService {
@Override
@Transactional
public void performTask() {
System.out.println("Performing task within a transaction...");
}
}
Spring Proxy Mechanisms
- JDK Dynamic Proxies:
- Requires the target object to implement an interface.
- The proxy object implements the same interface and delegates method calls to the target bean.
- CGLIB (Code Generation Library) Proxies:
- Used when the target object does not implement any interface.
- Creates a subclass of the target class at runtime and overrides its methods.
- Does not work if the class or methods are final, since the proxy is created by subclassing.
Proxy Creation Rules:
- If the target bean implements an interface, Spring uses JDK Dynamic Proxies.
- If no interface is implemented, Spring uses CGLIB Proxies.
- You can enforce the use of CGLIB proxies by setting proxyTargetClass=true in configuration @EnableTransactionManagement annotation.
Advantages and Limitations of Using Proxies
- Separation of Concerns:
- Cross-cutting logic is separated from business logic, making code cleaner and modular.
- Runtime Behavior:
- Proxies allow adding behavior without altering the original class.
- Extensibility:
- New behaviors can be introduced dynamically without changing the bean's code.
- Proxies Do Not Intercept Internal Calls:
- A method call within the same class does not pass through the proxy, so annotations like @Transactional won't apply.
- Performance Overhead:
- Proxies introduce a small runtime performance overhead due to method interception.
