How to Build Custom MyBatis Plugins: A Step‑by‑Step Guide
This article explains how MyBatis plugins work, shows how to create a custom interceptor with Java code, configure it in XML, and details the underlying mechanism that uses dynamic proxies to intercept Executor methods.
Overview
MyBatis allows you to intercept method calls at specific points during the execution of mapped statements. By default, the framework provides plugin interception for the following components:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
The method signatures inside the parentheses indicate the methods that can be intercepted.
Custom Plugin Development
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
public class ExecutorPlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
System.out.println(properties);
// implement pre‑processing if needed
Object returnObject = invocation.proceed();
// implement post‑processing if needed
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}Configuring the Plugin
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.pack.interceptors.ExecutorPlugin">
<property name="pageSize" value="100" />
</plugin>
</plugins>
</configuration>After adding the above configuration, the custom plugin is active.
Principle Analysis
Entry Point
// Obtain a SqlSession instance via the factory
SqlSession session = sqlSessionFactory.openSession();Creating the Executor
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// ...
// Create the executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
// ...
}
}The Configuration#newExecutor method builds the executor:
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// Apply interceptors
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}Applying Interceptors
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
// Iterate over interceptor objects
for (Interceptor interceptor : interceptors) {
// Core method: execute Interceptor#plugin
target = interceptor.plugin(target);
}
return target;
}
// ...
}Interceptor Interface
public interface Interceptor {
// Method that custom interceptors must implement
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
// Wrap the target (e.g., Executor) with the custom interceptor
return Plugin.wrap(target, this);
}
// ...
}Plugin Implementation (Dynamic Proxy)
public class Plugin implements InvocationHandler {
public static Object wrap(Object target, Interceptor interceptor) {
// Build a map of signatures from @Intercepts annotation
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// Get all interfaces of the target that need to be proxied
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// Create proxy object
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// Intercepted method; invoke interceptor
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}Through the analysis above, MyBatis interceptors achieve method interception by dynamically proxying the concrete Executor object.
End of tutorial.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
