Mybatis之插件(plugin)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

·Executor (update, query, flushStatements, commit,rollback, getTransaction, close, isClosed)

·ParameterHandler (getParameterObject, setParameters)

·ResultSetHandler (handleResultSets,handleOutputParameters)

·StatementHandler (prepare, parameterize, batch, update,query)

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

下面先看一个插件的例子
@Intercepts({@Signature(type = StatementHandler.class,// 要拦截的类
        method = "prepare",// 要拦截的方法
        args = {Connection.class})// 方法的参数
})
public class MyPlugin implements Interceptor {
    private String limitNum;
    public Object intercept(Invocation invocation) throws Throwable {
        // 此处可以写处理代码
        System.out.println("MyPlugin");
        return invocation.proceed();
    }

    public Object plugin(Object o) {
        // 生成代理对象
        return Plugin.wrap(o,this);
    }

    public void setProperties(Properties properties) {
        // 可以获取配置文件中配置的属性
        this.limitNum = properties.getProperty("limitNum", "30");
    }
}

在配置文件中配置插件

<plugins>
    <plugin interceptor="com.cyy.mybatis.plugin.MyPlugin">
        <property name="limitNum" value="10"/>
    </plugin>
</plugins>
这样一个插件就写完了。这个插件将会拦截在StatementHandler
实例中所有的prepare方法调用

我们简单看下插件的实现。

首先在解析 mybatis 的配置文件的时候, 会对 <plugins> 节点进行解析,解析的实现为 XMLConfigBuilder pluginElement 方法
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator i$ = parent.getChildren().iterator();

        while(i$.hasNext()) {
            XNode child = (XNode)i$.next();
		// 获取配置的插件的interceptor属性
            String interceptor = child.getStringAttribute("interceptor");
		// 解析<property>节点下配置的<propert>
            Properties properties = child.getChildrenAsProperties();
		// 产生插件的实例
            Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
		// 设置property
            interceptorInstance.setProperties(properties);
// 保存插件实例到插件链中
            this.configuration.addInterceptor(interceptorInstance);
        }
    }

}
我们知道在产生 StatementHandler 实例的时候, 代码实现是通过调用 Configuration newStatementHandler 方法来实现的
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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}
我们直接看代理对象的产生,此方法会循环产生代理对象,多少个插件就会生成多少个代理对象,然后会形成一个代理链。

public Object pluginAll(Object target) {
    Interceptor interceptor;
    for(Iterator i$ = this.interceptors.iterator(); i$.hasNext(); target = interceptor.plugin(target)) {
        interceptor = (Interceptor)i$.next();
    }

    return target;
}
继续查看Interceptorplugin方法,我们看到interceptor实际上是我们自己定义的插件。我们的插件一般都是直接用Pluginwrap方法来生成代理对象。
public Object plugin(Object o) {
    // 生成代理对象
    return Plugin.wrap(o,this);
}
我们进入这个方法

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
我们看 Plugin getSignatureMap 方法
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = (Intercepts)interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) {
        throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    } else {
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap();
        Signature[] arr$ = sigs;
        int len$ = sigs.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            Signature sig = arr$[i$];
            Set<Method> methods = (Set)signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet();
                signatureMap.put(sig.type(), methods);
            }

            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                ((Set)methods).add(method);
            } catch (NoSuchMethodException var10) {
                throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + var10, var10);
            }
        }

        return signatureMap;
    }
}
继续看 getAllInterfaces 方法(获取要代理的接口)
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    HashSet interfaces;
    for(interfaces = new HashSet(); type != null; type = type.getSuperclass()) {
        Class[] arr$ = type.getInterfaces();
        int len$ = arr$.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            Class<?> c = arr$[i$];
            if (signatureMap.containsKey(c)) {
                interfaces.add(c);
            }
        }
    }

    return (Class[])interfaces.toArray(new Class[interfaces.size()]);
}
如果有要代理的接口就会产生代理对象,后面在用此对象调用方法的时候都会调用 Plugin invoke 方法。我们看一下此方法的实现
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
        return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
    } catch (Exception var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}
然后判断要执行的方法是否要被插件拦截,如果不要就直接回调原来的方法,否则就直接调用自定义插件的intercept方法

public Object intercept(Invocation invocation) throws Throwable {
    // 此处可以写处理代码
    System.out.println("MyPlugin");
    return invocation.proceed();
}
在此方法中可以做我们自定义的事情,然后再回调原来的方法就可以了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值