Fundamentals 12 min read

Mastering the Proxy Pattern: A Hands‑On Guide with Nacos Client

This article explains the proxy design pattern, demonstrates its static implementation with Java code, and shows how Nacos Client leverages a custom proxy delegate to switch between HTTP and gRPC protocols, highlighting structural roles, practical usage, and differences from the decorator pattern.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
Mastering the Proxy Pattern: A Hands‑On Guide with Nacos Client

The article introduces the proxy design pattern, describing it as a software equivalent of a real‑world intermediary that handles tasks on behalf of a client without the client needing to know the details.

Proxy Pattern Overview

In simple terms, a proxy provides a surrogate object that controls access to the real subject, allowing additional behavior such as logging, preprocessing, or post‑processing around the core operation.

Structure of the Proxy Pattern

When not using a proxy, a client directly creates an instance of the concrete service class and invokes its methods. With a proxy, the client interacts with a proxy class that implements the same interface and holds a reference to the real service.

The proxy can add common services before and after delegating to the real object, such as message preprocessing, filtering, forwarding, and result handling.

Abstract Subject (Subject) : declares the common interface for both real and proxy objects.

Concrete Subject (RealSubject) : the actual implementation that performs the core business logic.

Proxy : implements the same interface, holds a reference to the RealSubject, and can inject additional behavior around method calls.

Static Proxy Implementation in Java

The following code shows a classic static proxy example with an interface CarService, a concrete class CarServiceImpl, and a proxy class CarServiceProxy that adds logging before delegating.

public interface CarService {
    Car chooseCar();
    boolean qualityCheck();
}

public class CarServiceImpl implements CarService {
    @Override
    public Car chooseCar() {
        System.out.println("真实操作:选车");
        return new Car();
    }
    @Override
    public boolean qualityCheck() {
        System.out.println("真实操作:质量检测");
        return true;
    }
}

public class CarServiceProxy implements CarService {
    private CarServiceImpl real;
    public CarServiceProxy() {
        real = new CarServiceImpl();
    }
    @Override
    public Car chooseCar() {
        System.out.println("代理类CarServiceProxy选车:先添加一些日志");
        return real.chooseCar();
    }
    @Override
    public boolean qualityCheck() {
        System.out.println("代理类CarServiceProxy质量检测:先添加一些日志");
        return real.qualityCheck();
    }
}

A simple client demonstrates usage:

public class Client {
    public static void main(String[] args) {
        CarService carService = new CarServiceProxy();
        carService.chooseCar();
        carService.qualityCheck();
    }
}

Running the program prints log messages before the real operations, confirming that the proxy can inject extra behavior.

Proxy Pattern in Nacos Client

Nacos Client communicates with the naming service via two protocols: HTTP and gRPC. Both protocols share the abstract interface NamingClientProxy, with concrete implementations NamingHttpClientProxy and NamingGrpcClientProxy.

To allow protocol selection through configuration, Nacos introduces a delegate proxy NamingClientProxyDelegate that holds references to both concrete proxies and performs preprocessing and decision‑making.

The delegate implements NamingClientProxy and forwards calls to the appropriate concrete proxy based on runtime configuration, while also handling extra responsibilities such as event listening.

public class NacosNamingService implements NamingService {
    private NamingClientProxy clientProxy;
    private void init(Properties properties) throws NacosException {
        this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
    }
    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        NamingUtils.checkInstanceIsLegal(instance);
        clientProxy.registerService(serviceName, groupName, instance);
    }
    // ... other methods
}
public interface NamingClientProxy extends Closeable {
    void registerService(String serviceName, String groupName, Instance instance) throws NacosException;
    void deregisterService(String serviceName, String groupName, Instance instance) throws NacosException;
    // ... other methods
}
public class NamingClientProxyDelegate implements NamingClientProxy {
    private final NamingHttpClientProxy httpClientProxy;
    private final NamingGrpcClientProxy grpcClientProxy;
    public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder, Properties properties, InstancesChangeNotifier changeNotifier) throws NacosException {
        this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties, serviceInfoHolder);
        this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties, serviceInfoHolder);
    }
    // ... delegate methods forward to httpClientProxy or grpcClientProxy
}

This design shows a flexible use of the proxy pattern: a single delegate proxy abstracts away the choice of underlying protocol, while still allowing each concrete proxy to focus on its specific transport logic.

Proxy vs. Decorator Pattern

Although both patterns involve wrapping an object, they serve different intents:

Decorator enhances the wrapped object's functionality (adds new behavior or state).

Proxy controls access to the wrapped object, often adding cross‑cutting concerns such as logging, caching, or security, without changing the object's core behavior.

Decorator is typically transparent to the client, whereas a proxy may be used explicitly to manage resource usage or enforce policies.

Conclusion

The article presented a clear walkthrough of the static proxy pattern, provided a concrete Java example, and examined its practical application within the Nacos service discovery client. While the Nacos implementation demonstrates a flexible and extensible use of proxies, naming choices (e.g., using “Proxy” suffix for both abstract and concrete classes) can cause confusion, suggesting room for improvement in code clarity.

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 PatternsJavaBackend Developmentservice discoveryNacosProxy Pattern
Senior Brother's Insights
Written by

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'.

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.