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.

Software Architecture & EngineeringCreated Updated 1 min readhistorical

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

PlantUML 图表

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")
}

6. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub