Today we're diving into an important topic you need to get if you want to understand the real magic behind AOP in Spring: proxy objects (Proxy). We'll break down what they are, how they work, and why Spring uses them.
If you're thinking "proxy" is that sketchy server you used to bypass a blocked website, you're... right! Partly. Proxies in programming play a similar role: they act as intermediaries. But instead of bypassing blocks, proxy objects in Spring add extra behavior to your code.
You can think of it like hiring an assistant (the proxy) to manage your calendar. When someone wants to meet you, they go to the assistant first. The assistant decides whether to bother you right now. Similarly, a proxy object in Spring can intercept method calls, do something useful (like logging or security checks), and then hand control over to the real object.
How do proxy objects work?
AOP in Spring is built on proxies. If you add an aspect (for example via @Aspect), Spring dynamically creates a proxy object that replaces the original bean. When a method is called on that bean, the call first goes through the proxy, and only then reaches the target method.
Execution flow:
- You call a method on your bean.
- The proxy object intercepts the call.
- The proxy runs its "extra code" (for example, logging or transaction checks).
- The proxy forwards control to the target method on your bean.
Types of proxies in Spring
Spring supports two kinds of proxies:
- JDK Dynamic Proxy — works with interfaces.
- CGLIB Proxy — works with classes.
JDK Dynamic Proxy
JDK Dynamic Proxy creates proxy objects only for interfaces. If your class implements an interface, Spring will create a proxy using JDK Dynamic Proxy.
Example:
public interface PaymentService {
void processPayment();
}
@Component
public class PaymentServiceImpl implements PaymentService {
@Override
public void processPayment() {
System.out.println("Processing payment...");
}
}
In this case Spring will create a proxy for the PaymentService interface. The proxy will intercept calls to processPayment().
CGLIB Proxy
If your bean doesn't implement interfaces, Spring uses CGLIB Proxy. This proxy type subclasses your class and overrides methods to add extra logic.
Example:
@Component
public class NotificationService {
public void sendNotification() {
System.out.println("Sending notification...");
}
}
Since NotificationService doesn't implement an interface, Spring will create a proxy using CGLIB.
Important: CGLIB doesn't work with methods marked final. Why? Because final prevents method overriding, which breaks how the proxy works.
Example of using proxy objects in AOP
Let's make an example where we use a proxy to log whenever a method is called.
Code without AOP:
@Component
public class UserService {
public void createUser(String username) {
System.out.println("User " + username + " created!");
}
}
This code just prints a user-created message. But if we want to automatically log every call to createUser, we'd have to modify the method itself. That would violate the single responsibility principle.
Code with AOP and a proxy:
Create an aspect:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.demo.UserService.createUser(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " is called");
}
}
Now when createUser is called, Spring will use a proxy object. The proxy will run logBefore first, and only then call createUser.
How to see the "magical" proxy in action?
You can inspect how Spring creates proxy objects with a simple test. For example:
@Autowired
private UserService userService;
@Test
public void checkProxy() {
System.out.println(userService.getClass());
}
The output might look like this:
- For JDK Dynamic Proxy:
class com.sun.proxy.$Proxy12 - For CGLIB Proxy:
class com.example.demo.UserService$$EnhancerBySpringCGLIB$$3f9b8a5f
Notice how Spring adds its "magical" suffixes to the class name.
Why it's important to understand proxies
To work effectively with AOP and avoid surprises, you need to know how proxies behave.
Common pitfalls:
- Calling methods inside the same class: If you call a method from another method of the same class, AOP won't work. The proxy can't intercept the call because it goes directly and bypasses the proxy object.
Example:
@Component
public class SampleService {
public void externalMethod() {
internalMethod(); // Proxy won't kick in!
}
public void internalMethod() {
System.out.println("Internal method called");
}
}
Fix: split the methods into different beans.
- Final methods: If you're using CGLIB Proxy, declaring a method
finalwill prevent the proxy from overriding it. That makes AOP ineffective for that method.
Example:
public final void finalMethod() {
System.out.println("This method cannot be proxied");
}
Why this matters in practice
Knowing how proxies work helps you avoid pitfalls and keeps your code clearer. In real projects proxies aren't used only for aspects—they're also used for transaction management and security. For example:
- The
@Transactionalannotation in Spring is also based on AOP and proxies. If you don't realize that proxies won't intercept calls made inside the same class, you might get surprising behavior, like transactions not rolling back.
We've learned that proxy objects are key to implementing AOP in Spring. They let you add cross-cutting concerns (aspects) without changing business logic. Spring smartly picks between JDK dynamic proxies and CGLIB proxies depending on the bean. Now you're armed with this knowledge and ready to dig deeper into using aspects in your apps.
GO TO FULL VERSION