Spring AOP Proxy Modes: Differences Between CGLIB and JDK Dynamic Proxies
This article explains the two proxy mechanisms used by Spring AOP—JDK dynamic proxy and CGLIB—detailing their core principles, required conditions, code examples, performance trade‑offs, how Spring chooses between them, common pitfalls that cause proxy failure, and practical solutions.
What is Spring AOP proxy?
Spring AOP creates a dynamic proxy at runtime that wraps the target (business) object. Calls to the target are intercepted, allowing cross‑cutting logic (e.g., logging, rate limiting) to be executed before and after the original method.
Proxy generation strategies
Spring supports two strategies: JDK dynamic proxy and CGLIB dynamic proxy.
JDK dynamic proxy
Core condition : the target must implement at least one interface; otherwise Spring falls back to CGLIB.
Manual example:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. Define an interface (required for JDK proxy)
public interface UserService {
void addUser(String username);
}
// 2. Implement the interface (target object)
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("Core business: add user -> " + username);
}
}
// 3. Implement InvocationHandler (enhancement logic)
public class JdkProxyHandler implements InvocationHandler {
private final Object target;
public JdkProxyHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK proxy – before method: log start");
Object result = method.invoke(target, args);
System.out.println("JDK proxy – after method: log end");
return result;
}
}
// 4. Test JDK dynamic proxy
public class JdkProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkProxyHandler(target));
proxy.addUser("ZhangSan");
}
}Run result:
JDK proxy – before method: log start
Core business: add user -> ZhangSan
JDK proxy – after method: log endSummary: the proxy implements the same interfaces as the target, so the relationship is “brother”; enhancement logic resides in InvocationHandler.invoke.
CGLIB dynamic proxy
Core condition : no interface requirement; the proxy is a subclass of the target, therefore any final method cannot be overridden and cannot be enhanced.
Manual example:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 1. Target class (no interface needed)
public class OrderService {
public void addOrder(String orderNo) {
System.out.println("Core business: add order -> " + orderNo);
}
public final void deleteOrder(String orderNo) {
System.out.println("Delete order -> " + orderNo);
}
}
// 2. MethodInterceptor (enhancement logic)
public class CglibProxyInterceptor implements MethodInterceptor {
private final Object target;
public CglibProxyInterceptor(Object target) { this.target = target; }
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB proxy – before method: log start");
Object result = proxy.invokeSuper(obj, args); // preferred way
System.out.println("CGLIB proxy – after method: log end");
return result;
}
}
// 3. Test CGLIB proxy
public class CglibProxyTest {
public static void main(String[] args) {
OrderService target = new OrderService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new CglibProxyInterceptor(target));
OrderService proxy = (OrderService) enhancer.create();
proxy.addOrder("ORDER20260417001");
proxy.deleteOrder("ORDER20260417001"); // final method, no enhancement
}
}Run result:
CGLIB proxy – before method: log start
Core business: add order -> ORDER20260417001
CGLIB proxy – after method: log end
Delete order -> ORDER20260417001Summary: CGLIB creates a subclass of the target; the proxy‑target relationship is “parent‑child”. All non‑final methods can be overridden to insert enhancement logic.
Comparison
Underlying mechanism : JDK uses java.lang.reflect.Proxy based on interfaces; CGLIB uses Enhancer based on inheritance.
Dependency condition : JDK requires the target to implement at least one interface; CGLIB has no interface requirement.
Method limitation : JDK can enhance only methods declared in the interface; CGLIB can enhance all non‑final methods of the class.
Proxy‑target relationship : JDK – “brother” (both implement the same interface); CGLIB – “parent‑child” (proxy is a subclass of the target).
Performance : JDK proxy creation is fast, invocation is slightly slower (reflection); CGLIB proxy creation is slower (bytecode generation), invocation is faster (direct method call).
Required JAR : JDK proxy uses only JDK classes; CGLIB requires the CGLIB library (bundled with Spring).
Spring default priority : JDK is preferred when the target has interfaces; CGLIB is used as a fallback or when spring.aop.proxy-target-class=true is set.
Typical use cases : JDK – service/DAO layers with interfaces; CGLIB – utility classes or scenarios needing to enhance class‑specific methods.
Spring proxy selection source code
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// Core decision logic
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
// If target is an interface, still use JDK proxy
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// Otherwise use CGLIB
return new ObjenesisCglibAopProxy(config);
} else {
// Target has interfaces → JDK proxy
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] interfaces = config.getProxiedInterfaces();
return interfaces == null || interfaces.length == 0 ||
(interfaces.length == 1 && SpringProxy.class.isAssignableFrom(interfaces[0]));
}
}Key selection logic:
If proxy-target-class=true (or optimization is enabled), Spring forces CGLIB regardless of interfaces.
Without forced configuration, Spring checks whether the target implements interfaces: if yes → JDK proxy; if no → CGLIB proxy.
Even when interfaces exist, enabling isOptimize switches to CGLIB for speed.
Common proxy failure scenarios and solutions
Scenario 1: Target method is final (CGLIB fails)
Cause: CGLIB creates a subclass; final methods cannot be overridden, so advice is never applied.
Solution: Remove the final modifier or move the logic to a non‑final method.
Scenario 2: Self‑invocation within the target object
Cause: A method in the target calls another method of the same instance (e.g., this.methodB()); the call bypasses the proxy, so AOP advice is not triggered.
Solution: Obtain the proxy from the Spring context and invoke through it, inject the proxy into the bean, or use AopContext.currentProxy().
Scenario 3: Target object not managed by Spring
Cause: Objects created with new are outside the Spring container, so no proxy is generated.
Solution: Let Spring manage the bean via @Autowired, @Resource, or configuration‑based injection.
Conclusion
JDK dynamic proxy: interface‑based, no extra dependencies, fast creation, slower invocation; suited for classes with interfaces.
CGLIB dynamic proxy: subclass‑based, requires CGLIB, slower creation, faster invocation; suited for classes without interfaces or when enhancing class‑specific methods.
Spring automatically selects the proxy mode but can be forced to use CGLIB with spring.aop.proxy-target-class=true.
Proxy failures are usually caused by final methods, internal self‑calls, or beans not managed by Spring; addressing these three points resolves most issues.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
