MyBatis源码简析

来源:互联网 发布:java静态变量 定义 编辑:程序博客网 时间:2024/06/09 21:39

MyBatis用的时间也不算短了,虽然应用的很熟练了,但是源码并没有研究过,今天就“忙里偷闲”

来研究一下。实际中使用MyBatis很少单独使用的,几乎都是和Spring配合使用,所以我们先看

一下常见的在Spring中配置MyBatis bean的xml代码

可以看到MyBatis bean对应的Class就是SqlSessionFactoryBean,因此我们就从这个类开始

研究起。首先这个类里面有很多属性,比如:configLocation、dataSource等,这个了实现了

InitializingBean,FactoryBean这两个接口:

InitializingBean:实现此接口的类都会在bean初始化的时候调用此接口的afterPropertiesSet

方法来完成bean的初始化。

FactoryBean:一旦实现此类,那么通过getBean方法获取到的bean就是通过此接口的

getObject方法返回的实例。

所以我们先从afterPropertiesSet方法看起,里面有一个buildSqlSessionFactory方法,具体

逻辑都在这个方法里面:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {    Configuration configuration;    XMLConfigBuilder xmlConfigBuilder = null;    if (this.configLocation != null) {      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);      configuration = xmlConfigBuilder.getConfiguration();    } else {      if (this.logger.isDebugEnabled()) {        this.logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");      }      configuration = new Configuration();      configuration.setVariables(this.configurationProperties);    }    if (this.objectFactory != null) {      configuration.setObjectFactory(this.objectFactory);    }    if (this.objectWrapperFactory != null) {      configuration.setObjectWrapperFactory(this.objectWrapperFactory);    }    if (hasLength(this.typeAliasesPackage)) {      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);      for (String packageToScan : typeAliasPackageArray) {        configuration.getTypeAliasRegistry().registerAliases(packageToScan,                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);        if (this.logger.isDebugEnabled()) {          this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");        }      }    }    if (!isEmpty(this.typeAliases)) {      for (Class<?> typeAlias : this.typeAliases) {        configuration.getTypeAliasRegistry().registerAlias(typeAlias);        if (this.logger.isDebugEnabled()) {          this.logger.debug("Registered type alias: '" + typeAlias + "'");        }      }    }    if (!isEmpty(this.plugins)) {      for (Interceptor plugin : this.plugins) {        configuration.addInterceptor(plugin);        if (this.logger.isDebugEnabled()) {          this.logger.debug("Registered plugin: '" + plugin + "'");        }      }    }    if (hasLength(this.typeHandlersPackage)) {      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);      for (String packageToScan : typeHandlersPackageArray) {        configuration.getTypeHandlerRegistry().register(packageToScan);        if (this.logger.isDebugEnabled()) {          this.logger.debug("Scanned package: '" + packageToScan + "' for type handlers");        }      }    }    if (!isEmpty(this.typeHandlers)) {      for (TypeHandler<?> typeHandler : this.typeHandlers) {        configuration.getTypeHandlerRegistry().register(typeHandler);        if (this.logger.isDebugEnabled()) {          this.logger.debug("Registered type handler: '" + typeHandler + "'");        }      }    }    if (xmlConfigBuilder != null) {      try {        xmlConfigBuilder.parse();        if (this.logger.isDebugEnabled()) {          this.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();      }    }    if (this.transactionFactory == null) {      this.transactionFactory = new SpringManagedTransactionFactory();    }    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);    configuration.setEnvironment(environment);    if (this.databaseIdProvider != null) {      try {        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));      } catch (SQLException e) {        throw new NestedIOException("Failed getting a databaseId", e);      }    }    if (!isEmpty(this.mapperLocations)) {      for (Resource mapperLocation : this.mapperLocations) {        if (mapperLocation == null) {          continue;        }        try {          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),              configuration, mapperLocation.toString(), configuration.getSqlFragments());          xmlMapperBuilder.parse();        } catch (Exception e) {          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);        } finally {          ErrorContext.instance().reset();        }        if (this.logger.isDebugEnabled()) {          this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");        }      }    } else {      if (this.logger.isDebugEnabled()) {        this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");      }    }    return this.sqlSessionFactoryBuilder.build(configuration);  }

通过XMLMapperBuilder读取xml配置文件的内容,并通过configuration来承载每一步获取到的信息,最后使用

SqlSessionFactoryBuilder的build方法创建SqlSessionFactory实例。

public SqlSessionFactory build(Configuration config) {    return new DefaultSqlSessionFactory(config);  }

由于实现了FactoryBean接口,所以通过getBean方法获取到的bean就是通过此接口的

getObject方法返回的实例SqlSessionFactory。


然后我们再来研究一下MapperFactoryBean这个类:这个类同样继承了InitializingBean、

FactoryBean这两个接口。所以bean初始化的时候会执行afterPropertiesSet这个方法,

通过看源码我们发现实在MapperFactoryBean的父类DaoSupport中实现的这个方法,


进入这个方法,主要逻辑在checkDaoConfig方法中实现,

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();      }    }  }

主要操作是通过addMapper方法,遇到

<mapper namespace="com.mapper.TCustBaseInfoDtoMapper">这样的便

会注册映射接口。然后通过getObject方法获取实例化bean

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

可以看到这里通过getMapper方法来获取mapper,这个方法也是在独立使用MyBatis

会用到的,被Spring通过FactoryBean进行了封装。


最后再来研究一下MapperScannerConfigurer这个类,可以看到继承了我们前面研究

过的InitializingBean这个类,但是进入到afterPropertiesSet这个方法之后就失望了,

因为里面几乎没什么重要的东西,好吧,只能继续看实现的其他接口了,查看

一下实现BeanDefinitionRegistryPostProcessor这个接口的方法

postProcessBeanDefinitionRegistry

 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)/*     */   {/* 304 */     if (this.processPropertyPlaceHolders) {/* 305 */       processPropertyPlaceHolders();/*     */     }/*     */     /* 308 */     ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);/* 309 */     scanner.setAddToConfig(this.addToConfig);/* 310 */     scanner.setAnnotationClass(this.annotationClass);/* 311 */     scanner.setMarkerInterface(this.markerInterface);/* 312 */     scanner.setSqlSessionFactory(this.sqlSessionFactory);/* 313 */     scanner.setSqlSessionTemplate(this.sqlSessionTemplate);/* 314 */     scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);/* 315 */     scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);/* 316 */     scanner.setResourceLoader(this.applicationContext);/* 317 */     scanner.setBeanNameGenerator(this.nameGenerator);/* 318 */     scanner.registerFilters();/* 319 */     scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));/*     */   }

这次找对位置了,然后大概看一下,确实实现了指定路径扫描的逻辑,

进入到processPropertyPlaceHolders方法,

private void processPropertyPlaceHolders()/*     */   {/* 330 */     Map<String, PropertyResourceConfigurer> prcs = this.applicationContext.getBeansOfType(PropertyResourceConfigurer.class);/*     */     /* 332 */     if ((!prcs.isEmpty()) && ((this.applicationContext instanceof GenericApplicationContext)))/*     */     {/* 334 */       BeanDefinition mapperScannerBean = ((GenericApplicationContext)this.applicationContext).getBeanFactory().getBeanDefinition(this.beanName);/*     */       /*     */ /*     */ /*     */ /* 339 */       DefaultListableBeanFactory factory = new DefaultListableBeanFactory();/* 340 */       factory.registerBeanDefinition(this.beanName, mapperScannerBean);/*     */       /* 342 */       for (PropertyResourceConfigurer prc : prcs.values()) {/* 343 */         prc.postProcessBeanFactory(factory);/*     */       }/*     */       /* 346 */       Object values = mapperScannerBean.getPropertyValues();/*     */       /* 348 */       this.basePackage = updatePropertyValue("basePackage", (PropertyValues)values);/* 349 */       this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", (PropertyValues)values);/* 350 */       this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", (PropertyValues)values);/*     */     }/*     */   }

可以看到其中registerBeanDefinition这个方法是DefaultListableBeanFactory实现BeanDefinitionRegistry这个

接口中的方法,而BeanDefinitionRegistry会在应用启动的时候调用,并且早于

BeanFactoryPostProcessor的调用,这意味着PropertyResourceConfigurer还没

有被加载,所有属性文件的引用会失效,为了避免这种情况的发生,可以

手动定义PropertyResourceConfigurer保证对属性的引用有效。如何手动定义呢?

在配置MapperScannerConfigurer的时候加上下图圈出来的这句话,因为这里的


basePackage使用的写死的值,没有引用properties文件的值,所以这里可以不加这个配置,

但是为了解释清楚这个属性的作用,所以说了一堆。

回到postProcessBeanDefinitionRegistry,继续往下看,通过ClassPathMapperScanner

扫描并设定属性值,然后有个registerFilters方法,通过字面理解应该是生成了一个

过滤器来过滤扫描结果,进去看一下

public void registerFilters()/*     */   {/* 118 */     boolean acceptAllInterfaces = true;/*     */     /*     */ /* 121 */     if (this.annotationClass != null) {/* 122 */       addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));/* 123 */       acceptAllInterfaces = false;/*     */     }/*     */     /*     */ /* 127 */     if (this.markerInterface != null) {/* 128 */       addIncludeFilter(new AssignableTypeFilter(this.markerInterface)/*     */       {/*     */         protected boolean matchClassName(String className) {/* 131 */           return false;/*     */         }/* 133 */       });/* 134 */       acceptAllInterfaces = false;/*     */     }/*     */     /* 137 */     if (acceptAllInterfaces)/*     */     {/* 139 */       addIncludeFilter(new TypeFilter()/*     */       {/*     */         public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {/* 142 */           return true;/*     */         }/*     */       });/*     */     }/*     */     /*     */ /* 148 */     addExcludeFilter(new TypeFilter()/*     */     {/*     */       public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {/* 151 */         String className = metadataReader.getClassMetadata().getClassName();/* 152 */         return className.endsWith("package-info");/*     */       }/*     */     });/*     */   }/*     */   

在121行可以看到对annotationClass属性的处理:如果用户设置了这一属性,就要

生成一个过滤器已达到开发者的目的,而封装此属性的过滤器AnnotationTypeFilter

可以保证在扫描mapper文件的时候只接受标注有annotationClass注解的mapper,

在127行可以看到对markerInterface属性的处理,此扫描过程表示只有实现了

markerInterface接口的接口才会被接收。

第148行往后的内容,表示把名为package-info的java文件排除掉。


至此,对于MyBatis源码的分析就到此为止,如有不妥当之处请大家指正。



原创粉丝点击