Mastering Proxy Pattern in Java: From Nacos Static Proxy to JDK & Cglib Dynamic Proxies
This article explains the Proxy design pattern, covering its definition, use cases, static proxy implementation in Nacos, step‑by‑step guides for JDK dynamic proxies and Cglib dynamic proxies, and how Spring AOP leverages both approaches for method interception.
Background
While exploring Nacos source code, a well‑implemented use of the Proxy pattern was discovered, providing a realistic example for readers to experience proxy usage. The article covers the definition of the Proxy pattern, its scenarios, Nacos service registration, static proxy, dynamic proxy (JDK and CGLIB), and Spring AOP proxy mechanisms.
What Is the Proxy Pattern?
The Proxy Pattern is a structural design pattern that uses a proxy object to invoke methods of a target object while adding extra behavior. A real‑world analogy is renting a house through an agent who not only handles the rental but also provides cleaning services, illustrating the proxy’s ability to enhance the target’s functionality.
Isolation role : The proxy acts as an intermediary when the client cannot directly reference the target.
Open/Closed Principle : New features (e.g., cleaning) can be added via the proxy without modifying the target’s code, supporting extensibility such as authentication, caching, logging, or transaction handling.
Proxy Pattern Classification
Proxies are generally divided into static proxies and dynamic proxies . Dynamic proxies can be implemented via JDK dynamic proxy or CGLIB dynamic proxy .
Static Proxy in Nacos
Nacos uses a static proxy for its service registration interface. The pattern requires an interface, a delegate class, and a proxy class that both implement the interface. The proxy decides whether to use an ephemeral or persistent registration implementation.
The static proxy adds logic such as logging or timing before and after delegating calls. Example code:
public interface ClientOperationService {
void registerInstance(Service service, Instance instance, String clientId) throws NacosException;
// ...
}Two delegate implementations handle ephemeral and persistent registrations respectively:
@Component("ephemeralClientOperationService")
public class EphemeralClientOperationServiceImpl implements ClientOperationService {
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
// ...ephemeral registration logic
}
}
@Component("persistentClientOperationServiceImpl")
public class PersistentClientOperationServiceImpl implements ClientOperationService {
@Override
public void registerInstance(Service service, Instance instance, String clientId) {
// ...persistent registration logic
}
}The proxy class receives both delegates via constructor injection and selects the appropriate one at runtime:
@Component
public class ClientOperationServiceProxy implements ClientOperationService {
private final ClientOperationService ephemeralClientOperationService;
private final ClientOperationService persistentClientOperationService;
public ClientOperationServiceProxy(EphemeralClientOperationServiceImpl eph, PersistentClientOperationServiceImpl per) {
this.ephemeralClientOperationService = eph;
this.persistentClientOperationService = per;
}
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
ClientOperationService target = instance.isEphemeral() ?
ephemeralClientOperationService : persistentClientOperationService;
target.registerInstance(service, instance, clientId);
}
}Advantages of static proxy include clear separation and easy extension without modifying the target. Drawbacks are code duplication and fragility when the target interface changes.
JDK Dynamic Proxy
JDK dynamic proxy creates proxy classes at runtime using reflection. It requires the target to implement an interface. The core classes are java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler.
Example: logging before and after a user login.
public interface UserService {
void login(String username, String password);
}
public class UserServiceImpl implements UserService {
@Override
public void login(String username, String password) {
System.out.println("User Login Service!");
}
}
public class LogHandler implements InvocationHandler {
private final Object target;
public LogHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before Login---");
Object result = method.invoke(target, args);
System.out.println("After Login---");
return result;
}
}
public class JdkProxyTest {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LogHandler(userService));
proxy.login("admin", "123456");
}
}Running this prints:
Before Login---
User Login Service!
After Login---Key parameters for Proxy.newProxyInstance are the class loader, the array of interfaces, and the invocation handler.
Generating the Proxy Class File
A utility can save the generated proxy bytecode to disk:
public class ProxyUtils {
public static void generateClassFile(Class clazz, String proxyName) {
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String path = clazz.getResource(".").getPath();
try (FileOutputStream out = new FileOutputStream(path + proxyName + ".class")) {
out.write(classFile);
} catch (Exception e) { e.printStackTrace(); }
}
}After execution, a UserServiceProxy.class file appears in the target directory and can be decompiled to reveal the proxy’s structure.
CGLIB Dynamic Proxy
CGLIB creates a subclass of the target class at runtime, allowing proxying without an interface. It uses Enhancer and implements MethodInterceptor.
public class LogInterceptor implements MethodInterceptor {
public Object getProxyInstance(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("LogInterceptor:Before Login---");
Object result = proxy.invokeSuper(obj, args);
System.out.println("LogInterceptor:After Login---");
return result;
}
}
public class OrderService {
public void order(String orderNo) {
System.out.println("order something... ");
}
}
public class CglibTest {
public static void main(String[] args) {
OrderService service = (OrderService) new LogInterceptor().getProxyInstance(OrderService.class);
service.order("123");
}
}Output:
LogInterceptor:Before Login---
order something...
LogInterceptor:After Login---CGLIB advantages: no need for interfaces, works on classes directly, but cannot proxy final classes or final/static methods.
Comparison of the Three Proxy Types
Static proxy: both proxy and target implement an interface; chosen at compile time.
JDK dynamic proxy: generated via Proxy.newProxyInstance; requires target to implement an interface; uses InvocationHandler.invoke for enhancement.
CGLIB dynamic proxy: generates a subclass using ASM; no interface required; uses Enhancer and MethodInterceptor.intercept.
Spring’s Dynamic Proxy Support
Spring AOP employs both JDK dynamic proxies ( org.springframework.aop.framework.JdkDynamicAopProxy) and CGLIB proxies ( org.springframework.aop.framework.CglibAopProxy). By default, Spring uses JDK proxies when the target implements an interface; otherwise, it falls back to CGLIB.
Understanding these proxy mechanisms helps diagnose common Spring AOP pitfalls such as private methods, final methods, or internal method calls that prevent proxying.
Conclusion
The article demonstrates proxy pattern usage through a concrete Nacos static proxy example, then walks through JDK and CGLIB dynamic proxy implementations, and finally shows how Spring’s AOP leverages both techniques. Readers should now appreciate the importance of proxies in Java applications and be able to apply static, JDK dynamic, or CGLIB proxies as needed.
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.
Senior Brother's Insights
A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.
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.
