Proxy Pattern

Concept Introduction

Proxy Pattern: Provides a proxy for an object and controls access to the original object through the proxy. The English name of proxy pattern is Proxy. It is a structural object pattern. Proxy patterns are divided into static proxy and dynamic proxy.

Main Roles

  • Abstract Subject: Defines the common interface of real object and proxy object, enabling proxy objects to be used anywhere a real object is needed.
  • Real Subject: Implements the abstract subject interface and is the object that actually executes requests.
  • Proxy: Implements the abstract subject interface and contains a reference to the real subject. The proxy object controls access to the real object and can perform additional operations before or after access.

Classification of Proxy Patterns

Remote Proxy

Provides a local proxy for objects in different address spaces and communicates with remote objects through the proxy. Example: RMI (Remote Method Invocation).

Virtual Proxy

Used to create objects that are expensive to instantiate, only creating the object when actually needed. Example: Image loader lazy-loading images.

Protection Proxy

Controls access permissions to real objects, executing different operations based on the caller’s permissions. Example: File permission management.

Smart Reference Proxy

Performs additional operations when accessing real objects, such as counting, logging, caching, etc.

Advantages of Proxy Pattern

  • Separation of Concerns: Through proxy-controlled access, additional responsibilities (such as security, logging) can be separated from real objects.
  • Enhanced Functionality: Proxies can dynamically add functionality when accessing real objects.
  • Performance Optimization: Virtual proxy延迟加载 improves performance.

Disadvantages of Proxy Pattern

  • Increased Complexity: Introducing proxy classes leads to increased number of classes and higher system complexity.
  • Performance Issues: In some scenarios, proxies may increase system overhead, especially remote proxies.

Write Code

Person

package icu.wzk.proxy;

public interface Person {
    void doSomething();
}

Bob

package icu.wzk.proxy;

public class Bob implements Person {

    @Override
    public void doSomething() {
        System.out.println("Bob do something!");
    }
}

JdkDynamicProxy

package icu.wzk.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkDynamicProxy implements InvocationHandler {

    private Person target;

    public JdkDynamicProxy(Person person) {
        this.target = person;
    }

    public Person getTarget() {
        return (Person) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before calling " + method.getName());
        Person result = (Person) method.invoke(target, args);
        System.out.println("After calling " + method.getName());
        return result;
    }
}

JdkDynamicProxyTest

package icu.wzk.proxy;

public class JdkDynamicProxyTest {

    public static void main(String[] args) {
        Person bob = new Bob();
        bob.doSomething();
        System.out.println("========================");
        Person bob2 = new Bob();
        JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(bob2);
        Person proxyBob = jdkDynamicProxy.getTarget();
        proxyBob.doSomething();
    }
}

MyBatis Implementation

Proxy pattern can be considered the core pattern used by MyBatis. It is precisely because of this pattern that we only need to write Mapper interfaces without specific implementations. MyBatis backend helps us complete SQL execution.

When we use Configuration’s getMapper method, it calls mapper.Registry.getMapper method, which in turn calls mapperProxyFactory.newInstance(sqlSession) to generate a specific proxy:

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

    // Constructor
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    // Getter for mapperInterface
    public Class<T> getMapperInterface() {
        return mapperInterface;
    }

    // Getter for methodCache
    public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
    }

    // Create a new instance of the proxy
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(
                mapperInterface.getClassLoader(),
                new Class[]{mapperInterface},
                mapperProxy
        );
    }

    // Create a new instance of the mapper with a given SqlSession
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}

Here, through the newInstance(SqlSession sqlSession) method, a MapperProxy object is obtained, then calling T newInstance(MapperProxy mapperProxy) generates a proxy object and returns it. Below is the code for MapperProxy:

public class MapperProxy<T> implements InvocationHandler, Serializable {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // If it's a method in Object class, call directly
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            // If it's a default method, use invokeDefaultMethod to handle
            else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            // Wrap and throw exception
            throw ExceptionUtil.unwrapThrowable(t);
        }

        // Get cached MapperMethod object and execute
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
}

This MapperProxy implements the InvocationHandler interface and implements the invoke method of that interface. Through this approach, we need to write Mapper interface classes. When actually executing a Mapper interface, it is forwarded to MapperProxy.invoke method, which then calls a series of subsequent methods like preparedStatement to complete SQL execution and return.