Mybatis学习笔记(一)- Mapper整合和注入原理分析流程

上期中我们主要学习了Spring的动态bean注册,其中的主要接口是ImportBeanDefinitionRegistrar,在文中我们还主要学习接口的上游做了哪些事情。今天我们主要通过mybatis的mapper管理来学习一下该接口的下游方法调用过程。据此也尝试搞清楚mybatis的mapper的管理过程。

对此我们就从registerBeanDefinitions方法看起。

 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//拿到具体的注解并转化成成annotationAttributes
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//实例化一个自定义的扫描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        if (this.resourceLoader != null) {
            scanner.setResourceLoader(this.resourceLoader);
        }
   //获取注解元素annotationClass
        Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            scanner.setAnnotationClass(annotationClass);
        }
//获取要被扫描过程检测的接口,由markerinterface元素指定。
        Class markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            scanner.setMarkerInterface(markerInterface);
        }
//设置类名生成器
        Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
        }
//设置mapper管理器,会将扫描的mapper类放到核心配置类configuration中
        Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
            scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
        }
//设置sqlsessionTemplate的名称
        scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
//设置sqlsessionfactory的名称
        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
        List basePackages = new ArrayList();
//获取要扫描的包名称
        String[] var10 = annoAttrs.getStringArray("value");
        int var11 = var10.length;


        int var12;
        String pkg;
        for(var12 = 0; var12 < var11; ++var12) {
            pkg = var10[var12];
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
//从beasePackages中获取要扫描的包名
        var10 = annoAttrs.getStringArray("basePackages");
        var11 = var10.length;


        for(var12 = 0; var12 < var11; ++var12) {
            pkg = var10[var12];
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
//从basePackageClasses中获取要扫描的类的全路径,然后获取要扫描的包名
        Class[] var15 = annoAttrs.getClassArray("basePackageClasses");
        var11 = var15.length;


        for(var12 = 0; var12 < var11; ++var12) {
            Class clazz = var15[var12];
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        String mapperHelperRef = annoAttrs.getString("mapperHelperRef");
        String[] properties = annoAttrs.getStringArray("properties");
        if (StringUtils.hasText(mapperHelperRef)) {
            scanner.setMapperHelperBeanName(mapperHelperRef);
        } else if (properties != null && properties.length > 0) {
            scanner.setMapperProperties(properties);
        } else {
            try {
                scanner.setMapperProperties(this.environment);
            } catch (Exception var14) {
                LOGGER.warn("只有 Spring Boot 环境中可以通过 Environment(配置文件,环境变量,运行参数等方式) 配置通用 Mapper,其他环境请通过 @MapperScan 注解中的 mapperHelperRef 或 properties 参数进行配置!如果你使用 tk.mybatis.mapper.session.Configuration 配置的通用 Mapper,你可以忽略该错误!", var14);
            }
        }
//设置可以被扫描到的标志
        scanner.registerFilters();
//进行for循环遍历
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }


在registerfilters方法中,就是通过设置需要被扫描的类的一些标记信息。要么是注解,要么是接口。

    public void registerFilters() {
        boolean acceptAllInterfaces = true;
        if (this.annotationClass != null) {
//设置需要被扫描的注解,通过上面的注解上的元素设定
            this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }
//设置需要被拦截的接口
        if (this.markerInterface != null) {
            this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
                protected boolean matchClassName(String className) {
                    return false;
                }
            });
            acceptAllInterfaces = false;
        }
//是否拦截所有的接口
        if (acceptAllInterfaces) {
            this.addIncludeFilter(new TypeFilter() {
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });
        }
//不需要被拦截的类型
        this.addExcludeFilter(new TypeFilter() {
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info") ? true : metadataReader.getAnnotationMetadata().hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
            }
        });
    }

通过上述两个步骤,我们知道mybatis先是通过@mapperscan注解设置一些值,然后通过设置需要被拦截的类的一些基本信息。然后就要开始for循环逐个扫描包路径了。

public SetdoScan(String... basePackages) {
//直接进行扫描,将符合条件的类beandefinition信息进行返回
        Set beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
//对符合条件的bean进行一些填充
            this.processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

Mybatis学习笔记(一)- Mapper整合和注入原理分析流程_第1张图片

寻找符合条件的bean。

 public SetfindCandidateComponents(String basePackage) {
        if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
               return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        }
        else {
               return scanCandidateComponents(basePackage);
        }
 }
//根据设置的拦截信息进行判断
       private boolean indexSupportsIncludeFilters() {
              for (TypeFilter includeFilter : this.includeFilters) {
                     if (!indexSupportsIncludeFilter(includeFilter)) {
                            return false;
                     }
              }
              return true;
       }


       private boolean indexSupportsIncludeFilter(TypeFilter filter) {
              if (filter instanceof AnnotationTypeFilter) {
                     Class annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
                     return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
                                   annotation.getName().startsWith("javax."));
              }
              if (filter instanceof AssignableTypeFilter) {
                     Class target = ((AssignableTypeFilter) filter).getTargetType();
                     return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
              }
              return false;
       }


//这块判断是接口哦,他可以扫描接口
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

通过上述对bean是否符合条件进行判断之后,就开始对扫描到的bean,也就是我们的mapper进行处理了。如下所示:

private void processBeanDefinitions(SetbeanDefinitions) {
        Iterator var3 = beanDefinitions.iterator();


        while(var3.hasNext()) {
//拿到具体的mapper
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
            }
//设置构造函数
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//设置该bean的name,mapperfactoryBean。这里设置主要是为了注入到mapperfacatorybean中
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            if (StringUtils.hasText(this.mapperHelperBeanName)) {
                definition.getPropertyValues().add("mapperHelper", new RuntimeBeanReference(this.mapperHelperBeanName));
            } else {
                if (this.mapperHelper == null) {
                    this.mapperHelper = new MapperHelper();
                }


                definition.getPropertyValues().add("mapperHelper", this.mapperHelper);
            }
//设置addToConfig属性
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            boolean explicitFactoryUsed = false;
            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
//sqlSessionFactory,设置sqlSessionFactory类
                definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionFactory != null) {
                definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                explicitFactoryUsed = true;
            }
//设置sqlSessionTemplate类名称
            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                if (explicitFactoryUsed) {
                    this.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) {
                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }


                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }


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


                definition.setAutowireMode(2);
            }
        }

我们通过查看MapperFactoryBean的构造函数发现如下:Mybatis学习笔记(一)- Mapper整合和注入原理分析流程_第2张图片

通过上述操作,我们知道我们的的mapper已经进入beandefinition中了,也就是mybatis需要的哪些接口。通过上述的一些分析。接口扫描和注册基本已经完成。现在就成了spring的整合问题。为此有了mybatis-spring的jar包用来整合。

sqlSessionFactoryBean类实现了接口initalizingbean。我们先看看这个接口做了哪些事情。

public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener;

我们看一下具体的实现

  

@Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
//创建sqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
  }


  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {


    final Configuration targetConfiguration;


    XMLConfigBuilder xmlConfigBuilder = null;
//xml解析
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
//设置xml路径等信息
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
//建一个解析器
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }


    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);


    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType)
          .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }


    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }


    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }


    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream()
          .filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }


    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }


    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }


    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);


    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }


    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));


    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
//开始解析了
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }


    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }


//进行xml转类
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {


      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
//将转出来的类转变为实体
      bindMapperForNamespace();
    }


    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

解析xml的

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
//在config中添加到configuration
          configuration.addMapper(boundType);
        }
      }
    }


在addMapper方法中

  

public void addMapper(Classtype) {
    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));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //缓存起来
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

这里的parse方法执行结束之后,我们的xml就进入knownMappers 保存。

Map, MapperProxyFactory> knownMappers = new HashMap<>();

但是这个addmapper又是在哪里调用的?

我们发现在MapperFactoryBean中有相关的操作。但是没有发现是谁调用了checkDaoConfig接口,所有去父类看一下。Mybatis学习笔记(一)- Mapper整合和注入原理分析流程_第3张图片

最后在父类中找到了initalizingBean的接口。于是有和spring有了关系。

public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());


    public DaoSupport() {
    }


    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
//这里进行了调用用
        this.checkDaoConfig();
        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }


    protected abstract void checkDaoConfig() throws IllegalArgumentException;


    protected void initDao() throws Exception {
    }
}

而这里的mapperInterface则是我们spring接口扫描的结果。我们还看到在addmapper方法的时候有判断这个接口对应的xml是否已经创建成功了。

Mybatis学习笔记(一)- Mapper整合和注入原理分析流程_第4张图片

同时,我们也发现MapperFactoryBean类也实现了FactoryBean,也就是这里的getObject方法。我们以前学习FactoryBean的时候说如果spring的getBean在内部找不到具体的类的话,就会从其他实现了FactoryBean的接口中获取。那么就这也就是我们开发中使用注解@Autowired注入了一个接口的原因所在。

通过对MapperFactoryBean的学习,我们发现这才是最后的大boss,除此那么既然发生了聚合,那么肯定需要一些基础的东西。于是我们发现Mybatis学习笔记(一)- Mapper整合和注入原理分析流程_第5张图片

也就是说MapperFactoryBean的时候需要SqlSessionFactory,然后通过sqlsessionFactory创建SqlSessionTemplate。最终调用的还是sqlSessionfactionbean中的configuration,configuration又调用了mapperRegistry。而最终也是通过JDK代理实现的。Mybatis学习笔记(一)- Mapper整合和注入原理分析流程_第6张图片

@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxymapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }


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

通过上述的分析,我们大概得可以知道mybatis的过程。首先通过spring接口扫描,然后再将扫描到的接口注入到mapperfactorybean中。而sqlSessionFactoryBean也是和spring相关系的,其通过配置文件找到需要解析的xml文件,然后实例化成代理类,并交由knownMappers进行管理。当mapperfactorybean初始化的时候,将扫描到的接口和xml解析的具体实体进行一一对应起来并放入到mapperRegistry中,等待spring注入的时候通过getBean方法进行实例化。能够注入的原因是mapperfactorybean实现了factoryBean接口。

最后我们大概得说一下,这些流程中的关键类,mapperfactorybean是主要的类。其中聚合sqlsessiontemplate,sqlsessiontemplate是从sqlsessionfactory创建而来。sqlsessionfactory又是从sqlsessionfactorybean中而来,sqlsessionfactorybean主要是解析xml文件并将其保存到knownMappers中的。

至此mybatis的主体逻辑大概想清楚了,那么sql的执行过程又是怎么样的?我们下期再看这个问题吧!

参考文献:

https://www.cnblogs.com/hei12138/p/mybatis-spring.html

你可能感兴趣的:(java,spring,mybatis,spring,boot,jvm)