Mybatis series article navigation
MyBatis provides plug-in functions, allowing other developers to develop plug-ins for MyBatis to extend the functions of MyBatis. With this plug-in function, developers can extend the functions of MyBatis.
Develop a plug-in
Before learning the source code, we must first know what functions it has and what we can do with this function. So I decided to take you to develop a plug-in to print query results on the console.
Create plug-in class
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object results = invocation.proceed(); System.out.println("Query results:"); for (Object result : ((List) results)) { System.out.println(result); } return results; } }
It can be found that I also declare some annotations on the class. Intercepts indicate that the current class is a plug-in class. Because the interceptor is general, we need to declare the methods we need to intercept through Signature. If there are multiple methods, we need to set multiple signatures.
- type: the name of the class to intercept.
- Method: name of the method to intercept.
- args: type of method parameter to intercept.
Since we intend to intercept the following methods, the configuration is as shown in the above code. And because the return value is List, we can safely convert the result to List type.
public interface ResultSetHandler { <E> List<E> handleResultSets(Statement stmt) throws SQLException; }
Configure plug-ins
In addition to creating the corresponding plug-in class, you also need to create the Configure this plug-in in XML.
<plugins> <plugin interceptor="com.yinxy.plugin.MyInterceptor"/> </plugins>
Effect display
Console print information
Query results: User(id=1, name=Jone, age=18) User(id=2, name=Jack, age=20) User(id=3, name=Tom, age=28) User(id=4, name=Sandy, age=21) User(id=5, name=Billie, age=24) ....
plugin package
Class diagram
[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-lmot9dyk-163048346241) (media / 16300505124116 / 16300531480986. JPEG)]
Plugin
In the plugin package, the most critical is the plugin class, which inherits Java lang.reflect. Invocationhandler is the base dynamic proxy class.
Member variable
// Proxied object private final Object target; // Interceptor private final Interceptor interceptor; // The method to be intercepted is extracted from the Signature annotation private final Map<Class<?>, Set<Method>> signatureMap;
Gets the method to intercept
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { // Get Intercepts annotation Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); if (interceptsAnnotation == null) { throw new PluginException(""); } // Get Signature annotation Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); // Traverse all Signature annotations to get the intercepted method for (Signature sig : sigs) { // Put the same class method into the same Set collection Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>()); try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException(""); } } return signatureMap; }
Generate proxy class
Loading a plug-in is actually generating a proxy class.
public static Object wrap(Object target, Interceptor interceptor) { // Get all the methods that need to be intercepted Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); // Get all the interfaces to be implemented by the proxy object Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // Because JDK dynamic proxy is used, an interface is required to generate the corresponding proxy object return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } // Return the original object directly return target; }
Need interception
Because the plug-in is universal, the Executor may load a plug-in that intercepts ResultSetHandler, so you need to judge whether the plug-in can intercept the proxied object.
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); // Determine whether to intercept the current method of the proxy object 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); } }
Invocation
Invocation is responsible for transferring data between interceptors.
public class Invocation { // Proxied object private final Object target; // Method called private final Method method; // Request parameters private final Object[] args; // Execution method public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } }
InterceptorChain
InterceptorChain stores all interceptors and can load interceptors for objects.
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<>(); public Object pluginAll(Object target) { // Plug in loading for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } // Add plug-in public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } }
In fact, the overall code of the plugin package is not complex. The important thing is that it uses the responsibility chain design pattern. If you don't understand the responsibility chain design pattern, it may be difficult to understand. So far, I have explained the whole MyBatis source code, and the annotated source code has been uploaded to GitHub.
reference
- General source code reading guide: Mybatis source code reading details - Yige