1, Disposition
MyBatis allows intercepting calls at some point during the execution of mapped statements. By default, MyBatis allows plug-ins to intercept method calls, including Executor, ParameterHandler, ResultSetHandler and StatementHandler.
These methods are executor, parameter processor, return result set processor and Statement processor. Typically, we define them in the xml file through the plugins attribute.
<property name="plugins"> <array> <bean class="com.viewscenes.netsupervisor.interceptor.ExecutorIntercepor"></bean> <bean class="com.viewscenes.netsupervisor.interceptor.ResultSetInterceptor"></bean> <bean class="com.viewscenes.netsupervisor.interceptor.PageInterceptor"></bean> </array> </property>
Then, when building SqlSessionFactory, Mybatis will check whether the plug-in is configured. If so, it is also relatively simple to add to the interceptors collection.
if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); } } public class InterceptorChain { public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } }
Then create a new class to implement the Interceptor interface, and declare the interface name, method name and parameter list through @ Intercepts to implement the plug-in. For example, in the following example, the intercepting interface is declared as Executor, the method name is query, and the parameter is args.
@Intercepts({@Signature(type = Executor.class, method = "query", args = { MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})}) public class ExecutorIntercepor implements Interceptor{ public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } public Object plugin(Object target) { if (target instanceof Executor){ return Plugin.wrap(target, this); } return target; } public void setProperties(Properties properties) {} }
2, Create proxy
The so-called plug-in is actually the process of creating an agent. In the above example, the agent class of the Executor interface is created, and the caller processor is the Plugin class. When executing the Executor.query() method, the actual call is Plugin. Invoke (invocation). We say that the interception methods include the above four methods. Let's look at them one by one. How do they achieve interception.
1,Executor
SQL execution process In, Mybatis will first create an sqlSession object. When creating sqlSession, an executor will be created. The Executor.query method is called from the beginning. At this time, the SQL is still in the unresolved state of one sqlNode after another.
public class Configuration { public Executor newExecutor(Transaction transaction, ExecutorType executorType) { Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { //Default actuator executor = new SimpleExecutor(this, transaction); } //Default to true,hold SimpleExecutor Package into CachingExecutor object if (cacheEnabled) { executor = new CachingExecutor(executor); } //Where agents are generated,If the plug-in is configured, the last returned executor It's a proxy object executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
As you can see, Mybatis will create an actuator based on the type. Then interceptorChain.pluginAll(executor) is invoked to determine whether agents need to be generated.
public class InterceptorChain { public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } }
Interceptor we know that when building SqlSessionFactory, the configured interceptor is added to it. So here, it loops through all custom interceptors and calls its plugin method. This explains why we need to judge the type in the plugin method, otherwise the object returned each time is the proxy object of the last interceptor.
public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; }
If the type matches, the static method wrap of Plugin will be called to actually generate the proxy. In other words, the proxy of which interface is generated according to the interface configured on the @ Signature annotation.
public class Plugin implements InvocationHandler { public static Object wrap(Object target, Interceptor interceptor) { //obtain@Signature Annotated interfaces, methods, and parameters Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); //Gets the interface implemented by the target class Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { //call JDK Method to return the proxy object //Calling program processor Plugin This is the class, which has been implemented InvocationHandler Interface return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } }
Then, when the Executor.query() method is called, the invoke() of the Plugin class is actually executed. In the invoke method, it will determine whether the current call method is within the scope of the custom interceptor annotation method, and then call its intercept method.
public class Plugin implements InvocationHandler { 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); } } }
2,StatementHandler
To create a StatementStatementHandler object when executing the simpleexecution.doquery method, you can also configure the interceptor here. At this time, the SQL statement has been parsed and the StatementHandler.prepare method is called to precompile the SQL. Think about it. What can we do to intercept it? Of course, their creation process is the same, calling interceptorChain.pluginAll(executor).
public class Configuration { 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; } }
3,ResultSetHandler
When instantiating the PreparedStatementHandler object in the previous step, the constructor of its parent class will be called, and two objects are created here: ResultSetHandler and ParameterHandler. ResultSetHandler is a return value collection processing class. Its handleResultSets method returns the converted Java data collection. You can think about what you can do if you intercept here?
public abstract class BaseStatementHandler{ protected final ResultSetHandler resultSetHandler; protected final ParameterHandler parameterHandler; protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); } }
It creates a collection of return values, and the processor is DefaultResultSetHandler. At the same time, it will also call interceptorChain.pluginAll to verify whether to generate an agent.
public class Configuration { 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; } }
3, Summary
This paper describes the configuration mode and parsing process of three common interceptors, namely Executor, StatementHandler and ResultSetHandler. Their execution timing is as follows:
- Executor
After generating the sqlSession object, start calling the Executor to call the actual method.
- StatementHandler
After parsing the SQL, create a PreparedStatement object, precompile and set parameters.
- ResultSetHandler
Get the data from the database, convert it into a Java data set, and then return.