A, configuration,

MyBatis allows you to intercept calls at certain points during the execution of mapped statements. By default, MyBatis allows plugins to intercept method calls including Executor, ParameterHandler, ResultSetHandler, and StatementHandler.

These methods were introduced in the first chapter of the article. They are the executor, the parameter handler, the return result set handler, and the Statement handler. Typically, we define them in AN 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>
Copy the code

When building the SqlSessionFactory, Mybatis checks to see if the plug-in is configured. If there is one, it is easy to add it 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);
	}
}
Copy the code

Create a class that provides the Interceptor interface, and declare the interface name, method name, and parameter list via @intercepts. For example, in the following example, the interception interface is declared as Executor, the method is named query, and the argument 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) {}
}
Copy the code

Create an agent

Plug-ins are the process of creating proxies. In the example above, you create a proxy class for the Executor interface that calls the Plugin class. The Plugin. Invoke (Invocation) is actually called when the Executor.query() method is executed. Let’s say there are four methods of interception, so let’s take a look at one by one and see how they actually work.

1, the Executor

As we saw in the previous section, Mybatis creates a sqlSession object. When sqlSession is created, an executor is created. The executor.query method is the one called at the beginning, while the SQL is still in the unparsed state of the sqlNode nodes one by one.

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 executor = new SimpleExecutor(this, transaction); } // The default istrueWrapper SimpleExecutor as a CachingExecutor objectif(cacheEnabled) { executor = new CachingExecutor(executor); } / / local agent, if the configuration plug-in, finally returned to the executor is a proxy object executor = (executor) interceptorChain. PluginAll (executor);returnexecutor; }}Copy the code

As you can see, Mybatis creates an actuator based on the type. Then call interceptorChain. PluginAll (executor) to determine whether to need to create agency.

public class InterceptorChain {
	public Object pluginAll(Object target) {
		for (Interceptor interceptor : interceptors) {
			target = interceptor.plugin(target);
		}
		returntarget; }}Copy the code

Interceptor we know that when we build the SqlSessionFactory, we add the configured interceptor to it. In this case, it loops through all custom interceptors, calling their plugin methods. This explains why we use type determination in the Plugin method, otherwise the object returned each time is a proxy object for the last interceptor.

public Object plugin(Object target) {
	if (target instanceof Executor) {
		return Plugin.wrap(target, this);
	}
	return target;
}
Copy the code

If the types match, Plugin’s static method wrap is called and the proxy is actually generated. That is, the @signature annotation generates a proxy for the interface configured on it.

public class Plugin implements InvocationHandler { public static Object wrap(Object target, Interceptor Interceptor) {// Get the @signature interface, method, and parameter Map<Class<? >, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<? >type= target.getClass(); // Get the interface Class<? >[] interfaces = getAllInterfaces(type, signatureMap);
		if(interfaces.length > 0) {// Call JDK methods, return proxy objects // call handler Plugin is this class, which has implemented the InvocationHandler interfacereturn Proxy.newProxyInstance(
			type.getClassLoader(),
			interfaces,
			new Plugin(target, interceptor, signatureMap));
		}
		returntarget; }}Copy the code

Then, invoke() of the Plugin class is actually executed when the executor.query () method is called. The invoke method determines whether the currently invoked method is within the scope of the custom interceptor annotation method and calls 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));
			}
			returnmethod.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); }}}Copy the code

2, StatementHandler

In the execution to SimpleExecutor. The doQuery method, to create StatementStatementHandler object, here also can configure interceptor. At this point, the SQL statement has been parsed, and the statementhandler. prepare method is called to precompile the SQL. Think about it. What can we intercept it for? Of course, their creation process is the same, are called 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);
		returnstatementHandler; }}Copy the code

3, ResultSetHandler

When the PreparedStatementHandler object was instantiated in the previous step, the constructor of its parent class was called, where two objects were created: ResultSetHandler and ParameterHandler. ResultSetHandler is a return value set handler whose handleResultSets method returns the converted Java data set. Think about it. What can we do if we 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); }}Copy the code

It creates the return value is set processor is DefaultResultSetHandler, at the same time, as will call interceptorChain. PluginAll verify whether to create agency.

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);
		returnresultSetHandler; }}Copy the code

Third, summary

This chapter describes the configuration and resolution of three common interceptors, Executor, StatementHandler, ResultSetHandler, and their execution times are as follows:

Executor

After the sqlSession object is generated, the Executor is called for the actual method call.

StatementHandler

After parsing the SQL, create a PreparedStatement object and precompile it and set the parameters.

ResultSetHandler

Get the data from the database, convert it to a Java data set and return it.

Think about it, what can we do with these three types of interceptors? Next time, I’ll show you how to use them.