mybatis之插件机制

  • 插件
    • 使用
    • 确定需要拦截的签名

插件

使用

  1. 使用插件意味着在修改mybatis的底层封装,虽然灵活但是也可能导致mybatis出现重大bug
  2. 在mybatis中使用插件就必须实现Interceptor接口

    public interface Interceptor {
    
      //直接覆盖所拦截对象原有的方法,通过Invocation反射调用原来对象的方法
      Object intercept(Invocation invocation) throws Throwable;
    // target指的是被拦截的对象,对拦截对象生成一个代理对象,并返回
      Object plugin(Object target);
    //在plugin元素中配置所需要的参数,在插件初始化的时候调用一次
      void setProperties(Properties properties);
    
    }
    
    public class InterceptorChain {
    
      private final List interceptors = new ArrayList();
    
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
    
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
    
    }
    
  3. mybatis提供了一个工具类Plugin,实现了InvocationHandler接口,采用jdk动态代理

    public class Plugin implements InvocationHandler {
    
      private Object target;
      private Interceptor interceptor;
      private Map, Set> signatureMap;
    
      private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
    
      public static Object wrap(Object target, Interceptor interceptor) {
        Map, Set> 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;
      }
    
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          Set methods = signatureMap.get(method.getDeclaringClass());
          if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
          }
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    
      private static Map, Set> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        if (interceptsAnnotation == null) { // issue #251
          throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map, Set> signatureMap = new HashMap, Set>();
        for (Signature sig : sigs) {
          Set methods = 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());
            methods.add(method);
          } catch (NoSuchMethodException e) {
            throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
          }
        }
        return signatureMap;
      }
    
      private static Class[] getAllInterfaces(Class type, Map, Set> signatureMap) {
        Set> interfaces = new HashSet>();
        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()]);
      }
    
    }

确定需要拦截的签名

  1. Mybatis插件可以拦截Executor、StatementHandler、ParameterHandler、ResulteSetHandler中的任意一个。
    * Executor是执行SQL的全过程,包括组装参数,组装结果集返回和执行SQL过程
    * StatementHandler是执行SQL的过程,可以重写执行SQL的过程,这是常用的拦截对象
    * ParameterHandler,很明显它主要是拦截执行SQL的参数组装,你可以重写组装参数规则
    * ResultSetHandler用于拦截执行结果的组装,你可以重写组装结果的规则
  2. 案例

        配置插件,注意顺序
            * Mybatis3.X 的版本使用的 dtd 作为 XML 的格式校验文档。
            * 而在 XML 规范中,dtd 是有严格的顺序的,在报错的异常中已经列出了对应的顺序,
            * 应该为:(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,
            objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?)
    
        
            "com.jannal.mybatis.plugin.MyPlugin">
                "jannal" value="jannalpro" />
            
        
    
         /**
     * @Intercepts 标识它是一个拦截器
     * @Signature 注册拦截器签名的地方
     */
    @Intercepts(value = { @Signature(type = StatementHandler.class, // 确定要拦截的对象
            method = "prepare", // 确定要拦截的方法
            args = { Connection.class }// 拦截方法的参数
            ) })
    public class MyPlugin implements Interceptor {
        private Properties properties;
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("开始拦截.....");
            Object proceed = invocation.proceed();
            System.out.println("结束拦截.....");
            return proceed;
        }
    
        @Override
        public Object plugin(Object target) {
            System.out.println("生成代理对象....");
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
            System.out.println(properties.get("jannal"));
           this.properties = properties;
        }
    
    }
    
    生成代理对象....
    生成代理对象....
    生成代理对象....
    生成代理对象....
    2017-06-23 11:50:50.790 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - Openning JDBC Connection
    2017-06-23 11:50:50.998 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - Created connection 982757413.
    2017-06-23 11:50:51.001 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@3a93b025]
    开始拦截.....
    2017-06-23 11:50:51.002 [main] DEBUG org.apache.ibatis.logging.slf4j.Slf4jImpl.debug(Slf4jImpl.java:43) - ==>  Preparing: select id ,mobile_no_internet ,cust_no ,mes_code ,mes_title ,mes_content ,create_time ,inserttime ,updatetime from t_user_message t where t.id=?; 
    结束拦截.....
    

你可能感兴趣的:(#,mybatis)