Mybatis Spring核心源码分析

##### 简介

在最开始接触Mybatis之前就好奇Mybatis是如何将我们执行我们定义的接口?其如何和我们编写的xml关联起来,最近又带着这个疑问分析了一下Mybatis Spring的源码,发现其核心也不复杂就是java 的动态代理。

##### 配置入口

依旧是老套路,我们先从入口分析,Mybatis需要使用@Mapper注解来在Spring中注册,我们只需要全局搜索看在哪里调用打@Mapper即可,在MybatisAutoConfiguration中找到了内部类以下方法

```
 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      if (!AutoConfigurationPackages.has(this.beanFactory)) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
        return;
      }

      logger.debug("Searching for mappers annotated with @Mapper");

      List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }

      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      builder.addPropertyValue("annotationClass", Mapper.class);
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
      BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
      Stream.of(beanWrapper.getPropertyDescriptors())
          // Need to mybatis-spring 2.0.2+
          .filter(x -> x.getName().equals("lazyInitialization")).findAny()
          .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
    }

  }
```

其实现了ImportBeanDefinitionRegistrar 接口,并且在外部类的配种中使用了@Import注解,那么在其初始化时就会调用registerBeanDefinitions方法,我们看到核心代码就是其在Spring中注册了一个MapperScannerConfigurer的类,我们继续进去查看

```
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware 
```

其实现了BeanDefinitionRegistryPostProcessor,InitializingBean这两个接口,其中BeanDefinitionRegistryPostProcessor在Bean被定义前没调用,而InitializingBean在初始化后被调用且InitializingBean并没有太多逻辑,所以我们重点查看BeanDefinitionRegistryPostProcessor的接口方法

```
@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
```

##### Mapper的扫描

我们看到这里新建了一个类ClassPathMapperScanner,并且调用了scan方法,看类命名我们就猜到其应该就是扫描@Mapper的地方,我们继续进去

```
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
```

发现其继承了ClassPathBeanDefinitionScanner类,而ClassPathBeanDefinitionScanner类时Spring 扫描类的核心,其会扫描配置路径下的所有类,这也印证了我们之前的猜想。我们寻该类型scan相关方法,发现其调用了父类的scan方法,父类又调用了doScan方法,而其重写了doScan方法

```
@Override
  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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
```

super.doScan 方法会扫描包下所有包含我们过滤条件的所有的类,并且将其注册到Spring,而在AutoConfiguredMapperScannerRegistrar配置类中,我们已经将我们需要扫描的注解@Mapper加入进来了,并且在MapperScannerConfigurer#postProcessBeanDefinitionRegistry中初始化了拦截器

```
builder.addPropertyValue("annotationClass", Mapper.class);
```

**我们顺着逻辑继续查看processBeanDefinitions方法,上面说到ClassPathBeanDefinitionScanner的doScan方法会扫描所有符合条件的类,并且注册到Spring,我们这里执行的了doScan方法,我们定义的接口已经被注册到Spring中了,如果我们这时候去调用肯定会报错,因为根本没有实现类,于是Mybatis投机取巧在processBeanDefinitions下修改BeanDefinition强行将@Mapper扫描到的接口关联到他的代理类中**

```
 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

      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) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }
```

这里 很多逻辑,我们并不需要全部都看懂,具体注意以下两点

```
 private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
     .
     .
     .
 definition.setBeanClass(this.mapperFactoryBeanClass);
     .
     .
     .
  definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
```

**它将我们@Mapper扫描的类定义强行替换成MapperFactoryBean,且将其Spring的注入类型改成根据类型自动注入,这样我们去Spring中获取相关类时只要有get set方法,Spring就能自动根据类型注入,不需要再去使用注解或者手动去注入,在刚开始时我一直没找到类属性的输入点,知道看到这里才恍然大悟**

所以这时候我们查看MapperFactoryBean的逻辑,第一步查看他的继承结构

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

其继承了SqlSessionDaoSupport 而SqlSessionDaoSupport又实现了InitializingBean,在Bean初始化时会去调用checkDaoConfig方法

而MapperFactoryBean重写了checkDaoConfig

```
@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 (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
```

我们继续进入 configuration.addMapper(this.mapperInterface); 方法一直往下找找到了MapperRegistry#addMapper方法

```
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
```

而这里就是解析Spring 我们编写xml的地方,并且解析完成后注册到Configuration的mapperRegistry中

```
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
```

##### Mapper对象的查找和注入

知道了xml是如何解析的,当时Mapper对象是如何注入的呢,我们现在知道了其是我们在Spring中注册的并不是我们定义的对象而是MapperFactoryBean ,我们查看其类的继承关系

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

**他继承了Spring的FactoryBean,之前网上只说FactoryBean可以实现懒加载,当时并不理解,知道看了这里的代码才知道,FactoryBean不止可以实现懒加载,还可以实现Bean对象实例化前的增强,和BeanPostProcessor异曲同工,而不同的是FactoryBean可以针对单个具体Bean对象做增强,而BeanPostProcessor需要遍历一遍所有Bean对象然后选择自己需要增强的Bean效率较低。**

我们顺着FactoryBean定义的接口getObject往下找,在Spring去查找Mapper对象时他回去调用FactoryBean的getObject对象去获取对象

```
 @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
```

继续往下我们发现其本质就是调用了Configuration下的mapperRegistry去获取对象的,而这个对象在我们之前解析xml时写入到mapperRegistry中

```
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

```

继续进入mapperRegistry.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.newInstance方法

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

再进入newInstance方法,发现其本质就是利用了java的反射

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

而MapperProxy类是其的代理类,所以我们使用Mybatis Spring时我们调用的就是使用动态代理的MapperProxy,而其根本没有调用原始声明的方法

```
@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 (method.isDefault()) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}
```

##### 总结

Mybatis Spring就是使用了java 的动态代理机制和Spring FactoryBean 增强机制,在Spring进行依赖注入时,使用动态代理技术,代理到我们代理的类上,其去执行sql语句,这也就是为什么我们去定义Mapper时只能用接口,这是由于java的动态代理只能使用接口而导致的,且利用依赖注入和动态代理的组合,往往能做到很多意想不到且有意思的事。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值