spring和mybatis的dao层接口调用的原理探究

  • Post author:
  • Post category:其他

首先我们来看看mybatis简单的使用方法.
我们需要配置一个UserDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaoyu.dao.UserDao">
    <select id="getUser" resultType="com.xiaoyu.entity.User">
        select
        a.id as id,
        a.name as name,
        from biz_user as a
        where a.id =#{userId}
    </select>

然后我们需要有个dao层接口,并使用spring的注解@Repository(高版本好像注解都不需要了)

@Repository
public class UserDao {
    User getUser(String userId);
}

这样我们就可以使用mybatis了,但是一方面我们会乐见于其使用的方便性,另一方面也可能会疑惑为什么我们的dao层(或mapper层)只需要写好一个接口就可以了,而完全没有任何实现.mybatis是如何去实现以及执行xml文件中的sql的了.我们接下来就研究下.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring.xml" })
public class TestService {

    @Autowired
    private UserDao userDao;

    @Test
    public void testUser() {
        User a = userDao.getUser("1");
        a.toString();
    }

}

我们写个简单的测试类并进行debug,可以看到代码会进入org.apache.ibatis.binding.MapperProxy类中调用方法.
顾名思义,这个MapperProxy是个代理类,并且实现了InvocationHandler.

public class MapperProxy<T> implements InvocationHandler, Serializable {}

其实从这里我们就可以想到,实现的原理就是用到了动态代理.也就是实际调用的是spring帮我们生成的代理对象.
好像就是这么回事?但是这样显得浅尝辄止,如何生成代理对象,以及生成了怎样的代理对象的才是我们应该知道的.
之前我一篇文章讲过spring扩展的问题.也同样适用于mybatis.我们在不深究之前可以大致想下处理流程.肯定是mybatis通过spring的扩展后,在启动的时候,spring按规则扫描mybatis的xml,并解析最后生成了一个代理对象存入spring中以供使用.所以我们来看下mybatis在spring启动的时候都做了什么吧.
在进行spring扩展的时候,我们需要创建spring.handlers文件,因此我们只需要在mybatis-spring-1.2.2.jar(当然我用的是这个版本的jar)中找到即可.

http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler

核心类已经列出来了,我们看看NamespaceHandler是怎么处理的吧.

public class NamespaceHandler extends NamespaceHandlerSupport {

  /**
   * {@inheritDoc}
   */
  public void init() {
    registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
  }

}

再找到核心的解析类MapperScannerBeanDefinitionParser.

public class MapperScannerBeanDefinitionParser implements BeanDefinitionParser {

  private static String ATTRIBUTE_BASE_PACKAGE = "base-package";
  private static String ATTRIBUTE_ANNOTATION = "annotation";
  private static String ATTRIBUTE_MARKER_INTERFACE = "marker-interface";
  private static String ATTRIBUTE_NAME_GENERATOR = "name-generator";
  private static String ATTRIBUTE_TEMPLATE_REF = "template-ref";
  private static String ATTRIBUTE_FACTORY_REF = "factory-ref";

  /**
   * {@inheritDoc}
   */
  public synchronized BeanDefinition parse(Element element, ParserContext parserContext) {
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(parserContext.getRegistry());
    ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
    XmlReaderContext readerContext = parserContext.getReaderContext();
    scanner.setResourceLoader(readerContext.getResourceLoader());
    try {
      String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION);
      if (StringUtils.hasText(annotationClassName)) {
        @SuppressWarnings("unchecked")
        Class<? extends Annotation> markerInterface = (Class<? extends Annotation>) classLoader.loadClass(annotationClassName);
        scanner.setAnnotationClass(markerInterface);
      }
      String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE);
      if (StringUtils.hasText(markerInterfaceClassName)) {
        Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName);
        scanner.setMarkerInterface(markerInterface);
      }
      String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR);
      if (StringUtils.hasText(nameGeneratorClassName)) {
        Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName);
        BeanNameGenerator nameGenerator = BeanUtils.instantiateClass((Class<?>) nameGeneratorClass, BeanNameGenerator.class);
        scanner.setBeanNameGenerator(nameGenerator);
      }
    } catch (Exception ex) {
      readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
    }
    String sqlSessionTemplateBeanName = element.getAttribute(ATTRIBUTE_TEMPLATE_REF);
    scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName);
    String sqlSessionFactoryBeanName = element.getAttribute(ATTRIBUTE_FACTORY_REF);
    scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName);
    scanner.registerFilters();
    String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE);
    scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    return null;
  }

}

代码有点多,我们抓住重点,一个是开头的定义”base-package”;这个也是我们每次做新项目需要配置的(ps其实新项目每次都是copy),通过这个配置我们即告诉spring去哪里找到mapper.
然后一眼扫去,这段代码里面大致都是scanner这个变量,好像设置了一些属性之类的,到最后我们看到.

String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE);
    scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

这里用到了basePackage,并好像去做了写扫描(scan)的工作,应该就是去扫描我们的mapper了.
我们点进去看下,还好这是个类,并没有其他的实现ClassPathMapperScanner.

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);

        definition.getPropertyValues().add("addToConfig", this.addToConfig);

        boolean explicitFactoryUsed = false;
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
          definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionFactory != null) {
          definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
          explicitFactoryUsed = true;
        }

        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
          if (logger.isDebugEnabled()) {
            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
          }
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
      }
    }

    return beanDefinitions;
  }

这里果然就是扫描bean的地方.第一行代码就是调用父类方法,按路径从basePackages扫描到指定的beanDefinitions.接下来是对这些粗糙的bean进行处理,我们可以看到设置了mapperInterface,addToConfig,sqlSessionFactory,sqlSessionTemplate,这些都是mybatis调用所需的必要条件.不过这里有个定义是我们需要关注的.

definition.setBeanClass(MapperFactoryBean.class);

setBeanClass是指定spring实例化的bean是哪一个类的class,而这里似乎不是我们的dao层的接口,而是一个指定的通用的类MapperFactoryBean,那我们实例化后的bean都是这玩意吗.
的确,我们知道spring是个bean容器,每个bean都是需要实例化后存放在spring的bean工厂的.那么,dao层从我们最上面的分析来看,其实她最后是一个cglib动态代理类.也就是说她是无法被spring通过构造函数进行实例化的.这是啥意思了,也许你会问,动态代理类,不是已经是个活生生的实例化的对象了吗.哈哈,好像是的,不过我们设置的是class,spring会在她必须要实例化的时候,才会进行实例化对象,就是通过class对象反射无参构造函数进行实例化的哦,而动态代理类是无法通过构造函数再次实例化的.
所以这就是矛盾的所在,我们需要为dao层生成动态代理,但是却无法设置bean的class.怎么办呢,spring作为一个了不起的框架已经为我们提供了方案了,就是工厂bean.我们看看上面的MapperFactoryBean.

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  /**
   * Sets the mapper interface of the MyBatis mapper
   *
   * @param mapperInterface class of the interface
   */
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * If addToConfig is false the mapper will not be added to MyBatis. This means
   * it must have been included in mybatis-config.xml.
   * <p>
   * If it is true, the mapper will be added to MyBatis in the case it is not already
   * registered.
   * <p>
   * By default addToCofig is true.
   *
   * @param addToConfig
   */
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Throwable t) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
        throw new IllegalArgumentException(t);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  /**
   * {@inheritDoc}
   */
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  public boolean isSingleton() {
    return true;
  }

}

这个类实现了spring的FactoryBean,里面的核心方法是getObject(),也就是说我们在spring里面的bean最后实际使用的对象是getObject返回的也就是我们开头所说的MapperProxy动态代理类,如果你愿意的话,可以试试debug的.
概括下整个实现过程在于动态代理+工厂bean.联想下的话,其实我们常用的rpc也是一样的,rpc中consumer暴露的也是一个api,如果愿意去深入研究下的,会看到套路是一样的哦.好了,分析结束,非常nice.


版权声明:本文为ruanjian1111ban原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。