##### 简介

在最开始接触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的动态代理只能使用接口而导致的,且利用依赖注入和动态代理的组合,往往能做到很多意想不到且有意思的事。。