Prospects for review

The first section from scratch handwritten Mybatis (a) MVP version of our implementation of a most basic can run Mybatis.

As the saying goes, all things are difficult in the beginning, and then in the middle.

Mybatis plug-in mechanism is the second soul of Mybatis besides dynamic proxy.

Here we come to experience the pain and happiness of this interesting soul ~

What plug-ins do

In the actual development process, we often use Mybaits plug-in is the paging plug-in, through the paging plug-in we can get the data after pagination without writing the count statement and limit, to our development brought great

Convenience. In addition to paging, plug-in usage scenarios mainly include updating database general fields, sub-database sub-tables, encryption and decryption, etc.

This blog is mainly about the principle of Mybatis plug-in. In the next blog, a Mybatis plug-in will be designed to realize the function of generating snowflake ID as the primary key of each data instead of the database self-increasing ID when new data is added.

JDK dynamic proxy + Chain of responsibility design pattern

Mybatis plugin is an interceptor function. It leverages a combination of the JDK dynamic proxy and chain of responsibility design patterns. Using the chain of responsibility model, you can organize multiple interceptors through dynamic proxies, through which you can do whatever you want.

So before we talk about Mybatis interceptor let’s talk about JDK dynamic proxy + responsibility chain design pattern.

JDK dynamic proxy example

package com.github.houbb.mybatis.plugin;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkDynamicProxy {

    /** * an interface */
    public interface HelloService{
        void sayHello(a);
    }

    /** * The target class implements the interface */
    static class HelloServiceImpl implements HelloService{

        @Override
        public void sayHello(a) {
            System.out.println("sayHello......"); }}/** * Custom proxy classes need to implement the InvocationHandler interface */
    static  class HelloInvocationHandler implements InvocationHandler {

        private Object target;

        public HelloInvocationHandler(Object target){
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------ Insert pre-notification code -------------");
            // Execute the corresponding target method
            Object rs = method.invoke(target,args);
            System.out.println("------ Insert post-processing code -------------");
            return rs;
        }

        public static Object wrap(Object target) {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),newHelloInvocationHandler(target)); }}public static void main(String[] args)  {
        HelloService proxyService = (HelloService) HelloInvocationHandler.wrap(newHelloServiceImpl()); proxyService.sayHello(); }}Copy the code
  • The output
------ Insert the pre-notification code ------------- sayHello...... ------ Insert the post-processing code -------------Copy the code

Optimization 1: Object-oriented

HelloInvocationHandler is a dynamic proxy class, which can also be interpreted as a utility class. It is impossible to write business code to or write to invoke methods.

Does not conform to the idea of object-oriented, can be abstracted to deal with.

Defines the interface

You can design an Interceptor interface and do what you need to do to implement the interface.

public interface Interceptor {

    /** * intercepts */
    void intercept(a);

}
Copy the code

Implementing an interface

public class LogInterceptor implements Interceptor{

    @Override
    public void intercept(a) {
        System.out.println("------ Insert pre-notification code -------------"); }}Copy the code

and

public class TransactionInterceptor implements Interceptor{

    @Override
    public void intercept(a) {
        System.out.println("------ Insert post-processing code -------------"); }}Copy the code

To implement proxy

public class InterfaceProxy implements InvocationHandler {

    private Object target;

    private List<Interceptor> interceptorList = new ArrayList<>();

    public InterfaceProxy(Object target, List<Interceptor> interceptorList) {
        this.target = target;
        this.interceptorList = interceptorList;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Handle multiple interceptors
        for (Interceptor interceptor : interceptorList) {
            interceptor.intercept();
        }
        return method.invoke(target, args);
    }

    public static Object wrap(Object target, List<Interceptor> interceptorList) {
        InterfaceProxy targetProxy = new InterfaceProxy(target, interceptorList);
        returnProxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); }}Copy the code

The validation test

public static void main(String[] args) {
    List<Interceptor> interceptorList = new ArrayList<>();
    interceptorList.add(new LogInterceptor());
    interceptorList.add(new TransactionInterceptor());

    HelloService target = new HelloServiceImpl();
    HelloService targetProxy = (HelloService) InterfaceProxy.wrap(target, interceptorList);
    targetProxy.sayHello();
}
Copy the code
  • The log
-- -- -- -- -- -- insert front notification code -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- insert rear processing code -- -- -- -- -- -- -- -- -- -- -- -- -- sayHello...Copy the code

One obvious problem here is that all intercepts are handled before the method is executed.

Optimization 2: Specify before and after flexibly

The dynamic proxy above can indeed pull the business logic out of the proxy class, but we notice that only the front proxy can not do the front proxy, so it needs to be optimized.

So we need to do a further abstraction,

The Interceptor encapsulates the information of the Interceptor object as the parameter of the Interceptor Interceptor method, and puts the real execution method of the Interceptor object into the Interceptor. In this way, the Interceptor can realize the intercepting before and after, and also modify the parameters of the Interceptor object.

Implementation approach

Proxy class context

Design a Invocation object.

public class Invocation {

    /** * Target object */
    private Object target;
    /** * the execution method */
    private Method method;
    /** * method arguments */
    private Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    /** * Executes the target object's method */
    public Object process(a) throws Exception{
        return method.invoke(target,args);
    }

    / / omit Getter/Setter

}
Copy the code

Adjust the interface

  • Interceptor.java
public interface Interceptor {

    /** * intercepts */
    Object intercept(Invocation invocation) throws Exception;

}
Copy the code
  • The logging implementation
public class MyLogInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Exception {
        System.out.println("------ Insert pre-notification code -------------");
        Object result = invocation.process();
        System.out.println("------ Insert post-processing code -------------");
        returnresult; }}Copy the code

Re-implement the proxy class

public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    private Interceptor interceptor;

    public MyInvocationHandler(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Invocation invocation = new Invocation(target, method, args);
        // Returns the result of the proxy class
        return interceptor.intercept(invocation);
    }

    public static Object wrap(Object target, Interceptor interceptor) {
        MyInvocationHandler targetProxy = new MyInvocationHandler(target, interceptor);
        returnProxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); }}Copy the code

The trick is to build the Invocation and then execute the corresponding method.

test

  • code
public static void main(String[] args) {
    HelloService target = new HelloServiceImpl();
    Interceptor interceptor = new MyLogInterceptor();
    HelloService targetProxy = (HelloService) MyInvocationHandler.wrap(target, interceptor);
    targetProxy.sayHello();
}
Copy the code
  • The log
------ Insert the pre-notification code ------------- sayHello...... ------ Insert the post-processing code -------------Copy the code

Optimization 3: Draw boundaries

The interceptor can obtain information about the interceptor object.

But this call to the test code looks awkward, and for the target class, you just need to know what interception has been inserted.

Add a method to the interceptor that inserts the target class.

implementation

Interface to adjust

public interface Interceptor {

    /** * intercepts **@returnMethod execution result *@sinceHundreds * /
    Object intercept(Invocation invocation) throws Exception;

    /** * inserts the target class **@returnThe proxy *@sinceHundreds * /
    Object plugin(Object target);

}
Copy the code

adjustment

You can think of it as adjusting static methods to object methods.

public class MyLogInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Exception {
        System.out.println("------ Insert pre-notification code -------------");
        Object result = invocation.process();
        System.out.println("------ Insert post-processing code -------------");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return MyInvocationHandler.wrap(target, this); }}Copy the code

test

  • code
public static void main(String[] args) {
    HelloService target = new HelloServiceImpl();
    Interceptor interceptor = new MyLogInterceptor();
    HelloService targetProxy = (HelloService) interceptor.plugin(target);
    targetProxy.sayHello();
}
Copy the code
  • The log
------ Insert the pre-notification code ------------- sayHello...... ------ Insert the post-processing code -------------Copy the code

Chain of Responsibility model

How to handle multiple interceptors?

The test code

public static void main(String[] args) {
    HelloService target = new HelloServiceImpl();
    //1. Interceptor 1
    Interceptor interceptor = new MyLogInterceptor();
    target = (HelloService) interceptor.plugin(target);
    //2. Interceptor 2
    Interceptor interceptor2 = new MyTransactionInterceptor();
    target = (HelloService) interceptor2.plugin(target);
    / / call
    target.sayHello();
}
Copy the code

MyTransactionInterceptor is implemented as follows:

public class MyTransactionInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Exception {
        System.out.println("------tx start-------------");
        Object result = invocation.process();
        System.out.println("------tx end-------------");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return MyInvocationHandler.wrap(target, this); }}Copy the code

The log is as follows:

-- -- -- -- -- - tx start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- insert front notification code -- -- -- -- -- -- -- -- -- -- -- -- -- sayHello... -- -- -- -- -- - inserted into the rear processing code -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the tx end -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code

Of course, many partners have already thought of using the chain of responsibility mode when they see this. Let’s take a look at the chain of responsibility mode.

Chain of Responsibility model

Chain of Responsibility model

public class InterceptorChain {

    private List<Interceptor> interceptorList = new ArrayList<>();

    /** * Insert all interceptors */
    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptorList) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptorList.add(interceptor);
    }
    /** * returns an unmodifiable collection that can only be added by the addInterceptor method so that control is in your own hands */
    public List<Interceptor> getInterceptorList(a) {
        returnCollections.unmodifiableList(interceptorList); }}Copy the code

test

public static void main(String[] args) {
    HelloService target = new HelloServiceImpl();

    Interceptor interceptor = new MyLogInterceptor();
    Interceptor interceptor2 = new MyTransactionInterceptor();
    InterceptorChain chain = new InterceptorChain();
    chain.addInterceptor(interceptor);
    chain.addInterceptor(interceptor2);

    target = (HelloService) chain.pluginAll(target);
    / / call
    target.sayHello();
}
Copy the code
  • The log
-- -- -- -- -- - tx start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- insert front notification code -- -- -- -- -- -- -- -- -- -- -- -- -- sayHello... -- -- -- -- -- - inserted into the rear processing code -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the tx end -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code

Personal reflections

Can interceptors be improved?

In fact, I feel that there could be a different Angle here, such as when defining the interceptor interface:

This way, you don’t have to write the execution part of the code, so it’s easier to implement and you won’t forget.

public interface Interceptor {

    /** * intercepts */
    void before(Invocation invacation);

    /** * intercepts */
    void after(Invocation invacation);

}
Copy the code

However, this also has the disadvantage of not being visible to the parts of the process execution, thus losing some flexibility.

Abstract implementation

For the plugin() method, the implementation is actually pretty fixed.

If the interface is invisible, it can be directly placed in the chain.

Hand-written Mybatis introduction plugin

Having said that, the plug-in implementation part is a piece of cake if you understand it.

It’s just a simple implementation of the above idea.

Rapid experience

config.xml

The plug-in is introduced and the rest is omitted.

<plugins>
    <plugin interceptor="com.github.houbb.mybatis.plugin.SimpleLogInterceptor"/>
</plugins>
Copy the code

SimpleLogInterceptor.java

We’re simply going to print out the input and output parameters.

public class SimpleLogInterceptor implements Interceptor{
    @Override
    public void before(Invocation invocation) {
        System.out.println("----param: " + Arrays.toString(invocation.getArgs()));
    }

    @Override
    public void after(Invocation invocation, Object result) {
        System.out.println("----result: "+ result); }}Copy the code

Execute test method

The following log output is displayed:

----param: [com.github.houbb.mybatis.config.impl.XmlConfig@3b76982e, MapperMethod{type='select', sql='select * from user where id = ? ', methodName='selectById', resultType=class com.github.houbb.mybatis.domain.User, paramType=class java.lang.Long}, [Ljava.lang.Object; @67011281] ----result: User{id=1, name='luna', password='123456'}
User{id=1, name='luna', password='123456'}
Copy the code

Is not very simple, so how to achieve it?

The core to realize

The interface definition

public interface Interceptor {

    /** * front intercept *@paramInvocation context *@sinceHundreds * /
    void before(Invocation invocation);

    /** * backintercept *@paramInvocation context *@paramResult Execution result *@sinceHundreds * /
    void after(Invocation invocation, Object result);

}
Copy the code

Start the plugin

During openSession(), we start the plugin:

public SqlSession openSession(a) {
    Executor executor = new SimpleExecutor();
    / / 1. Plug-in
    InterceptorChain interceptorChain = new InterceptorChain();
    List<Interceptor> interceptors = config.getInterceptorList();
    interceptorChain.add(interceptors);
    executor = (Executor) interceptorChain.pluginAll(executor);

    / / 2. To create
    return new DefaultSqlSession(config, executor);
}
Copy the code

Here we see a chain of responsibility implemented as follows.

Chain of responsibility

public class InterceptorChain {

    /** * List of interceptors *@sinceHundreds * /
    private final List<Interceptor> interceptorList = new ArrayList<>();

    /** * Add interceptor *@paramInterceptor Interceptor *@return this
     * @sinceHundreds * /
    public synchronized InterceptorChain add(Interceptor interceptor) {
        interceptorList.add(interceptor);

        return this;
    }

    /** * Add interceptor *@paramInterceptorList List of interceptors *@return this
     * @sinceHundreds * /
    public synchronized InterceptorChain add(List<Interceptor> interceptorList) {
        for(Interceptor interceptor : interceptorList) {
            this.add(interceptor);
        }

        return this;
    }

    /** ** agent all *@paramTarget Target class *@returnResults *@sinceHundreds * /
    public Object pluginAll(Object target) {
        for(Interceptor interceptor : interceptorList) {
            target = DefaultInvocationHandler.proxy(target, interceptor);
        }

        returntarget; }}Copy the code

DefaultInvocationHandler is implemented as follows:

/** * The default proxy implementation *@sinceHundreds * /
public class DefaultInvocationHandler implements InvocationHandler {

    /** * Proxy class *@sinceHundreds * /
    private final Object target;

    /** * interceptor *@sinceHundreds * /
    private final Interceptor interceptor;

    public DefaultInvocationHandler(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Invocation invocation = new Invocation(target, method, args);

        interceptor.before(invocation);

        // invoke
        Object result = method.invoke(target, args);

        interceptor.after(invocation, result);

        return result;
    }

    /** * Build proxy *@paramTarget Target object *@paramInterceptor Interceptor *@returnThe proxy *@sinceHundreds * /
    public static Object proxy(Object target, Interceptor interceptor) {
        DefaultInvocationHandler targetProxy = new DefaultInvocationHandler(target, interceptor);
        returnProxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); }}Copy the code

summary

The implementation of this section is not difficult, because it is difficult to understand the overall design concept of Mybatis for plug-ins, and the technical level is still dynamic agent, combined with the design mode of responsibility chain.

After learning this kind of routine, in fact, many similar frameworks can be used for reference in our own implementation.

Develop reading

Hand-write mybatis (a) MVP version from scratch

The resources

Mybatis Framework (8)– Mybatis plug-in principle (Agent + Responsibility chain)