MyBatis Source Code Analysis

Source: Internet
Author: User
Tags creative commons attribution prepare reflection throwable
1. Introduction

In general, the Open source framework provides plug-ins or other forms of outreach that developers can expand on their own. The benefits are obvious and one is to increase the flexibility of the framework. The other is that the developer can expand the framework and make it work better with the actual demand. Taking MyBatis as an example, we can realize the functions of paging, sub-table, monitoring and so on based on MyBatis plug-in mechanism. Because plug-ins are not business-agnostic, the presence of plug-ins is not a business sense. Therefore can be no sense implant plug-in, in the virtually enhanced functionality.

Development MyBatis plug-in need to MyBatis more deep understanding, generally best able to master MyBatis source, the threshold is relatively high. After analyzing the MyBatis plugin mechanism, this article will write a simple page plug-in to help you better master the MyBatis plug-in writing.

2. Plug-in mechanism principle

When we write plug-ins, we need to annotate the plug-in's intercept points with annotations, in addition to having the plug-in class implement the Interceptor interface. The so-called intercept point refers to the plug-in can intercept the method, MyBatis is allowed to intercept the following methods:

    • Executor (update, query, Flushstatements, Commit, rollback, gettransaction, close, isClosed)
    • Parameterhandler (Getparameterobject, Setparameters)
    • Resultsethandler (Handleresultsets, Handleoutputparameters)
    • Statementhandler (Prepare, parameterize, batch, update, query)

If we want to intercept Executor's Query method, we can define the plugin as such.

@Intercepts({    @Signature(        type = Executor.class,        method = "query",        args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}    )})public class ExamplePlugin implements Interceptor {    // 省略逻辑}

In addition, we need to configure the plugin to the relevant files. This allows the MyBatis to load the plug-in at startup and save the plug-in instance to the related object (Interceptorchain, Interceptor chain). After the preparation is done, the MyBatis is in a ready state. When we execute SQL, we need to create the sqlsession through Defaultsqlsessionfactory first. The Executor practice is created during the creation of the sqlsession, and after the Executor instance is created, MyBatis generates the proxy class for the instance through the JDK dynamic proxy. In this way, the plug-in logic can be executed before the Executor correlation method is invoked.

The above is the basic principle of MyBatis plug-in mechanism. Next, let's look at the corresponding source code behind the principle of how.

3. Source code Analysis 3.1 embedded plug-in logic

In this section, I'll take Executor as an example to analyze how MyBatis is embedding plug-in logic for Executor instances. Executor instances are created when sqlsession is turned on, so we analyze them from the source. Let's take a look at the sqlsession opening process.

// -☆- DefaultSqlSessionFactorypublic SqlSession openSession() {    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {    Transaction tx = null;    try {        // 省略部分逻辑                // 创建 Executor        final Executor executor = configuration.newExecutor(tx, execType);        return new DefaultSqlSession(configuration, executor, autoCommit);    }     catch (Exception e) {...}     finally {...}}

The creation process of the Executor is encapsulated in the Configuration, so let's go inside and see.

// -☆- Configurationpublic Executor newExecutor(Transaction transaction, ExecutorType executorType) {    executorType = executorType == null ? defaultExecutorType : executorType;    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;    Executor executor;        // 根据 executorType 创建相应的 Executor 实例    if (ExecutorType.BATCH == executorType) {...}     else if (ExecutorType.REUSE == executorType) {...}     else {        executor = new SimpleExecutor(this, transaction);    }    if (cacheEnabled) {        executor = new CachingExecutor(executor);    }        // 植入插件    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;}

As above, the Newexecutor method, after creating a good Executor instance, immediately interceptorchain the proxy logic for the Executor instance through the interceptor chain. Let's take a look at what the Interceptorchain code is.

public class InterceptorChain {    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();    public Object pluginAll(Object target) {        // 遍历拦截器集合        for (Interceptor interceptor : interceptors) {            // 调用拦截器的 plugin 方法植入相应的插件逻辑            target = interceptor.plugin(target);        }        return target;    }        /** 添加插件实例到 interceptors 集合中 */    public void addInterceptor(Interceptor interceptor) {        interceptors.add(interceptor);    }    /** 获取插件列表 */    public List<Interceptor> getInterceptors() {        return Collections.unmodifiableList(interceptors);    }}

The above is Interceptorchain all the code, relatively simple. Its Pluginall method invokes the specific plug-in's plugin method to populate the corresponding plug-in logic. If there are multiple plug-ins, the plugin method is called multiple times, resulting in a layer-level nested proxy class. The shape is as follows:

When a method of Executor is called, the plug-in logic is executed in advance. The order of execution is outside, for example, in order of execution plugin3 → plugin2 → Plugin1 → Executor .

The plugin method is implemented by a specific plug-in class, but the method code is generally fixed, so find an example below to analyze it.

// -☆- ExamplePluginpublic Object plugin(Object target) {    return Plugin.wrap(target, this);}// -☆- Pluginpublic static Object wrap(Object target, Interceptor interceptor) {    /*     * 获取插件类 @Signature 注解内容,并生成相应的映射结构。形如下面:     * {     *     Executor.class : [query, update, commit],     *     ParameterHandler.class : [getParameterObject, setParameters]     * }     */    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);    Class<?> type = target.getClass();    // 获取目标类实现的接口    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);    if (interfaces.length > 0) {        // 通过 JDK 动态代理为目标类生成代理类        return Proxy.newProxyInstance(            type.getClassLoader(),            interfaces,            new Plugin(target, interceptor, signatureMap));    }    return target;}

As above, the plugin method internally invokes the Wrap method of the plugin class to generate the proxy for the target object. The Plugin class implements the Invocationhandler interface, so it can be passed as a parameter to the Newproxyinstance method of the Proxy.

Here, the logic of plug-in implantation is finished. Next, let's look at how the plug-in logic executes.

3.2 Implementing the plug-in logic

Plugin implements the Invocationhandler interface, so its Invoke method intercepts all method calls. The Invoke method detects the intercepted method to determine whether to execute the plug-in logic. The logic of the method is as follows:

// -☆- Pluginpublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {        /*         * 获取被拦截方法列表,比如:         *    signatureMap.get(Executor.class),可能返回 [query, update, commit]         */        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 code for the Invoke method is relatively small, and the logic is not difficult to understand. First, the Invoke method detects whether the intercepted method is configured in the @Signature annotations of the plug-in, and if so, executes the plug-in logic, otherwise the intercepted method is executed. The plug-in logic is encapsulated in intercept, and the method has a parameter type of invocation. Invocation is primarily used to store target classes, methods, and method parameter lists. Here's a quick look at the definition of the class.

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);    }}

On the implementation of the plug-in logic analysis to this, the whole process is not difficult to understand, we can simply look at it.

4. Implement a paging plugin

In order to better introduce to you the plugin mechanism of MyBatis, I will write a paging plugin for MySQL. Talk is cheap. Show code.

@Intercepts ({@Signature (type = Executor.class,//target class method = "Query",//target methods args ={map Pedstatement.class, Object.class, Rowbounds.class, resulthandler.class}) public class Mysqlpagingplugin implements I    Nterceptor {private static final Integer Mapped_statement_index = 0;    private static final Integer Parameter_index = 1;    private static final Integer Row_bounds_index = 2; @Override public Object intercept (invocation invocation) throws Throwable {object[] args = Invocation.getargs ()        ;        Rowbounds RB = (rowbounds) Args[row_bounds_index];        No paging if (RB = = Rowbounds.default) {return invocation.proceed (); }//Set the original rowbounds parameter to Rowbounds.default, close the MyBatis built-in paging mechanism args[row_bounds_index] = Rowbounds.defau        LT;        Mappedstatement ms = (mappedstatement) Args[mapped_statement_index];        Boundsql boundsql = Ms.getboundsql (Args[parameter_index]); Get SQL statements, stitching LImit statement String sql = Boundsql.getsql ();        String limit = String.Format ("Limit%d,%d", Rb.getoffset (), Rb.getlimit ());        sql = SQL + "" + limit; Create a staticsqlsource and pass the stitched sql into sqlsource sqlsource = new Staticsqlsource (ms.getconfiguration (), SQL, BOUNDSQ        L.getparametermappings ());        Gets and sets the Sqlsource field of Mappedstatement by reflection fields field = MappedStatement.class.getDeclaredField ("Sqlsource");        Field.setaccessible (TRUE);                Field.set (MS, Sqlsource);    Execute intercepted method return Invocation.proceed ();    } @Override public Object plugin (object target) {return Plugin.wrap (target, this); } @Override public void SetProperties (properties properties) {}}

The page plug-in above gets the paging information through the Rowbounds parameter and generates the corresponding limit statement. Then stitch the SQL and use the SQL as a parameter to create the Staticsqlsource. Finally, the Sqlsource field in the Mappedstatement object is replaced with reflection. In the above code there are some unfamiliar classes, such as Boundsql,mappedstatement and Staticsqlsource, here is a simple explanation. Boundsql contains the parsed SQL statements, and the parameters passed in by the consumer at runtime, which are eventually set to SQL. Mappedstatement corresponds to nodes in the mapping file <select>,<insert>, including configuration information for the nodes, such as Id,fetchsize and Sqlsource. Staticsqlsource is one of the Sqlsource implementation classes that contains fully parsed SQL statements. The so-called full resolution refers to placeholders such as ${xxx} or #{xxx} that are not included in the SQL statement, and other unresolved dynamic nodes, such as <if>,<where>. So much about these classes, if you still don't understand, look at the articles I wrote earlier. Next, write the test code to verify that the plugin is working properly. Let's take a look at the Dao interface and the definition of the mapping file:

public interface StudentDao {    List<Student> findByPaging(@Param("id") Integer id, RowBounds rb);}
<mapper namespace="xyz.coolblog.dao6.StudentDao">    <select id="findByPaging" resultType="xyz.coolblog.model5.Student">        SELECT            `id`, `name`, `age`        FROM            student        WHERE            id > #{id}    </select></mapper>

The test code is as follows:

public class PluginTest {    private SqlSessionFactory sqlSessionFactory;    @Before    public void prepare() throws IOException {        String resource = "mybatis-plugin-config.xml";        InputStream inputStream = Resources.getResourceAsStream(resource);        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);        inputStream.close();    }    @Test    public void testPlugin() {        SqlSession session = sqlSessionFactory.openSession();        try {            StudentDao studentDao = session.getMapper(StudentDao.class);            studentDao.findByPaging(1, new RowBounds(20, 10));        } finally {            session.close();        }    }}

After the above code is run, the following log is printed.

In the above output, the SQL statement contains the word LIMIT, which indicates that the plug-in is in effect.

5. Summary

To this, about the MyBatis plug-in mechanism is finished analysis. Overall, the MyBatis plug-in mechanism is relatively simple. But the implementation of a plug-in is more complex, need to MyBatis a better understanding of the line. Therefore, if you want to write efficient plug-ins, you need to learn the source code.

Well, this article will be here first. Thank you for reading.

Appendix: MyBatis Source Analysis Series Articles List
Update Time title
2018-09-11 MyBatis Source Analysis Series Articles Collection
2018-07-16 MyBatis Source Analysis Series Articles Guide
2018-07-20 MyBatis Source Analysis-configuration file parsing process
2018-07-30 MyBatis Source Code Analysis-mapping file parsing process
2018-08-17 MyBatis Source code Analysis-SQL execution process
2018-08-19 MyBatis Source Analysis-built-in data source
2018-08-25 MyBatis Source code Analysis-Caching principle
2018-08-26 MyBatis Source Analysis-plugin mechanism

This article is published under the Knowledge Sharing License Agreement 4.0 and must be marked in a prominent location.
Tian Xiaobo
This document is posted in my personal blog: www.tianxiaobo.com


This work is licensed under the Creative Commons Attribution-NonCommercial use-no derivative of the 4.0 International License Agreement.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.