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源码的分析就到此为止,如有不妥当之处请大家指正。
- MyBatis源码简析
- Mybatis源码
- 【Mybatis】mybatis插件源码分析
- Mybatis源码研究序
- Mybatis源码赏析
- MyBatis源码研究-缓存
- Mybatis源码学习
- MyBatis 3源码分析
- MyBatis开始学习源码
- mybatis源码阅读心得
- MyBatis源码浅析
- MyBatis源码分析
- mybatis源码分析
- Mybatis修改源码记录
- MyBatis源码浅析
- myBatis源码之XMLConfigBuilder
- myBatis源码之Configuration
- mybatis源码之MapperMethod
- Multi tenancy
- 其他题目---KMP算法
- 微信 Tinker 在 Android 中集成以及使用
- Oracle数据库闪回FLASHBACK命令总结
- DateTime格式大全
- MyBatis源码简析
- 修改maven仓库的位置/本地目录
- JQuery3.1.1源码解读(十九)【ajax】
- 实习第三天
- Fresco属性及 scleType大全
- Spring Boot系列(四)Spring Boot Maven插件
- 1006. 换个格式输出整数 (15)
- matlab安装libsvm
- ibatis使用Oracle的regexp_replace等正则函数时需要注意转义问题