关于mybatis解析plugin及plugin使用的理解

mybatis支持插件来插入自定制的处理过程,所有的plugin都需实现Interceptor接口,自定制的处理过程可以在Executor,ParameterHandler,ResultSetHandler,StatementHandler四个处理过程中插入,原理是在使用这四中类型处理数据的时候使用的都是经过plugin处理过的代理对象。同一个处理过程支持配置多个plugin,则plugin的执行顺序是根据包装的顺序,从最外部向内部执行,直到执行到目标对象的调用方法。包装的顺序是根据配置顺序,也就是说配置越靠前,包装的越深,越后执行

interceptor源码:

1 public interface Interceptor {
2 
3   Object intercept(Invocation invocation) throws Throwable; //代理对象中调用的插件的自定制代码
4 
5   Object plugin(Object target); //生成插件处理过的代理对象
6 
7   void setProperties(Properties properties);
8 
9 }

得到Executor对象的源码:

SqlSessionFactory#openSqlSession:得到SqlSession

 1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 2     Transaction tx = null;
 3     try {
 4       final Environment environment = configuration.getEnvironment();
 5       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
 6       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
 7       final Executor executor = configuration.newExecutor(tx, execType);
 8       return new DefaultSqlSession(configuration, executor, autoCommit);
 9     } catch (Exception e) {
10       closeTransaction(tx); // may have fetched a connection so lets call close()
11       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
12     } finally {
13       ErrorContext.instance().reset();
14     }
15   }

得到StatementHandler对象的源码:

Executor:

1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
2     Configuration configuration = ms.getConfiguration();
3     StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
4     Statement stmt = prepareStatement(handler, ms.getStatementLog());
5     return handler.<E>query(stmt, resultHandler);
6   }

得到ParameterHandler和ResultSetHandler对象的源码:

BaseStatementHandler:

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

可以看到,都使用的Configuration对象创建这四中对象实例

Configuration:

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  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) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

可以看到使用的是interceptorChain来处理创建的对象

InterceptorChain:

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

 

 可以看到调用的是interceptor接口子类对象的plugin方法来处理目标对象,interceptor接口子类对象是在XMLConfigBuilder解析mybatis-config.xml配置文件时添加的,源码:

XMLConfigBuilder:

 1 private void pluginElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         String interceptor = child.getStringAttribute("interceptor");
 5         Properties properties = child.getChildrenAsProperties();
 6         Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
 7         interceptorInstance.setProperties(properties);
 8         configuration.addInterceptor(interceptorInstance);
 9       }
10     }
11   }

 

Configuration:

1 public void addInterceptor(Interceptor interceptor) {
2     interceptorChain.addInterceptor(interceptor); //protected final InterceptorChain interceptorChain = new InterceptorChain()
3   }

 

 

时序图:

插件实例(tracer系统中打印sql执行过程日志):

@Intercepts({ @Signature(type = Executor.class, method = "update", args = {
        MappedStatement.class, Object.class}),
     @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
    RowBounds.class, ResultHandler.class }) })
public class MybatisLogPlugin implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        LogFoot logFoot = new LogFoot();
        doBeforeInvocation(logFoot);
        Object result ="";
        try{
              result = invocation.proceed();
           doAfterInvocation(logFoot,invocation,true,result);
        }catch(Exception e){
            doAfterInvocation(logFoot,invocation,false,e);
            throw e;
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this); //mybatis提供的包装工具类
    }

    @Override
    public void setProperties(Properties properties) {
    }
    
    private void doBeforeInvocation(LogFoot logFoot){
        //注意这里的逻辑:这个拦截入口是HTTP或者RPC,把之前的Threadlocal中context清空
        //fix me:非常小心 theadlocal和线程池同时使用时会有问题,线程会重复利用导致context会重复利用
        if(LogContext.get()==null){
           LogContext.set(new LogContext());
        }
        Long startTimeMillis = System.currentTimeMillis(); // 记录方法开始执行的时间
        logFoot.setId(LogContext.get().getRequestId());
        logFoot.setStartTimeMillis(startTimeMillis);
        String optTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTimeMillis);
        logFoot.setOptTime(optTime);
        //step 递增
        int seqID= LogContext.get().addSequence();
        logFoot.setSequence(seqID);
    }
    
    private void doAfterInvocation(LogFoot logFoot,Invocation invocation,Boolean succ,Object result){
          logFoot.setType(LogTypeEnum.RESOURCE_MYSQL.getValue());
          
           Object[] arguments = invocation.getArgs();
           String sql =getSqlStatement(arguments);
           logFoot.setArguments(new Object[]{sql});
            
            //调用的方法名
            String method=invocation.getMethod().toString();
            logFoot.setMethod(method);

            String JVM =JVMUtil.getJVMName();
            logFoot.setJvmname(JVM);

            String local_ip =IPAddrUtil.localAddress();
            logFoot.setLocal_ip(local_ip);

            Long thread = Thread.currentThread().getId();
            logFoot.setThread(thread.toString());
            
            logFoot.setSucc(succ);

            //logFoot.doPrint();
            // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
            Map<String, Object> outputParamMap = new HashMap<String, Object>();
            outputParamMap.put("result", result);
            logFoot.setOutputParamMap(outputParamMap);

            Long endTimeMillis = System.currentTimeMillis(); // 记录方法执行完成的时间
            logFoot.setEndTimeMillis(endTimeMillis);
            logFoot.doPrint();
    }
    
    private String getSqlStatement(Object[] arguments){
        MappedStatement mappedStatement = (MappedStatement) arguments[0];
        Object parameter = null;
        if (arguments.length > 1) {
            parameter = arguments[1];
        }
        String sqlId = mappedStatement.getId();
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        Configuration configuration = mappedStatement.getConfiguration();
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder(100);
        str.append(sqlId);
        str.append(":");
        str.append(sql);
        str.append(":");
        return str.toString();
    }
    
    public  String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
 
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }
    
     private static String getParameterValue(Object obj) {
            String value = null;
            if (obj instanceof String) {
                value = "'" + obj.toString() + "'";
            } else if (obj instanceof Date) {
                DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
                value = "'" + formatter.format(new Date()) + "'";
            } else {
                if (obj != null) {
                    value = obj.toString();
                } else {
                    value = "";
                }
     
            }
            return value;
        }
    
}

 

 

mybatis的Plugin源码:

 1 public class Plugin implements InvocationHandler {
 2 
 3   private Object target;
 4   private Interceptor interceptor;
 5   private Map<Class<?>, Set<Method>> signatureMap;
 6 
 7   private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
 8     this.target = target;
 9     this.interceptor = interceptor;
10     this.signatureMap = signatureMap;
11   }
12 
13   public static Object wrap(Object target, Interceptor interceptor) {
14     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
15     Class<?> type = target.getClass();
16     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
17     if (interfaces.length > 0) {
18       return Proxy.newProxyInstance(
19           type.getClassLoader(),
20           interfaces,
21           new Plugin(target, interceptor, signatureMap));
22     }
23     return target;
24   }
25 
26   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
27     try {
28       Set<Method> methods = signatureMap.get(method.getDeclaringClass());
29       if (methods != null && methods.contains(method)) { //先执行plugin中的方法,最后再执行目标对象调用的方法
30         return interceptor.intercept(new Invocation(target, method, args));
31       }
32       return method.invoke(target, args);
33     } catch (Exception e) {
34       throw ExceptionUtil.unwrapThrowable(e);
35     }
36   }
37 
38   private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
39     Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
40     if (interceptsAnnotation == null) { // issue #251
41       throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
42     }
43     Signature[] sigs = interceptsAnnotation.value();
44     Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
45     for (Signature sig : sigs) {
46       Set<Method> methods = signatureMap.get(sig.type());
47       if (methods == null) {
48         methods = new HashSet<Method>();
49         signatureMap.put(sig.type(), methods);
50       }
51       try {
52         Method method = sig.type().getMethod(sig.method(), sig.args());
53         methods.add(method);
54       } catch (NoSuchMethodException e) {
55         throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
56       }
57     }
58     return signatureMap;
59   }
60 
61   private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
62     Set<Class<?>> interfaces = new HashSet<Class<?>>();
63     while (type != null) {
64       for (Class<?> c : type.getInterfaces()) {
65         if (signatureMap.containsKey(c)) {
66           interfaces.add(c);
67         }
68       }
69       type = type.getSuperclass();
70     }
71     return interfaces.toArray(new Class<?>[interfaces.size()]);
72   }
73 
74 }

 

转载于:https://www.cnblogs.com/stefanking/articles/5114175.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatisX Plugin(简称MyBatisX)是一个用于开发Java应用程序中操作数据库的插件。它是基于MyBatis框架的扩展,旨在提高开发人员在使用MyBatis进行数据库操作时的效率和便捷性。 MyBatisX插件提供了许多实用的功能,以帮助开发人员更轻松地进行数据库操作。首先,它提供了一套丰富的代码生成工具,可以根据数据库表结构自动生成实体类、Mapper接口、XML映射文件等。这样一来,开发人员可以节省大量时间和精力,不需要手动编写繁琐的数据库操作代码。 其次,MyBatisX还提供了一些增强的代码编辑功能。例如,它支持代码自动补全、语法高亮、快速跳转等功能,使开发人员能够更方便地编写和修改数据库操作代码。此外,它还提供了一些代码质量分析功能,帮助开发人员发现潜在的问题和错误,并给出相应的建议和改进意见。 此外,MyBatisX还提供了一些实用的调试和优化工具。开发人员可以通过它来查看和分析数据库操作的执行情况,了解SQL语句的执行计划和性能瓶颈,以便及时进行调整和优化。这些工具可以帮助开发人员提高程序的性能和稳定性,减少不必要的资源消耗和时间延迟。 总之,MyBatisX Plugin是一个功能强大的插件,可以大幅提升开发人员在使用MyBatis进行数据库操作时的效率和便捷性。它的代码生成工具、增强的代码编辑功能和实用的调试和优化工具为开发人员提供了丰富的功能和工具,使他们能够更轻松地进行数据库开发工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值