NOTE
Proxy Pattern
The proxy pattern adds functionality around an existing class or function without changing the original code, with static, dynamic, AOP, Go, and cache-proxy examples.
This is a historical learning note and may contain outdated or incomplete understanding.
1. Definition
Add new functionality to a class or function without changing the original code.
I do not want to do things unrelated to my business logic.
2. UML
3. Java
3.1. Static Proxy
The proxy class is generated at compile time and needs to be written manually by the programmer.
3.1.1. Client
public class Client
{
public static void main(String[] args)
{
Proxy proxy = new Proxy(new RealImpl());
proxy.run();
}
}
3.1.2. Proxy
public class Proxy implements Real
{
private Real real;
public Proxy(Real real)
{
this.real = real;
}
public void run()
{
System.out.println("before running");
this.real.run();
System.out.println("after running");
}
}
3.1.3. Proxied Object
public interface Real
{
void run();
}
public class RealImpl implements Real
{
@Override
public void run()
{
System.out.println("Real run");
}
}
3.2. Dynamic Proxy
The proxy class is generated at runtime using Java’s reflection mechanism.
3.2.1. JDK Dynamic Proxy
Dynamic proxying is implemented through an interface, so the target class needs an interface.
3.2.1.1. Client
public class Client
{
public static void main(String[] args)
{
Proxy proxy = new Proxy(new RealImpl());
Real obj = (Real) proxy.getProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Real.class},
proxy);
obj.run();
}
}
3.2.1.2. Proxy
// The proxy class does not need to implement the proxied class's interface
public class Proxy implements InvocationHandler
{
private Real real;
public Proxy(Real real)
{
this.real = real;
}
// Generate a proxy object for the target object
public Object getProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
return java.lang.reflect.Proxy.newProxyInstance(loader, interfaces, h);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
System.out.println("*************************");
method.invoke(real, args);
System.out.println("*************************");
return null;
}
}
3.2.1.3. Proxied Object
public interface Real
{
void run();
}
public class RealImpl implements Real
{
@Override
public void run()
{
System.out.println("Real run");
}
}
3.2.2. CGLIB
Implemented by inheriting from the target class. It cannot proxy classes marked final.
3.2.2.1. Client
public class Client
{
public static void main(String[] args)
{
Proxy proxy = new Proxy(new Real());
Real obj = (Real) proxy.getProxyInstance();
obj.run();
}
}
3.2.2.2. Proxy
public class Proxy implements MethodInterceptor
{
private Real real;
public Proxy(Real real)
{
this.real = real;
}
// Generate a proxy object for the target object
public Object getProxyInstance() {
// Utility class
Enhancer en = new Enhancer();
// Set the superclass
en.setSuperclass(real.getClass());
// Set the callback function
en.setCallback(this);
// Create a proxy for the subclass object
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
{
System.out.println("before run");
// Execute the target object's method
Object returnValue = method.invoke(real, objects);
System.out.println("after run");
return null;
}
}
3.2.2.3. Proxied Object
public class Real
{
public void run()
{
System.out.println("Real run");
}
}
3.3. Spring AOP
- If the target class implements an interface:
- JDK dynamic proxy is used by default.
- CGLIB can also be forced.
- If the target class does not implement an interface:
- CGLIB is used.
4. Golang
4.1. Proxied Object
/* Interface */
type IReal interface {
Do()
}
/* Concrete object */
type Real struct {
}
func NewReal() *Real {
return &Real{}
}
func (r Real) Do() {
fmt.Println("concrete object doing...")
}
4.2. Proxy
/* Proxy object */
type Proxy struct {
Real IReal
}
func NewProxy(real IReal) *Proxy {
return &Proxy{Real: real}
}
func (p Proxy) Do() {
fmt.Println("before")
p.Real.Do()
fmt.Println("after")
}
4.3. Client
func main() {
real := 代理.NewReal()
proxy := 代理.NewProxy(real)
proxy.Do()
}
5. Example
5.1. Cache Proxy

- cache/cache.go
package cache
import (
"context"
"fmt"
lru "github.com/hnlq715/golang-lru"
)
var ActIdCache *lru.ARCCache
func init() {
ActIdCache, _ = lru.NewARC(100)
}
type CacheableFunc = func(ctx context.Context, key fmt.Stringer) (interface{}, error)
// Cache the value computed by fn; do not cache it if fn returns an error
func WithCache(lruCache *lru.ARCCache,
fn func(ctx context.Context, key fmt.Stringer) (interface{}, error),
ctx context.Context, key fmt.Stringer) (interface{}, error) {
if value, ok := lruCache.Get(key.String()); ok {
fmt.Printf("WithCache: get value %v from cache for key %v
", value, key.String())
return value, nil
}
value, err := fn(ctx, key)
if err == nil {
fmt.Printf("WithCache: add value %v to cache for key %v
", value, key.String())
lruCache.Add(key.String(), value)
}
return value, err
}
func WithFallbackCache(lruCache *lru.ARCCache,
fn func(ctx context.Context, key fmt.Stringer) (interface{}, error),
ctx context.Context, key fmt.Stringer) (interface{}, error) {
value, err := fn(ctx, key)
if err == nil {
fmt.Printf("WithFallbackCache: add value %v to cache for key %v
", value, key.String())
lruCache.Add(key.String(), value)
return value, nil
}
if value, ok := lruCache.Get(key.String()); ok {
fmt.Printf("WithFallbackCache: get value %v from cache for key %v
", value, key.String())
return value, nil
}
return value, err
}
- model/model.go
package model
import "fmt"
type ActIdCacheParam struct {
Source string
}
func NewActIdCacheParam(source string) *ActIdCacheParam {
return &ActIdCacheParam{Source: source}
}
func (a *ActIdCacheParam) String() string {
return fmt.Sprintf("%s", a.Source)
}
- service/service.go
package service
import (
"context"
"fmt"
"strconv"
"test/proxy/cache"
"test/proxy/model"
)
// Get actId from source
// Using cache.CacheableFunc indicates that this function implements the cacheable interface
var getActIdBySourceFunc cache.CacheableFunc = func(ctx context.Context, key fmt.Stringer) (interface{}, error) {
actIdCacheParam := key.(*model.ActIdCacheParam)
actId, err := strconv.Atoi(actIdCacheParam.Source)
return actId, err
}
func GetActIdBySource(source string) (int, error) {
cachedValue, err := cache.WithCache(cache.ActIdCache, getActIdBySourceFunc, context.Background(),
model.NewActIdCacheParam(source))
if err != nil {
return 0, err
}
actId := cachedValue.(int)
return actId, nil
}
- proxy_test.go
package proxy
import (
"test/proxy/service"
"testing"
)
func TestProxy(t *testing.T) {
service.GetActIdBySource("11")
service.GetActIdBySource("11")
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub