Mybaits和Spring的那点事

前言

在spring中使用mybaits简直不要太简单,只需要几个配置,一个DAO接口和一个mapper.xml就可以完成一次数据库交互。但是简单背后往往是复杂的实现,现在我们来探讨一下里面的一点原理吧,对,在不考虑springboot自动配置的前提下。

spring和mybaits是如何集成的

 spring是一个框架,mybaits也是一个框架,要想一个框架起作用,总得有一个调用入口的。springboot的入口我们应该很清楚,如下 

@SpringBootApplication(scanBasePackages = "com.example.demo")
@MapperScan("com.example.demo.repository.dao")
public class StartApplication {
    public static void main(String[] args) {
        SpringApplication.run(StartApplication.class, args);
    }
}

main方法里面调用了SpringApplication.run(StartApplication.class, args),就开始了springboot一系列的初始化流程。而mybaits我们好像没有调用啊,那它是怎么初始化的。

@MapperScan("com.example.demo.repository.dao")

再细看一下,我们好像是用到了mybaits包里面的MapperScan注解。它里面是这样的

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  ///省略
}

上面的注解关键在于@Import(MapperScannerRegistrar.class),用了@Import,spring就可以将MapperScannerRegistrar注册成bean,还可以对实现了特殊接口的类做处理,再看看MapperScannerRegistrar是什么

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
        //省略
  }
  void setResourceLoader(ResourceLoader resourceLoader)
  {
      //省略
  }
//省略 }

MapperScannerRegistrar实现了ResourceLoaderAware接口,可以在实例化的时候得到资源加载器ResourceLoader。另外还实现了ImportBeanDefinitionRegistrar,可以得到BeanDefinitionRegistry,拥有注册BeanDefinition的能力。

再从spring的主流程来说,在spring容器初始化后,会调用容器的refresh方法,然后经过一系列的调用后,会执行MapperScannerRegistrar的registerBeanDefinitions方法,开始mybaits注册BeanDefinition之旅了。

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //省略
    List<String> basePackages = new ArrayList<>();
    //省略
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("basePackages"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));
    //省略
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

上面的代码主要是为了拿到我们定义的basePackages值,然后就执行scanner.doScan方法开始扫描符合条件的类,然后注册成BeanDefinition。注册完成后,mybaits还会对BeanDefinition列表执行processBeanDefinitions方法

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      //省略 
      definition.setBeanClass(this.mapperFactoryBeanClass);
       //省略
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
       //省略
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }
     //省略
    }
  }

从上面可以看到,mybaits在processBeanDefinitions里面将BeanClass设置为mapperFactoryBean,还会挂sqlSessionFactory和sqlSessionTemplate到属性中。这样之后,我们定义的DAO接口就静静地躺着容器中等待getBean了。

定义的DAO接口怎么变成实例

上面已经大概描述了DAO接口如何变成BeanDefinition,但是如果要拿到实例,还要经历一个getBean的过程,一般的BeanDefinition只需要通过反射就可以变成实例了。但是mybaits注册的BeanDefinition类型都改成mapperFactoryBean,如下

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
   //省略
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
   //省略
}

从上面看到MapperFactoryBean实现FactoryBean,这样的BeanDefinition不再通过反射拿到实例了,而是通过FactoryBean.getObject()获取。所以这个时候,DAO接口实例化的处理由Spring交给Mybaits的getMapper负责了。getMapper处理如下

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

//mapperProxyFactory
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

在上面代码中,mybaits为接口生成了代理。而我们需要关注的是生成了什么代理,MapperProxy的代码如下

public class MapperProxy<T> implements InvocationHandler, Serializable {

 //省略
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
}

MapperProxy代理的都是接口,所以invoke执行实际上就是MapperMethod的execute执行,如下

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

上面的代码简单来说就是,mybaits会根据mapper.xml中定义的标签,做不同的处理,然后将结果返回。

小结

本文简单描述一个DAO接口如何在spring和mybaits中一步步变成一个可以调用的实例。其中ImportBeanDefinitionRegistrar和FactoryBean是关键点,mybaits靠这两个关键类集成到spring中。如果我们要做一个类似框架集成到spring,也可以学习一下这种方式。

posted @ 2019-05-30 11:29  Caizl  阅读(236)  评论(0编辑  收藏  举报