Java Proxy Patterns Explained: Static, JDK Dynamic, CGLIB & Spring AOP Internals
This tutorial demonstrates static proxy (decorator pattern), JDK dynamic proxy (interface-based), CGLIB dynamic proxy (subclassing), and Spring AOP's dual-proxy implementation with @EnableAspectJAutoProxy, using an EmployeeService example that adds C# and Go skills without modifying original code.
The proxy pattern enhances functionality without changing original class code. This article demonstrates four implementation approaches through runnable Maven demos: static proxy, JDK dynamic proxy, CGLIB dynamic proxy, and Spring AOP's annotation-driven AOP.
Project Setup
A Maven project requires two dependencies: spring-context 5.3.17 and aspectjweaver 1.9.8. Critical note: the <scope>runtime</scope> tag from mvnrepository.com for aspectjweaver must be removed, otherwise AOP annotations like @Aspect and @Around become unavailable at compile time. JUnit's test scope should be kept as it excludes the dependency from the final JAR.
The project structure contains four packages: StaticProxy, JdkProxy, CglibProxy, and SpringAop. All demos enhance an EmployeeServiceImpl that initially only prints "拥有 Java 语言的开发能力" (has Java development skill). Enhancements add C# and Go skills.
Static Proxy (Decorator Pattern)
Static proxy uses the decorator pattern. The target must implement an interface ( EmployeeService). Wrapper classes implement the same interface, hold a reference to the target, delegate to it, then add behavior.
public interface EmployeeService { void getAbility(); } @Service("empService")
public class EmployeeServiceImpl implements EmployeeService {
@Override
public void getAbility() {
System.out.println("拥有 Java 语言的开发能力");
}
}First enhancement wrapper:
public class EmployeeAbility01 implements EmployeeService {
private EmployeeService employeeService;
public EmployeeAbility01(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@Override
public void getAbility() {
employeeService.getAbility(); // original call
System.out.println("拥有 C# 语言的开发能力"); // post-enhancement
}
}Second wrapper chains on the first:
public class EmployeeAbility02 implements EmployeeService {
private EmployeeService employeeService;
public EmployeeAbility02(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@Override
public void getAbility() {
employeeService.getAbility();
System.out.println("拥有 Go 语言的开发能力");
}
}Client code:
EmployeeService es = new EmployeeServiceImpl();
es.getAbility();
EmployeeAbility01 ea1 = new EmployeeAbility01(es);
ea1.getAbility();
EmployeeAbility02 ea2 = new EmployeeAbility02(ea1);
ea2.getAbility();Console output shows progressive enhancement: Java → Java + C# → Java + C# + Go.
JDK Dynamic Proxy
JDK dynamic proxy (java.lang.reflect.Proxy) requires the target to implement at least one interface. It generates a proxy class at runtime that implements the same interfaces. An InvocationHandler intercepts method calls.
public class EmployeeAbilityJdkProxy {
public static EmployeeService createJdkProxy(EmployeeService employeeService) {
ClassLoader cl = employeeService.getClass().getClassLoader();
Class[] classes = employeeService.getClass().getInterfaces();
InvocationHandler ih = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(employeeService, args);
if (method.getName().equals("getAbility")) {
System.out.println("拥有 C# 语言的开发能力");
System.out.println("拥有 Go 语言的开发能力");
}
return result;
}
};
return (EmployeeService) Proxy.newProxyInstance(cl, classes, ih);
}
}Usage:
EmployeeService esProxy = EmployeeAbilityJdkProxy.createJdkProxy(new EmployeeServiceImpl());
esProxy.getAbility();Output: Java → C# → Go. The handler intercepts only interface methods.
CGLIB Dynamic Proxy
CGLIB (Code Generation Library) creates a subclass at runtime, overriding methods. No interface required. Spring bundles CGLIB in spring-core. The Enhancer class sets the superclass and a MethodInterceptor callback.
public class EmployeeAbilityCglibProxy {
public static EmployeeService createCglibProxy(Class cls) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(o, args); // call parent method
if (method.getName().equals("getAbility")) {
System.out.println("拥有 C# 语言的开发能力");
System.out.println("拥有 Go 语言的开发能力");
}
return result;
}
});
return (EmployeeService) enhancer.create();
}
}Usage passes the class literal:
EmployeeService esProxy = EmployeeAbilityCglibProxy.createCglibProxy(EmployeeServiceImpl.class);
esProxy.getAbility();Output identical to JDK proxy. Key difference: CGLIB proxies all methods (including non-interface ones) but cannot proxy final methods.
Spring AOP Implementation
Spring AOP uses JDK dynamic proxy by default (target implements interface). If no interface, it falls back to CGLIB. To force CGLIB even with interfaces, set @EnableAspectJAutoProxy(proxyTargetClass = true).
@Configuration
@ComponentScan("com.jobs.SpringAop.impl")
@EnableAspectJAutoProxy // proxyTargetClass = false by default (JDK)
// @EnableAspectJAutoProxy(proxyTargetClass = true) // force CGLIB
public class SpringConfig {}Aspect class with around advice:
@Component
@Aspect
public class EmployeeAop {
@Pointcut("execution(* com.jobs.SpringAop.impl.*Service.*(..))")
public void pt() {}
@Around("pt()")
public Object runtimeAround(ProceedingJoinPoint pjp) {
try {
System.out.println("Aop 前置增强:拥有 C# 语言的开发能力");
pjp.proceed(pjp.getArgs());
System.out.println("Aop 后置增强:拥有 Go 语言的开发能力");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}Client retrieves the proxied bean from the Spring container:
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
EmployeeService empService = (EmployeeService) ctx.getBean("empService");
empService.getAbility();Output: Java → AOP pre-enhancement (C#) → AOP post-enhancement (Go). The article concludes that Spring AOP's core is the two dynamic proxy mechanisms demonstrated, and encourages hands-on practice over pure theory.
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 Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.
