Mybatis插件使用与实现原理

目录

1、使用篇:Mybatis插件的用法?   --实现 + 注册

2、原理篇:当我们不能修改mybatis代码的时候如何去实现增强和修改功能的死路和方法?  --代理和装饰器模式

3、应用场景:mybatis插件能应用在那些场景呢?

 

1、Mybatis插件的编写

【思考】:结合实例: PageInterceptor

        1、mybatis插件编写   

                 【重要思考点】动态代理实现增强与修改需要解决的问题?

                  ①插件使用范围:有哪些对象允许被代理?有哪些方法可以被拦截?      四大天王可以被拦截:                       

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

                          StatementHandler(getParameterObject,  setParameters)

                          ParameterHandler( handleResultSets,  handleOutputParamters )

                          ResultSetHandler ( prepare, parameterize, batch, update, query)

                  ②怎么创建代理?

                          第一步,实现mybatis预留的Interceptor接口

@Intercepts( { //定义要拦截对象+方法:拦截对象Executor#query(MappedStatement, Object, RowBounds, ResultHandler)

       @Signature(method = "query", type = Executor.class, args = {  

              MappedStatement.class, Object.class, RowBounds.class,  

              ResultHandler.class }),  

       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })  

public class MyInterceptor implements Interceptor {     

   //实现要增强的功能

    public Object intercept(Invocation invocation) throws Throwable {  

       Object result = invocation.proceed();  

       System.out.println("Invocation.proceed()");  

       return result;  

    }  

     //包装目标类,生成代理对象(在创建代理对象的时候遍历实现Interceptor的类调用该方法进行包装)

    public Object plugin(Object target) {  

       return Plugin.wrap(target, this);  

    }  

    //获取插件自定义参数

    public void setProperties(Properties properties) {  

       String prop1 = properties.getProperty("prop1");  

    }     

}  

                          第二步,在mybatis-config.xml中注册插件

     

         

               

         

     

                         

2、Plugin插件实现原理

插件的加载:启动的时候被存放在Configuration按照数组的方式存放;

/**1、加载时:插件注册到配置对象*/
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        // 核心:解析的插件设置进来
        // 代理在创建的时候按照配置的顺序层层包装,先注册的包装在最里面;在代理对象被调用的时候则按照顺序逆序开始执行,顺序结束执行;
        configuration.addInterceptor(interceptorInstance);   
      }
    }
}


//被调用:将插件添加给 Configuration内部属性 interceptorChain
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}


interceptorChain类解析:
public class InterceptorChain {

  private final List interceptors = new ArrayList();

  //给目标代理对象添加插件
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      //调用实现类的plugin方法;里面是进行包装的核心逻辑:Plugin.wrap(target, this);
      target = interceptor.plugin(target);
    }
    return target;
  }

  //添加插件
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  //获取XX插件
  public List getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

 

                  ③创建的代理对象什么时候使用?启动或者创建会话或者执行SQL?



/**2、在会话调用中对四大对象进行插件包装*/
parameterHandler设置插件
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

resultSetHandler设置插件
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;
}

statementHandler设置插件
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;
}

Executor设置插件
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

                  ④被代理后,调用的是什么方法?怎么调用到原被代理对象的方法?

    第一步,在被代理的时候,做了调用:   target = interceptor.plugin(target);

    第二步,interceptor.plugin() 在实现类中最终都会调用:  Plugin.wrap(target, this);

   第三步,而在 Plugin.wrap(target, this)中,实现了动态代理创建new Plugin(target, interceptor, signatureMap))

   第四步,因此,我们想知道动态代理在真实调用的时候如何执行那就需要看其invoke方法;

   第五步,在invoke方法我们看到其最终调用的是Interceptor接口的interceptor方法;

   第六步,最终调用 invocation.proceed()  实现反射调用被代理对象的方法

                       第一步,在被代理的时候,做了调用:   target = interceptor.plugin(target),interceptor.plugin() 在实现类中最终都会调用:  Plugin.wrap(target, this); ;  前文代码有备注,不再重复;

                       第二步,而在 Plugin.wrap(target, this)中,实现了动态代理创建new Plugin(target, interceptor, signatureMap))

public static Object wrap(Object target, Interceptor interceptor) {
    //获取插件类的 @Interceptors注解及注解属性@Signature集合(中的class类型和method和method的入参数args);结果保存key-目标代理class类型,value-Set
    Map, Set> signatureMap = getSignatureMap(interceptor);
    Class type = target.getClass();

    //根据调用的target对象实现的接口决定返回代理对象【-插件】数组
    Class[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {

      //Interceptor  是对被代理对象的的封装。
      //Proxy.newProxyInstance()  是JDK方式创建动态代理。
      //new Plugin(target, interceptor, signatureMap)  这是代理对象,必须实现InvocationHandler接口, 并实现invoke()方法,invoke方法是其代理执行逻辑;
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

                       第三步,因此,我们想知道动态代理在真实调用的时候如何执行那就需要看其invoke方法;在invoke方法我们看到其最终调用的是Interceptor接口的interceptor方法;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        //存在插件则调用插件的  intercept 方法并且创石化当前对象的类,方法,参数为 Invocation对象;
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
}

                       第四步,interceptor最终调用 invocation.proceed()  实现反射调用被代理对象的方法;

public class Invocation {

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

  //....get/set/构造

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

}

        2、插件的拦截链怎么形成?如何做到层层拦截?      责任链模式实现;InterceptorChain,  上面代码已经说明;

       

  3、 Mybatis插件的使用场景:

        1,水平分表,注解实现

        2,权限控制,根据用户不同对不同用户过滤不同的数据

        3,关键数据脱敏

 

 

 

 

你可能感兴趣的:(J2EE,mybatis,plugin,原理,应用)