mybatis 插件原理

1、mybatis初始化
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 
的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML
配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。(摘自www.mybatis.org)


mybatis的核心配置类就是Configuration,初始化时肯定是根据xml得到Configuration进而得到SqlSessionFactory。
现在我们使用spring+mybatis一般会配置两个bean
org.mybatis.spring.SqlSessionFactoryBean
org.mybatis.spring.mapper.MapperScannerConfigurer


MapperScannerConfigurer是帮我们生成dao的代理对象,并注入到spring容器中。
SqlSessionFactoryBean就是用来初始化mybatis的


SqlSessionFactoryBean实现了几个spring的接口
实现FactoryBean在getObject()返回了sqlSessionFactory
实现InitializingBean在afterPropertiesSet()调用了buildSqlSessionFactory()

buildSqlSessionFactory()方法

if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } 
......
 xmlConfigBuilder.parse();

XMLConfigBuilder传入了配置文件,那么按照道理它应该会初始化configuration,对应的方法就是xmlConfigBuilder.parse()

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

可以看到配置文件中那些properties,plugins,settings等都设置Configuration对应属性里面了。
 buildSqlSessionFactory()最后就是return this.sqlSessionFactoryBuilder.build(configuration);还是使用的mybatis的SqlSessionFactoryBuilder
 

2、插件
插件需要实现的接口

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;  //插件具体的处理方法,参数invocation是上一个代理对象的封装

  Object plugin(Object target);//生成代理对象的方法,需要将插件,与目标对象target整合一起,一般使用Plugin.wrap生成

  void setProperties(Properties properties);//给插件设置参数方法

}

//工具方法Plugin

public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private 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();
    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<Method> methods = signatureMap.get(method.getDeclaringClass());//取得此interceptor注解配置的需要拦截的方法
      if (methods != null && methods.contains(method)) {//如果方法里面包含当前执行方法,执行插件的intercept方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  
//配置插件例
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
type要拦截什么对象,Executor,StatementHandler,ResultSetHandler,ParameterHandler
method要拦截的方法
args方法的参数

3、插件链的生成及执行
插件作用在四个对象上面Executor,StatementHandler,ResultSetHandler,ParameterHandler
操作sql需要使用SqlSession对象,实现类DefaultSqlSession里面的方法最终都调用了Executor方法
Configuration的四个方法

Executor newExecutor(...)
StatementHandler newStatementHandler(...)
ResultSetHandler newResultSetHandler(...)
ParameterHandler newParameterHandler(...)
//每个方法最后都调用了interceptorChain.pluginAll(..)
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
由于上面的初始化,如果我们配置了plugins,那么此时interceptors里面会有我们配置的插件对象
这样循环调用的结果就是生成了一个链,假如有两个会形成下面的

第2层     intercept(invocation)     invocation指向第1层
     第1层   intercept(invocation)  invocation指向target
	            原始的target


最后得到的代理对象,执行对应方法时候,如果注解配置满足,就会执行插件的intercept方法,
当然如果我们需要让插件继续往下执行,则必须return invocation.proceed()


插件的原理执行过程就是这样。
继续看Executor后续执行会发现,它又调用用了StatementHandler方法,而StatementHandler在处理参数时候
调用了ParameterHandler,查询结束返回时候调用了ResultSetHandler

SqlSession->Executor->StatementHandler->{ParameterHandler,ResultSetHandler}


猜你喜欢

转载自blog.csdn.net/u013038630/article/details/75088417