We start from six aspects: plug-in configuration, plug-in writing, plug-in operation principle, plug-in registration and interception time, initialization plug-in, paging plug-in principle.
1. Plug in configuration
Mybatis plug-ins are configured in the configuration. During initialization, these plug-ins will be read and saved in the InterceptorChain of the configuration object.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <plugins> <plugin interceptor="com.mybatis3.interceptor.MyBatisInterceptor"> <property name="value" value="100" /> </plugin> </plugins> </configuration> public class Configuration { protected final InterceptorChain interceptorChain = new InterceptorChain(); }
org.apache.ibatis.plugin.InterceptorChain.java source code.
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } }
The for loop above represents that as long as it is a plug-in, it will execute one by one in the way of responsibility chain (don't expect it to skip a node). The so-called plug-in is actually similar to an interceptor.
2. How to write a plug-in
The plug-in must implement the org.apache.ibatis.plugin.Interceptor interface.
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
intercept() method: the place where the intercepted content is executed, for example, if you want to charge a protection fee. Triggered by the plugin() method, interceptor.plugin(target) is sufficient.
plugin() method: determines whether to trigger the intercept() method.
setProperties() method: pass the xml configured property parameters to the custom interceptor.
Here is a custom Interceptor:
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }), @Signature(type = Executor.class, method = "close", args = { boolean.class }) }) public class MyBatisInterceptor implements Interceptor { private Integer value; @Override public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } @Override public Object plugin(Object target) { System.out.println(value); // Plugin class is the core class of plug-in, which is used to create a dynamic proxy object of JDK for target and trigger the intercept() method return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { value = Integer.valueOf((String) properties.get("value")); } }
Facing the above code, we need to solve two questions:
- Why annotate? What does Annotation mean?
Answer: Mybatis stipulates that the plug-in must write Annotation annotation, which is required rather than optional.
@Comments on Intercepts: to load a list of @ signatures, an @ Signature is actually a method encapsulation that needs to be intercepted. that
Well, if an interceptor wants to intercept multiple methods, it is naturally a @ Signature list.
type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }
Explanation: to intercept the query() method in the Executor interface, the parameter type is args list.
type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }
Explanation: to intercept the query() method in the Executor interface, the parameter type is args list. - What is Plugin.wrap(target, this)?
Answer: use JDK's dynamic proxy to create a delegate proxy object for the target object, so as to realize method interception and enhancement
Yes, it will call back the intercept() method.
org.apache.ibatis.plugin.Plugin.java source code:
public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; private Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // Create JDK dynamic proxy object return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); // Determine whether it is a method to intercept (very important) if (methods != null && methods.contains(method)) { // Callback the intercept() method return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } //... }
Map < class <? >
Fruit.
So, let's not just say that the reflection performance is poor, because you don't cache the reflection node of an object like Mybatis
Fruit.
This comment is very important to judge whether it is a method to intercept. Once it is ignored, I don't know how Mybatis judges whether to intercept or not
Cut the content, remember.
3. What interface objects can mybatis intercept?
public class Configuration { //... public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); // 1 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); // 2 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); // 3 return statementHandler; } public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 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); // 4 return executor; } //... }
Mybatis can only intercept 4 interfaces, including ParameterHandler, ResultSetHandler, StatementHandler and Executor
Object.
Reexamine the interceptorChain.pluginAll() method: this method is called when the above four interface objects are created, and its meaning is to give these
The interface object registers the interceptor function. Note that it registers instead of intercepting.
Interceptor execution time: after the plugin() method registers the interceptor, when executing the specific methods in the above four interface objects, it will
The execution of the interceptor is triggered dynamically, that is, the execution of the plug-in.
Therefore, we must distinguish when to register and when to execute. Do not think pluginAll() or plugin() is execution, it is just registration.
4. Invocation
public class Invocation { private Object target; private Method method; private Object[] args; }
The parameter Invocation of the intercept(Invocation invocation) method
5. Initialization plug-in source code analysis
Part of the source code of org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode) method.
pluginElement(root.evalNode("plugins")); private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // This shows when to call the setProperties() method interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
For Mybatis, it doesn't distinguish the Interceptor interface. All plug-ins are interceptors, and Mybatis completely depends on
Annotation identifies who is intercepted, so it has interface consistency.
6. Principle of paging plug-in
Since mybatis uses logical paging instead of physical paging, there is a paging plug-in of mybatis that can implement physical paging in the market. To implement physical paging, String sql needs to be intercepted and enhanced. Mybatis stores String sql through the BoundSql object, while BoundSql is obtained by the StatementHandler object.
public interface StatementHandler { <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException{ String sql = getBoundSql(); //Paging statement: sql+"limit Sentence" //Total queries statement:"SELECT COUNT(1) "" +sql.substring(from After statement) }; BoundSql getBoundSql(); } public class BoundSql { public String getSql() { return sql; } }
Therefore, we need to write a query method interceptor for StatementHandler, and then get the sql to rewrite and enhance the sql.