Backend Development 7 min read

Custom JDK Dynamic Proxy for MyBatis Automatic Mapper Implementation

This article explains how MyBatis can instantiate a mapper interface without an implementation class by using a custom JDK dynamic proxy (referred to as "投鞭断流") that intercepts method calls, creates result objects, and demonstrates the full source code and execution output.

Architecture Digest
Architecture Digest
Architecture Digest
Custom JDK Dynamic Proxy for MyBatis Automatic Mapper Implementation

During a casual conversation the author wondered why MyBatis can return an instance of a mapper interface without any concrete implementation class, which led to an exploration of JDK dynamic proxies and their "投鞭断流" (whip‑cut‑stream) technique for automatic mapper creation.

Dynamic proxy functionality : It intercepts method calls via an InvocationHandler , allowing enhancement of the target method and, in this special mode, completely discarding the original target object.

Custom JDK Dynamic Proxy for Automatic Mapper Creation

First, a simple POJO User is defined:

public class User {
    private Integer id;
    private String name;
    private int age;

    public User(Integer id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    // getter setter
}

Then the mapper interface UserMapper :

public interface UserMapper {
    public User getUserById(Integer id);
}

A custom InvocationHandler named MapperProxy is created to generate proxy instances and to handle method invocations. When the invoked method belongs to Object , it simply forwards the call; otherwise it returns a new User object, effectively implementing the "投鞭断流" logic.

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

public class MapperProxy implements InvocationHandler {
    @SuppressWarnings("unchecked")
    public
T newInstance(Class
clz) {
        return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                // hashCode(), toString(), equals() etc.
                return method.invoke(this, args);
            } catch (Throwable t) { }
        }
        // 投鞭断流 – create a dummy User
        return new User((Integer) args[0], "zhangsan", 18);
    }
}

A test program demonstrates the proxy in action:

public static void main(String[] args) {
    MapperProxy proxy = new MapperProxy();
    UserMapper mapper = proxy.newInstance(UserMapper.class);
    User user = mapper.getUserById(1001);
    System.out.println("ID:" + user.getId());
    System.out.println("Name:" + user.getName());
    System.out.println("Age:" + user.getAge());
    System.out.println(mapper.toString());
}

Running the program produces:

ID:1001
Name:zhangsan
Age:18
x.y.MapperProxy@6bc7c054

This output reveals the underlying mechanism of MyBatis automatic mapper generation.

The article also notes that mapper methods cannot be overloaded because MyBatis uses the fully‑qualified method name as a key to locate the corresponding SQL statement; overloading would cause ambiguity.

Finally, a brief source‑code analysis of MyBatis's own MapperProxy and MapperProxyFactory classes is provided to show how the framework implements the same proxy pattern.

Javabackend developmentReflectionMyBatisDynamic ProxyMapper
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

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