Internet Lightweight Framework SSM - Day 8 (MyBatis Plug-in Plug-in Use and Principles)

Brief: The eighth day was recorded today (only eight days were written).Sometimes I see more, sometimes I see less, and then I save a few days to write together.Today, I wrote this plugin for an afternoon yesterday. I went back after work. I ended it today and added a process diagram to help understand the following principles.I have been reading books for more than half a month. I hope I can develop a habit that I can't quit in a month.

Chapter VIII Plug-ins

In the previous article, four objects were introduced that were used by SqlSession to complete database operations and return results during execution.(Executor, StatementHandler, ParameterHandler, ResultSetHandler).My missing entries and omissions from yesterday are documented. If you want to know more, you can look up the data. Or look at the seventh chapter of this book (titled JavaEE Internet Lightweight Framework Integration Development).

The principle of plug-ins is to insert our code into the dispatch of four objects to perform some special requirements to meet the specific scenario requirements.

Italics are part of the principle.I'll insert a flowchart here first, and if in doubt you can see it at the end and come back to think about it.

 

How to use it: (Example: To print something on the console before precompilation, you need to intercept the precompilation method of the StatementHandler object that executes SQL, also known as the prepare method)

To use a plug-in in MyBatis, you must implement the interceptor interface and implement its three methods (there are comments in the code, what should you know):

package com.ssm.chapter8.plugin;
import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.log4j.Logger;

@Intercepts({
    @Signature(
            type=StatementHandler.class,
            method="prepare",
            args = {Connection.class,Integer.class}
            )
})
public class MyPlugin implements Interceptor {
    private Logger log = Logger.getLogger(MyPlugin.class);
    private Properties props = null;
    /**
     * Plug-in method, which replaces the prepare d method of StatementHandler
     * 
     * @param invocation Entry
     * @return Return precompiled preparedStatement
     * @throws Throwable abnormal
     * */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        //Make a binding
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        Object object = null;
        /*Separate Proxy Object Chain (since the target class may be intercepted by multiple interceptors [plug-ins], resulting in multiple proxies that can be looped to separate the most original target class)*/
        while(metaStatementHandler.hasGetter("h")){
            object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }
        statementHandler = (StatementHandler)object;
        String sql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
        Long parameterObject = (Long)metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        
        log.info("Executing SQL: ["+sql+"]");
        log.info("Parameter: ["+parameterObject+"]");
        log.info("before......");
        //If the current proxy is a non-proxy object, it calls back the method that actually intercepts the object
        //If not, it dispatches the next plug-in proxy object's invoke Method
        Object obj = invocation.proceed();
        log.info("after......");
        return obj;
    }
    /**
     * Generate Proxy Object
     * 
     * @param target Intercepted Object
     * @return Proxy Object
     * */
    public Object plugin(Object target) {
        // Use system default Plugin.wrap Method Generation
        return Plugin.wrap(target, this);
    }
    /**
     * Set parameters, when MyBatis initializes, it generates an instance of the plug-in and calls this method
     * 
     * @param props configuration parameter
     * */
    public void setProperties(Properties props) {
        this.props = props;
        log.info("dbType = "+this.props.getProperty("dbType"));
        
    }
}

The three ways to achieve this goal are to:

  • Interept: Intercept.Simply put, intercept the corresponding method in the corresponding class in the signature (note @Signature above the class name).The parameter Invocation is the integration of blocked content.
  • Plugin: English translation: plugin (computer noun).target is the object being intercepted
  • setProperties: Set parameters.This parameter is to be configured in the XML configuration.

The comment @Intercepts indicates that it is an interceptor.@Singnature is the place where interceptor signatures are registered and can only be intercepted if the signature meets the requirements. Typee can be one of four objects, StatementHandler here.Method represents an interface method to intercept four objects, while args represents the parameters of the method (intercept according to the method to intercept objects).The following is a definition code for the prepare method in StatementHandler.

public abstract Statement prepare(Connection connection, Integer integer) throws SQLException;

So args is a Connection.class and an Integer.class.After interception, the Invocation object reflects the method by which the original object is scheduled.Paste a section of Invocation's source code.

public class Invocation {

    public Invocation(Object target, Method method, Object args[]) {
        this.target = target;
        this.method = method;
        this.args = args;
    }
    public Object getTarget() {
        return target;
    }
    public Method getMethod() {
        return method;
    }
    public Object[] getArgs() {
        return args;
    }
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
    private final Object target;
    private final Method method;
    private final Object args[];
}

At a glance, you know that Target is the target object being intercepted, Method is the method being intercepted, Args is the parameter in the annotation, where proceed is the method calling the method in the original object through reflection.

Configuration XML: Note the order of tags in MyBatis configuration XML.

<plugins><!-- Plug-in unit -->
    <plugin interceptor="com.ssm.chapter8.plugin.MyPlugin">
        <property name="dbType" value="mysql"/>
    </plugin>
</plugins>

The query configuration I use:

<select id="getRole" parameterType="Long" resultType="role">
    select id,role_name as roleName,note from t_role where id = #{id}
</select>

- Execution results:

DEBUG - Reader entry: <?xml version="1.0" encoding="UTF-8"?>
DEBUG - Checking to see if class com.learn.ssm.chapter3.mapper.RoleMapper matches criteria [is assignable to Object]
DEBUG - Cache Hit Ratio [com.learn.ssm.chapter3.mapper.RoleMapper]: 0.0
DEBUG - Opening JDBC Connection
DEBUG - Created connection 407858146.
DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@184f6be2]
 INFO - Executing SQL: [select id,role_name as roleName,note from t_role where id = ?]
 INFO - Parameter: [1]
 INFO - before......
DEBUG - ==>  Preparing: select id,role_name as roleName,note from t_role where id = ? 
 INFO - after......
DEBUG - ==> Parameters: 1(Long)
DEBUG - <==      Total: 1
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@184f6be2]
DEBUG - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@184f6be2]
DEBUG - Returned connection 407858146 to pool.

The red is what is printed by log.info in the Intercept method.It is clear in the middle of before and after that precompiled, that is, the original prepare was called.

Summary:

Comb your ideas and introduce some of the principles you haven't mentioned:

Procedurally, plug-in objects are created when the Configuration is built.The following is part of the XMLConfigBuilder code.

private void parseConfiguration(XNode root) {
     try {
            /*ellipsis*/
            pluginElement(root.evalNode("plugins"));
            /*ellipsis*/
        } catch (Exception e) {
            throw new BuilderException((new StringBuilder()).append("Error parsing SQL Mapper Configuration. Cause: ")
                    .append(e).toString(), e);
        }
}
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        Interceptor interceptorInstance;
        for (Iterator iterator = parent.getChildren().iterator(); iterator.hasNext(); configuration
                    .addInterceptor(interceptorInstance)) {
            XNode child = (XNode) iterator.next();
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
        } } }

When the plugin is initialized, the pluginElement method is called.Reflection technology is used in the pluginElement method to generate the plug-in strength corresponding to the plug-in, then the setProperties method in the plug-in method is called, the parameters we configure are set, and the plug-in instance is saved in the configuration object for reading and using it.So the plug-in's physical object is initialized at the beginning, not when it's used.

In fact, the reflected plug-in instances are stored in interceptorChain (Chain means chain), and the process is segmented.

 

 

The plug-in is ready, and the next step is to intercept it with the plug-in.In the configuration class.Code for the initialization method of the four main objects, another method of interceptorChain is used here:

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
            BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType != null ? executorType : defaultExecutorType;
    executorType = executorType != null ? executorType : ExecutorType.SIMPLE;
    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);
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

Notice the red code. The interceptorChain.pluginAll method is used to facilitate all plug-ins to check whether they intercept the object.

public Object pluginAll(Object target) {
    for (Iterator iterator = interceptors.iterator(); iterator.hasNext();) {
        Interceptor interceptor = (Interceptor) iterator.next();
        target = interceptor.plugin(target);
    }
    return target;
}

The plugin method, which I wrote in the last example.

Now it's time to intercept the prepare method and override it with the intercept method.

The intercept method intercepts the prepared method of StatementHandler. When the four objects are passed to the plugin method, a proxy object is returned. When using the method of the proxy object (in example, the prepared method of StatementHandler), the invoke method is entered for logical processing. This is the key to the proxy mode and can be judged by logic to be inappropriate.The preparemethod returns the intercept method, which is where interception occurs.Writing proxy classes yourself is a heavy workload, and MyBatis provides a common tool class, Plugin, to generate proxy objects.The Plugin class implements the InvocationHandler interface using JDK's dynamic proxy technology.The code is as follows:

public class Plugin implements InvocationHandler {
    public static Object wrap(Object target, Interceptor interceptor) {
    Map signatureMap = getSignatureMap(interceptor);
    Class type = target.getClass();
    Class interfaces[] = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0)
        return Proxy.newProxyInstance(type.getClassLoader(), interfaces,
                new Plugin(target, interceptor, signatureMap));
    else
        return target;
    }
    public Object invoke(Object proxy, Method method, Object args[]) throws Throwable {
    try {
        Set methods = (Set) signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method))
            return interceptor.intercept(new Invocation(target, method, args));
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
    return method.invoke(target, args);
    }
}

In the example, MyPlugin's plugin method calls the Plugin.wrap method to return a proxy object.When the prepare method is called on a proxy object, it enters the invoke method.Signature s are the signatures mentioned above. If there is an intercept method for signatures, the intercept method is called and the result is returned.So what we want to do is done ~

Keywords: Java Apache SQL JDBC Mybatis

Added by tmaiden on Wed, 15 May 2019 23:32:20 +0300