Plugin Introduction

Generally, open-source frameworks provide plugins or extension points for developers to extend on their own. The benefits are obvious: first, it increases the flexibility of the framework; second, developers can extend the framework according to actual needs to make it work better.

Taking MyBatis as an example, we can implement pagination, table sharding, and monitoring functions based on the MyBatis plugin mechanism. Since plugins are unrelated to business and businesses cannot perceive the existence of plugins, plugins can be seamlessly integrated to achieve enhancement invisibly.

MyBatis plugin mechanism allows users to extend MyBatis functionality by implementing plugin interfaces, intercepting SQL statement execution process for custom processing.

Basic Concepts

MyBatis plugin mechanism is based on Java’s interceptor pattern, using plugin classes to intercept and modify MyBatis core behavior. Plugins can insert custom code when performing specific operations (such as query, update, delete, etc.).

Scope of Action

The objects of plugin action are MyBatis internal core components: Executor, StatementHandler, ParameterHandler, and ResultSetHandler:

  • Executor: Operations that execute SQL
  • StatementHandler: Handles SQL statement generation, parameter setting, etc.
  • ResultSetHandler: Handles result set mapping
  • ParameterHandler: Handles SQL statement parameter setting

Common Scenarios

  • SQL logging
  • SQL performance monitoring
  • Transaction control
  • Caching
  • SQL rewriting

Advantages

  • Flexibility: Allows developers to flexibly add or modify functions without modifying MyBatis core code
  • Extensibility: Can easily extend MyBatis to meet various business needs
  • Customizability: Plugins can be customized according to business needs

Disadvantages

  • Complex Debugging: Since plugins are implemented through proxy pattern, debugging may increase complexity
  • Performance Overhead: Every SQL execution needs to go through plugin interception and processing

Plugin Introduction

MyBatis, as a widely used excellent ORM open-source framework, provides a simple and easy-to-use plugin extension mechanism for four major components: Executor, StatementHandler, ParameterHandler, ResultSetHandler.

MyBatis allows intercepting the following methods:

  • Executor: update, query, commit, rollback, etc.
  • StatementHandler: prepare, parameterize, batch, updates query, etc.
  • ParameterHandler: getParameterObject, setParameters methods
  • ResultSetHandler: handlerResultSets, handleOutputParameters, etc.

Plugin Principles

When the four core objects are created, each created object is not returned directly. Instead, it goes through interceptorChain.pluginAll(parameterHandler).

public ParameterHandler createParameterHandler(
        MappedStatement mappedStatement,
        Object parameter,
        BoundSql boundSql,
        InterceptorChain interceptorChain) {

    ParameterHandler parameterHandler =
            mappedStatement.getLang().createParameterHandler(mappedStatement, parameter, boundSql);

    parameterHandler = (ParameterHandler) interceptorChain.applyInterceptors(parameterHandler);

    return parameterHandler;
}

public Object applyInterceptors(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

interceptorChain saves all interceptors, and the interceptor chain calls interceptors in sequence to intercept or enhance the target.

Custom Plugin

The MyBatis plugin interface Interceptor contains three methods:

  • interceptor method: The core method of the plugin
  • plugin method: Generates a proxy object for the target
  • setProperties method: Passes parameters required by the plugin
@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class ExamplePlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

Configure the plugin:

<plugins>
    <plugin interceptor="icu.wzk.interceptor.ExamplePlugin"></plugin>
</plugins>

Source Code Analysis

Plugin implements the InvocationHandler interface, and the invoke method detects all intercepted methods:

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)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}

The Invocation class is used to store the target class, method, and method parameter list:

public class Invocation {
    private final Object target;
    private final Method method;
    private final Object[] args;

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
}