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.

Java Captain
Java Captain
Java Captain
Java Proxy Patterns Explained: Static, JDK Dynamic, CGLIB & Spring AOP Internals

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

design patternsSpring AOPAspect-Oriented ProgrammingBytecode GenerationCGLIBJDK Dynamic ProxyJava Proxy
Java Captain
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.