Mybatis插件开发

ゝ一世哀愁。 2021-09-28 00:24 550阅读 0赞

前面几篇文章介绍了Mybtis中四个重要的对象,其中提到它们都是在Configuration中被创建的,我们一起看一下创建四大对象的方法,代码如下所示:

  1. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  2. ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  3. parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  4. return parameterHandler;
  5. }
  6. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
  7. ResultHandler resultHandler, BoundSql boundSql) {
  8. ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  9. resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  10. return resultSetHandler;
  11. }
  12. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  13. StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  14. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  15. return statementHandler;
  16. }
  17. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  18. executorType = executorType == null ? defaultExecutorType : executorType;
  19. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  20. Executor executor;
  21. if (ExecutorType.BATCH == executorType) {
  22. executor = new BatchExecutor(this, transaction);
  23. } else if (ExecutorType.REUSE == executorType) {
  24. executor = new ReuseExecutor(this, transaction);
  25. } else {
  26. executor = new SimpleExecutor(this, transaction);
  27. }
  28. if (cacheEnabled) {
  29. executor = new CachingExecutor(executor);
  30. }
  31. executor = (Executor) interceptorChain.pluginAll(executor);
  32. return executor;
  33. }

重点关注每个方法中的这样一个语句:

  1. parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  2. resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  3. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  4. executor = (Executor) interceptorChain.pluginAll(executor);

我们看到前面已将创建出了相关的对象,那么这里的pluginAll()的作用是什么?下面我们针对pluginAll()进行说明。

我们进入InterceptorChain中的pluginAll()方法,具体代码如下所示:

  1. public Object pluginAll(Object target) {
  2. for (Interceptor interceptor : interceptors) {
  3. target = interceptor.plugin(target);
  4. }
  5. return target;
  6. }

在PluginAll()方法中,以循环的方式调用了plugin(),跟踪代码进入Interceptor接口,其代码如下:

  1. public interface Interceptor {
  2. Object intercept(Invocation invocation) throws Throwable;
  3. Object plugin(Object target);
  4. void setProperties(Properties properties);
  5. }

Interceptor是一个顶级接口,且没有实现类,那么调用它的意义何在?

其实,这是Mybatis给开发者留下的“后门”,它采用了动态代理模式的思想,允许开发者进行插件开发,开发者可以通过实现Interceptor接口实现对四大对象的监控处理。

在PluginAll()方法中,要注意的是:

  • 形参Object target,这个是Executor、ParameterHandler、ResultSetHandler、StatementHandler接口的实现类,换句话说,plugin方法是要为Executor、ParameterHandler、ResultSetHandler、StatementHandler的实现类生成代理,从而在调用这几个类的方法的时候,其实调用的是InvocationHandler的invoke方法,这里应用动态代理模式的思想。
  • 这里的target是通过for循环不断赋值的,也就是说如果有多个拦截器,那么如果用P表示代理,生成第一次代理为P(target),生成第二次代理为P(P(target)),生成第三次代理为P(P(P(target))),不断嵌套下去,这就得到一个重要的结论:中后定义的实际其拦截器方法先被执行,因为根据这段代码来看,后定义的代理实际后生成,包装了先生成的代理,自然其代理方法也先执行。

plugin方法中调用MyBatis提供的现成的生成代理的方法Plugin.wrap(Object target, Interceptor interceptor),接着看下wrap方法的源码实现,代码如下:

  1. public static Object wrap(Object target, Interceptor interceptor) {
  2. Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  3. Class<?> type = target.getClass();
  4. Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  5. if (interfaces.length > 0) {
  6. return Proxy.newProxyInstance(
  7. type.getClassLoader(),
  8. interfaces,
  9. new Plugin(target, interceptor, signatureMap));
  10. }
  11. return target;
  12. }

其中:Proxy.newProxyInstance()就是动态代理模式的体现。动态代理模式的相关讲解具体参见设计模式系列。

首先看第二行代码:getSignatureMap(interceptor),getSignatureMap()代码如下:

  1. private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  2. Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  3. if (interceptsAnnotation == null) {
  4. throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  5. }
  6. Signature[] sigs = interceptsAnnotation.value();
  7. Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
  8. for (Signature sig : sigs) {
  9. Set<Method> methods = signatureMap.get(sig.type());
  10. if (methods == null) {
  11. methods = new HashSet<Method>();
  12. signatureMap.put(sig.type(), methods);
  13. }
  14. try {
  15. Method method = sig.type().getMethod(sig.method(), sig.args());
  16. methods.add(method);
  17. } catch (NoSuchMethodException e) {
  18. throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  19. }
  20. }
  21. return signatureMap;
  22. }

getSignatureMap()先拿@Intercepts注解,如果没有定义@Intercepts注解,抛出异常,这意味着使用MyBatis的插件,必须使用注解方式。接着拿到@Intercepts注解下的所有@Signature注解,获取其type属性(表示具体某个接口),再根据method与args两个属性去type下找方法签名一致的方法Method(如果没有方法签名一致的就抛出异常,此签名的方法在该接口下找不到),能找到的话key=type,value=Set,添加到signatureMap中,构建出一个方法签名映射。这里的作用就是通过定义的@Intercepts注解,要Executor、StatementHandler、ParameterHandler、ResultSetHandler下所有Method。

参考了:https://www.cnblogs.com/xrq730/p/6984982.html

根据上面分析总结Mybatis插件开发的步骤如下:

  1. 开发一个Interceptor接口的实现类
  2. 重写Interceptor接口方法
  3. 在mybatis框架通过核心配置注册拦截器
  4. 通过@Intercpts指定当前拦截器监听的对象类型和行为

发表评论

表情:
评论列表 (有 0 条评论,550人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Mybatis开发

    前面几篇文章介绍了Mybtis中四个重要的对象,其中提到它们都是在Configuration中被创建的,我们一起看一下创建四大对象的方法,代码如下所示: public