首页 > 其他分享 >MyBatis-07-插件原理

MyBatis-07-插件原理

时间:2024-04-16 22:56:43浏览次数:15  
标签:插件 拦截器 07 Object public MyBatis new method target

MyBatis 插件

MyBatis 的插件实际上就是 Interceptor,拦截器,拦截并重写 SQL
image

Interceptor: 拦截器
InterceptorChain: 拦截器链
Invocation: 封装 Object.method(args),反射调用
Plugin: JDK 代理处理器 InvocationHandler,封装一个 Interceptor 及其拦截的方法,及 拦截对象 + 拦截方法 + Interceptor,就可以 invoke 前先进行拦截处理了

Interceptor

拦截器,只能拦截 MyBatis 中的四个对象

  • ParameterHandler
  • ResultSetHandler
  • StatementHandler
  • Executor
    于是当这几个对象被 new 时,会代理他们,在代理逻辑中对他进行拦截
public interface Interceptor {
  // 拦截器逻辑
  Object intercept(Invocation invocation) throws Throwable;
  // 封装为插件, 当 target 要被拦截时, 取出所有拦截器, 均调用他们的 plugin, 最终得到一个封装了要拦截的对象和所有拦截器实例的对象
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }
}

InterceptorChain

实际就是多个 Interceptor 的更进一步封装,拦截器链
查看 pluginAll 的引用,可以看见就是 MyBatis 的四大对象的 new 的地方使用到了

public class InterceptorChain {
  // 持有所有拦截器
  private final List<Interceptor> interceptors = new ArrayList<>();
  // 调用所有拦截器的 plugin 方法, 每调用一次生成一个代理对象, 后面的拦截器在前一个代理对象的基础上继续生成代理对象
  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);
  }

}

Intercepts、Signature

声明拦截器及拦截的方法
如下案例

@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }))
public static class SwitchCatalogInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    Connection con = (Connection) args[0];
    con.setSchema(SchemaHolder.get());
    return invocation.proceed();
  }
}

Invocation

配合封装拦截器的,从构造函数可以看出仅支持 Executor、ParameterHandler、ResultSetHandler、StatementHandler 的封装

public class Invocation {

  private static final List<Class<?>> targetClasses = Arrays.asList(Executor.class, ParameterHandler.class,
      ResultSetHandler.class, StatementHandler.class);
  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    if (!targetClasses.contains(method.getDeclaringClass())) {
      throw new IllegalArgumentException("Method '" + method + "' is not supported as a plugin target.");
    }
    this.target = target;
    this.method = method;
    this.args = args;
  }

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

}

Plugin

实现插件/拦截的逻辑,也是为什么叫做插件的原因

public class Plugin implements InvocationHandler {
  // 要拦截的对象
  private final Object target;
  // 拦截器
  private final Interceptor interceptor;
  // 拦截器要拦截的方法
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
  
  // 代理逻辑
  public static Object wrap(Object target, Interceptor interceptor) {
    // 获取拦截器要拦截的所有方法(类 --> 方法列表)
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
	// 要被代理的实例的所有接口, 用于创建 JDK 代理
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
	// 有接口才会代理, 本身 MyBatis 的四大对象都是接口
    if (interfaces.length > 0) {
	  // Plugin 是 InvocationHandler, 实现了代理逻辑
      return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
	  // 是否在它一要代理的类中 
      Set<Method> 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<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // Intercepts 注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
	// 为空, 这种应该是注入了 Interceptor 类型的但是没有使用注解
    if (interceptsAnnotation == null) {
      throw new PluginException(
          "No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
      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<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> 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<?>[0]);
  }

}

总结

image

XMLConfigBuilder#pluginsElement: 解析 Interceptor
  - Configuration#addInterceptor: 添加到 Configuration 中
    - InterceptorChain#addInterceptor: Configuration 持有 InterceptorChain 实例, 添加到 InterceptorChain 中

Configuration new 四个对象时都调用了拦截器链的代理方法

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
    BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
      parameterObject, boundSql);
  // 调用
  return (ParameterHandler) interceptorChain.pluginAll(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);
  // 调用
  return (ResultSetHandler) interceptorChain.pluginAll(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);
  // 调用
  return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : 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);
  }
  // 调用
  return (Executor) interceptorChain.pluginAll(executor);
}

标签:插件,拦截器,07,Object,public,MyBatis,new,method,target
From: https://www.cnblogs.com/chenxingyang/p/18139466

相关文章

  • MyBatis-08-Spring的MyBatis Interceptor
    addInterceptor3个地方XML解析的SqlSessionFactoryBean:生成SqlSession的FactoryBeanPageHelperAutoConfiguration:分页助手的自动配置SqlSessionFactoryBean发现现在都没有将他作为一个FactoryBean使用了getObject调用了afterPropertiesSet生成SqlSessionF......
  • MyBatis-09-FactoryBean的问题
    ListableBeanFactory#getBeanNamesForType(Class<?>)这个方法的逻辑在对FactoryBean进行判断时,会使用FactoryBean的生成的对象的类型进行判断BD的属性数据AttributeAccessor.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE)反射创建对象并调用FactoryBean#getObjectT......
  • MyBatis-01-Demo
    数据库DDLCREATEDATABASE`mybatis_demo01`;--mybatis_demo01.`user`definitionCREATETABLE`user`(`id`intNOTNULLAUTO_INCREMENT,`username`varchar(100)DEFAULTNULL,`birthday`datetimeDEFAULTNULL,`sex`char(1)DEFAULTNULL,`address`......
  • MyBatis-02-别名
    别名TypeAliasRegistry默认注册了很多别名,构造函数注册的publicTypeAliasRegistry(){registerAlias("string",String.class);registerAlias("byte",Byte.class);registerAlias("char",Character.class);registerAlias("character"......
  • MyBatis-03-environment
    配置<environmentsdefault="default"><environmentid="default"><!--事务类型--><transactionManagertype="JDBC"/><!--数据源类型--><dataSourcetype="POOLED">&l......
  • mybatisplus
    mybatisplus如何实现获取信息通过扫描实体类并通过反射获取实体类信息作为数据库表信息约定:类名、变量名驼峰转下划线作为表名id字段默认为主键常用注解@TableName,@TableId,@TableField@TableField使用场景:成员变量为boolean并且名称为is开头,转化时会去掉is......
  • 【题解】P4307 [JSOI2009] 球队收益 / 球队预算
    P4307[JSOI2009]球队收益/球队预算题解题目传送门题意简述一共有\(n\)个球队比赛,输了赢了都会有相应的支出,现在让你安排\(m\)场比赛的输赢,是总支出最少。思路首先看到最小支出,状态不好定义,直接费用流,启动!。后文如果没有特殊说明,边的费用均为\(0\)。考虑建图,其......
  • FR107-ASEMI快恢复二极管FR107
    编辑:llFR107-ASEMI快恢复二极管FR107型号:FR107品牌:ASEMI封装:DO-41最大平均正向电流(IF):1A最大循环峰值反向电压(VRRM):1000V最大正向电压(VF):1.20V工作温度:-55°C~150°C反向恢复时间:50ns芯片个数:1芯片尺寸:mil引脚数量:2正向浪涌电流(IFMS):30A包装方式:50/管1000/盘3000/箱F......
  • 80、SpringBoot3 SpringSecurity Mybatisplus最新版 整合 实现登入权限控制
    1、导入pom依赖<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apac......
  • SpringBoot+MybatisPlus 增删改查学习第三章 (C#转JAVA)
    packagecom.example.demo;importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;importcom.example.demo.entity.Person;importcom.example.demo.mapper.PersonMapper;importcom.example.demo.service.PersonService;importorg.junit.jupiter.api.Test;i......