Mybatis插件源碼解讀

一、責任鏈模式

Mybatis插件按照`責任鏈模式`實現。

[責任鏈,菜鳥教程](http://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html)

责任é¾æ¨¡å¼ç UML å¾

最核心的每個Logger的logMessage()方法都可以選擇自己執行或者傳遞給下一個執行,從而形成了一個鏈。

package cn.baopz.module;

/**
 * @author baopz
 */
public abstract class AbstractLogger {
    public static final String INFO = "info";
    public static final String DEBUG = "debug";
    public static final String ERROR = "error";

    private AbstractLogger next;

    public AbstractLogger getNext() {
        return next;
    }

    protected void setNext(AbstractLogger next) {
        this.next = next;
    }

    /**
     * 實現
     * @param level
     */
    public abstract void logger(String level);

}
package cn.baopz.module;

public class Debug extends AbstractLogger {
    @Override
    public void logger(String level) {
        if(level.equals(AbstractLogger.DEBUG)){
            System.out.println("debug 消息處理");
            new Exception("#").printStackTrace();
        }else{
            getNext().logger(level);
        }
    }
}
package cn.baopz.module;

public class Error extends AbstractLogger {
    @Override
    public void logger(String level) {
        if (level.equals(AbstractLogger.ERROR)){
            System.out.println("error 處理");
            new Exception("#").printStackTrace();
        }else{
            getNext().logger(level);
        }
    }
}
package cn.baopz.module;

public class Info extends AbstractLogger {
    @Override
    public void logger(String level) {
        if(level.equals(AbstractLogger.INFO)){
            System.out.println("info 處理");
            new Exception("#").printStackTrace();
        }else {
            getNext().logger(level);
        }
    }
}
package cn.baopz.module;

/**
 * @author baopz
 */
public class Client {
    public static void main(String[] args) {
        AbstractLogger debug = new Debug();
        AbstractLogger info = new Info();
        AbstractLogger error = new Error();

        debug.setNext(info);
        info.setNext(error);
        error.setNext(debug);

        info.logger(AbstractLogger.ERROR);
        info.logger(AbstractLogger.DEBUG);
    }
}

這裏說明一下:new Exception("#").printStackTrace();這段代碼可以打印棧運行軌跡,不錯的調試手段。

結果如下:

error 處理
java.lang.Exception: #
debug 消息處理
	at cn.baopz.module.Error.logger(Error.java:8)
	at cn.baopz.module.Info.logger(Info.java:10)
	at cn.baopz.module.Client.main(Client.java:16)
java.lang.Exception: #
	at cn.baopz.module.Debug.logger(Debug.java:8)
	at cn.baopz.module.Error.logger(Error.java:10)
	at cn.baopz.module.Info.logger(Info.java:10)
	at cn.baopz.module.Client.main(Client.java:17)

二、Mybatis插件核心類

Mybatis中提供一個通用的攔截器,底層使用java的反射機制實現。

org.apache.ibatis.plugin.Interceptor接口,統一實現攔截的接口。

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.plugin;

import java.util.Properties;

/**
 * 插件中需要用的攔截器接口
 * @author Clinton Begin
 */
public interface Interceptor {

  /**
   *
   *執行攔截邏輯的方法
   * @param invocation
   * @return
   * @throws Throwable
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * target被代理的對象
   * @param target
   * @return 返回增強的target
   */
  Object plugin(Object target);

    /**
     * 得到property name="someProperty" 的值,得到配置文件的值
     * <plugins>
     *   <plugin interceptor="org.mybatis.example.ExamplePlugin">
     *     <property name="someProperty" value="100"/>
     *   </plugin>
     * </plugins>
     * @param properties
     */
  void setProperties(Properties properties);

}

在實現該接口的類中添加兩個註解org.apache.ibatis.plugin.Intercepts、org.apache.ibatis.plugin.Signature

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.plugin;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.plugin;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  /**
   * 類名
   * @return
   */
  Class<?> type();

  /**
   * 方法
   * @return
   */
  String method();

  /**
   * 參數
   * @return
   */
  Class<?>[] args();
}

三、插件測試實現

具體實現如下,設置一個被攔截的接口。

package cn.baopz.plugin;

/**
 * 需要被攔截的接口
 * @author baopz
 */
public interface PluginInterface {

    /**
     * 測試被攔截的方法
     * @param test
     */
    void test(String test);

}

實現一個被攔截的實例

package cn.baopz.plugin;

/**
 * @author baopz
 */
public class Target implements PluginInterface {

    @Override
    public void test(String test) {
        System.out.println(test);
    }

}

一個攔截實例

package cn.baopz.plugin;

import org.apache.ibatis.plugin.*;

import java.util.Properties;

/**
 * @author baopz
 */
@Intercepts({@Signature(type = PluginInterface.class, method = "test", args = {String.class})})
public class InterceptorInstance implements Interceptor {
    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] objects = invocation.getArgs();
        for (Object object : objects) {
            System.out.println("被攔截的參數。" + object);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        System.out.println(target.getClass().getName());
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

一個測試實例

package cn.baopz.plugin;

/**
 * @author baopz
 */
public class TestPlugin {

    public static void main(String[] args) {
        PluginInterface pluginInterface = new Target();
        pluginInterface = (PluginInterface) new InterceptorInstance().plugin(pluginInterface);
        pluginInterface.test("hello world");
    }
}

運行結果

cn.baopz.plugin.Target
被攔截的參數。hello world
hello world

測試實例分析:

需要被攔截的實例(PluginInterface pluginInterface = new Target();),通過實現的攔截器實例攔截,反射,實現自己的功能(pluginInterface = (PluginInterface) new InterceptorInstance().plugin(pluginInterface);),得到一個增強的目標實例,在目標實例執行的時候,執行了我們寫的業務邏輯。

注意事項:通過後文的Plugin類,可以得知,如果攔截的是類本身,而非接口,那麼攔截將會失敗,繼而直接執行的是目標類未被增強的方法。

四、目標爭強核心類

org.apache.ibatis.plugin.Plugin、org.apache.ibatis.plugin.Invocation

/**
 * Copyright 2009-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ibatis.plugin;

import org.apache.ibatis.reflection.ExceptionUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * plugin工具類型,反射工具
 * @author Clinton Begin
 */
public class Plugin implements InvocationHandler {

    private final Object target;
    private final Interceptor interceptor;
    private final 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;
    }

    /**
     * @param target 被代理的對象
     * @param interceptor 攔截器,主要用來執行org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        //得到interceptor的簽名
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //被代理的類
        Class<?> type = target.getClass();
        //得到所有被註解的接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }

    /**
     * 具體的包裝類執行方法
     * @param proxy
     * @param method
     * @param args
     * @return object 執行的結構
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //得到目標類所包含的所有方法,method.getDeclaringClass()得到目標類
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            //找到註解的方法
            if (methods != null && methods.contains(method)) {
                //攔截方法,並執行注入到interceptor中的方法
                return interceptor.intercept(new Invocation(target, method, args));
            }
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

    /**
     * 按照類(就是接口,接口在type中也是class的一種,如Map接口是Map.class類),記錄下所有的方法
     * @param interceptor
     * @return
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        //得到所有的註解
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // issue #251
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        //得到註解的所有簽名,可以攔截多個簽名(類名,方法名,參數)
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        //所有的簽名
        for (Signature sig : sigs) {
            //sig.type(),類(接口),多個方法
            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("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }

    /**
     * 獲取目標類型實現的接口
     * @param type
     * @param signatureMap
     * @return
     */
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        //接口容器
        Set<Class<?>> interfaces = new HashSet<Class<?>>();
        //循環把目標類,及其父類的所有被註解標記的接口記錄下來
        while (type != null) {
            //所有接口,
            for (Class<?> c : type.getInterfaces()) {
                //目標類與註解的類一一對應
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            //父類
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }

}
/**
 * Copyright 2009-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 調用
 * @author Clinton Begin
 */
public class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

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

    public Object getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

}

核心代碼分析:

核心代碼

/**
     * @param target 被代理的對象
     * @param interceptor 攔截器,主要用來執行org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        //得到interceptor的簽名
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //被代理的類
        Class<?> type = target.getClass();
        //得到所有被註解的接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }

查找目標類及其父類的所有接口,通過反射方法(java.lang.reflect.Proxy#newProxyInstance),new出增強類。

五、實現在目標方法上鍊式添加所有攔截器

/**
 * Copyright 2009-2015 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ibatis.plugin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 攔截器鏈
 * @author Clinton Begin
 */
public class InterceptorChain {

    /**
     * 存放插件的容器
     */
    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

    /**
     * 添加攔截器
     * @param target
     * @return
     */
    public Object pluginAll(Object target) {
        /**
         * 被所有攔截器增強的target
         */
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    /**
     * 添加一個攔截器
     * @param interceptor
     */
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    /**
     * 得到所有的攔截器
     * @return
     */
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章