Table of contents
Open Table of contents
Article body
This article analyzes how the second approach, *Mapper mapper = sqlSession.getMapper(*Mapper.class), works.
The focus here is JDK dynamic proxies. For cglib and Byte Buddy proxy examples, see the source repository.
Note: cglib is no longer recommended because it is unmaintained and has problems on JDK 17. Use Byte Buddy instead.
A typical JDK dynamic proxy consists of the following parts.
An interface, ProxyService:
public interface ProxyService {
String print(String message);
}
Its implementation, ProxyServiceImpl:
public class ProxyServiceImpl implements ProxyService {
@Override
public String print(String message) {
return message;
}
}
And the crucial dynamic proxy handler, CommonProxyHandler:
public class CommonProxyHandler implements InvocationHandler {
private Object target;
public CommonProxyHandler(Object tarject) {
this.target = tarject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target,args);
}
}
Finally, here is how to use it:
@SpringBootTest
public class CommonProxyTest {
@Test
public void testCommonProxy(){
// Use Java polymorphism to hold an instantiated ProxyServiceImpl
ProxyService proxyService = new ProxyServiceImpl();
// Pass the target interface implementation instance, ProxyServiceImpl, to the handler
CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService);
// First argument: the current class loader
// Second argument: interfaces implemented by the proxied class
// Third argument: the handler
// Returns the proxy for ProxyServiceImpl
ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(CommonProxyTest.class.getClassLoader(),
proxyService.getClass().getInterfaces(), commonProxyHandler);
String s = proxyInstance.print("我是一个小代理");
System.out.println(s);
}
}
This is a simple implementation of the most common dynamic-proxy approach.
After analyzing JDK dynamic proxies in Part 4, I noticed a problem. If the proxied class is an interface implementation, I should need to implement UserMapper to use a JDK dynamic proxy. Yet MyBatis does not require us to implement Mapper interfaces.
Start debugging.

Before calling an interface method, we can see that the current mapper instance is of type MapperProxy. It came from sqlSession.getMapper(*Mapper.class) in the previous step, so debug into that call.
It calls getMapper on the configuration object, passing the fully qualified class name of UserMapper.

Next, mapperRegistry.getMapper retrieves the corresponding mapperProxyFactory based on the class name and uses it to create a MapperProxy object.
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// Obtain a MapperProxyFactory instance here
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// Begin creating a MapperProxy object here
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
Step into mapperProxyFactory.newInstance(). MapperProxy implements InvocationHandler, so it is equivalent to CommonProxyHandler above.
protected T newInstance(MapperProxy<T> mapperProxy) {
// Does this look familiar?
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// Call the method above
return newInstance(mapperProxy);
}
Pay Special Attention:
Here is the key point. In the initial example, we pass an interface implementation, an Impl class, when constructing CommonPorxyHandler. Here we pass the interface itself. Inspect new MapperProxy to see this.

This creates a proxy object for the UserMapper interface, rather than a proxy for the Impl class as in the usual JDK dynamic-proxy example.
P.S. An attentive reader may ask: before configuration.getMapper(), mapperResitry.getMapper, and the crucial knowMppers.get(), how did the values get added? At project startup, while parsing Mapper.xml and mybatis-config.xml, bindMapperForNamespace calls configuration.addMapper to add them. I specifically highlighted this in red in Part 3; take a look there.
After obtaining the UserMapper proxy, but before calling addUser, another question occurred to me. Part 4 states that the final target must be an interface implementation, or method.invoke() will fail. How does MyBatis avoid this? Step into it:

MyBatis checks the method and does not enter method.invoke(), avoiding that error. Instead, it instantiates a MapperMethod.

Using the supplied method object, it instantiates the command inside MapperMethod. Its name is the fully qualified method path.
It ultimately calls mapperMethod.execute(), which chooses how to execute based on the SQL command type:

The core call is sqlSession.insert(). It is now clear that the arguments are the method name and parameters explicitly supplied to sqlSession in our second approach. From this point, execution is identical to the first approach.
The clever part of MyBatis is that core functionality is delegated to sqlSession, while the user-facing Mapper interface is used through method arguments. It employs another form of JDK proxy—a proxy for the interface rather than for an implementation—to let developers invoke the core functionality with less code.
To be honest, only after reading the MyBatis source did I realize that JDK dynamic proxies are not limited to interface implementations. They can be used in another way. I recommend studying MapperProxy closely to see how MyBatis accepts an interface.
That concludes this rough analysis of parts of MyBatis. The small demos I wrote are on GitHub; feel free to use them. Repository