前面文章分析了这么多关于Mybatis源码解析,但是我们最终使用的却不是以前面文章的方式,编写自己mybatis_config.xml,而是最终将配置融合在spring的配置文件中。有了前面几篇博客的分析,相信这里会容易理解些关于Mybatis的初始化及其执行,但是仍旧需要Spring的很多知识,用到的时候会简略提到下。下面先看下我们具体使用Mybatis时候是怎样配置的。
mysql
这就是使用过程中的关于数据库相关的一个配置文件。里面主要涉及到下面两个Spring的初始化类。
SqlSessionFactoryBean类
负责Mybatis的初始化,最终会初始化出一个Configuration
MapperScannerConfigurer类
负责创建接口的代理,可以看到上面类实例化的时候传入的参数就是接口包
下面开始源码的具体分析。
(1)SqlSessionFactoryBean
从上面的配置文件可以看到这个类有几个属性,dataSource,plugin都比较熟悉,就不说了。主要讲解mapper.xml文件的初始化。
看一下这个类的继承结构
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener
实现了三个接口,简要分析一下这三个接口。
1.FactoryBean
工厂bean,实例化的时候不是返回对象本身,而是调用它的方法getObject()方法返回的对象,如果要获取FactoryBean对象,可以在id前面加一个&符号来获取
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
可以看到这个方法返回的是一个sqlSessionFactory(相信前面mybatis初始化的时候对这个类比较熟悉了),这个方法的逻辑也比较简单,如果这个属性没有初始化的化先执行初始化,初始化了直接返回,和假单例模式一样。
2.InitailizingBean
实现这个接口的bean,会在其实例化完成后,初始化阶段调用他的方法afterPropertiesSet()(可以看下spring bean的生命周期)
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
可以看到这个方法的逻辑代码就是最后一句,也是从这一句开启了mapper.xml的初始化,这也就是mybatis和spirng结合以后初始化的入口,后面具体分析。
3.ApplicationListener
是一个接口,里面只有一个onApplicationEvent方法。所以自己的类在实现该接口的时候,要实装该方法。如果在上下文中部署一个实现了ApplicationListener接口的bean,那么每当在一个ApplicationEvent发布到 ApplicationContext时,这个bean得到通知。其实这就是标准的Oberver设计模式。
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
这个不是分析的重点,有兴趣的自行查下。
下面从初始化入口开启分析:
this.sqlSessionFactory = buildSqlSessionFactory();
还记得前面一篇初始化配置文件为Configuration类的博客吗,整过过程切实初始化出来就是构建了SqlSessionFactory,SqlSessionFactory里包含Configuration。看这个方法的名字也是做的这件事,只是入口的方式不一样,下面看看是怎样归到一起的。
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 (logger.isDebugEnabled()) {
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 (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (logger.isDebugEnabled()) {
logger.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (logger.isDebugEnabled()) {
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 (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (logger.isDebugEnabled()) {
logger.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (logger.isDebugEnabled()) {
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 (logger.isDebugEnabled()) {
logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
方法看起来有点长,其实逻辑很简单,就是将配置文件中的各个配置属性读入进来,因为它得对每个属性判空,所以显得很长,其实执行的只是配置了属性的那几项。
分析一下这个方法的逻辑,第一个if语句,调到了else分支实例化了一个Configuration类出来,下一步将数据库的初始化读入进来。
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
接着就到了解析mapper文件的逻辑了:
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 (logger.isDebugEnabled()) {
logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}
mapperLocations可以以一个数组的方式将所有xml文件配置过来,然后逐步解析。这里其实就已经归到了前面博客的初始化过程了,后面就是逐个解析xml文件,解析每一个节点,将每个sql节点初始化为一个MappedStatement类,最终归入到Configuration里。初始化就讲解这么多。
(2) MapperScannerConfigurer
下面看这个类是怎么将接口创建为代理的。首先也看下这个类的继承结构。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
1.Aware接口
实现这些 Aware接口的Bean在被实例化 之后,可以取得一些相对应的资源,例如实现BeanFactoryAware的Bean在实例化后,Spring容器将会注入BeanFactory的实例,而实现ApplicationContextAware的Bean,在Bean被实例化后,将会被注入 ApplicationContext的实例等等。
2.InitalizingBean
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
没有什么逻辑,仅仅是一个对basePackage的判空操作
3.BeanDefinitionRegistryPostProcessor
这个接口:public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor。
实现该接口,可以在spring的bean创建之前,修改bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置'order'属性来控制各个BeanFactoryPostProcessor的执行次序。注意:BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的。
这个类的执行就是从这个后处理开始的:
/**
*
* @param registry 这个参数是spring注册beanDefiniton的地方,这个类里面有一个缓存map,专门存储beanDefinition
* @throws BeansException
*/
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//解决${jdbc.username}这种占位符的复制问题
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//这个类就是将接口扫描为BeanDefinition,并且最终被代理
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.registerFilters();
//扫描器的scan方法将basePackage扫描为beanDefinition
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
感觉理解Mybatis的知识还是得具备一些spring源码的知识,不然略微会有点不知所云,至少应该知道spring bean的生命周期,BeanDefinition的感念。
//逻辑转移到了doScan方法
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
这里的doScan方法调用的是ClassPathMapperScanner中的,这里算是类的多态特性。
@Override
public Set doScan(String... basePackages) {
//首先调用了父类的doScan方法,下面具体分析,比较关键,转化为了spring bean的形式
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
for (BeanDefinitionHolder holder : beanDefinitions) {
//这里就转化为了正常的spring中的bean的形式了,spring就是最开始将配置文件初始化为了一个GenericBeanDefinition
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//这种就是对MapperFactoryBean中的一些属性的赋值,(可以看些BeanWrapperImpl的相关知识)
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
//最最关键的一步,这就是beanDefintion的真正的类型,这里也就是将所有的接口类最终都按照MapperFactoryBean处理的
definition.setBeanClass(MapperFactoryBean.class);
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) {
//在MapperFactoryBean中找了一下sqlSessionFactory这个属性,没有找到,他的父类SqlSessionDaoSupport中也没有这个属性
//但是有sqlSession这个属性,其实这里给sqlSessionFactory赋值,就是调用他的set方法,看到这个类虽然没有这个属性,但是确实有这个
//属性的set方法,玄机都在这个 set方法里,最终跟踪下去实例化了蛮多对象
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
上面的方法特别注意两个地方,将所有的接口转化为MapperFactoryBean和为sqlSessionFactory赋值。这个方法结束以后就完成了将接口注册成为了spring中真正的bean了,但是还没有经历实例化,实例化的过程中会对其进行代理。
//看返回值就看得出,将传入的dao接口,最终扫描为BeanDefinitionHolder,这个类里包含这BeanDefinition属性
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
//显然basePackages也是可以配置为数组形式的
for (String basePackage : basePackages) {
//靠这个函数就将basePackage转化为最基本的beanDefinition
Set candidates = findCandidateComponents(basePackage);
//过滤处理所有生成的基本beanDefinition
for (BeanDefinition candidate : candidates) {
//bean是单例还是原型判断
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//取得beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
//封装到holder类中
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//注册到registry中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
public Set findCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet();
try {
//basePackage路径构造
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
//将路径转化为资源
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
//遍历每一个文件资源
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
//对资源内容的一个转化
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
//用传入的资源构建出基本的BeanDefinition,初始化bean的名称,注解等一些基本字段
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
到这里就从细节上完成了接口注册为bean的过程了,下面所有的动作就只能在MapperFactoryBean里了,下面详细分析这个类。
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
private boolean addToConfig = true;
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface class of the interface
*/
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means
* it must have been included in mybatis-config.xml.
*
* If it is true, the mapper will be added to MyBatis in the case it is not already
* registered.
*
* By default addToCofig is true.
*
* @param addToConfig
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* {@inheritDoc}
*/
@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 (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* {@inheritDoc}
*/
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
public Class getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
public boolean isSingleton() {
return true;
}
}
相信大家也看出来了这个类上的玄机,继承了一个类,同时实现了FactoryBean。首先看下继承的这个类,
public abstract class SqlSessionDaoSupport extends DaoSupport,是一个抽象类,同时又继承了另一个类
感觉以这种截图的方式讲解还蛮清晰的。看到这个类是不是明白了,最终相当于MapperFactoryBean实现了InitializingBean,看到afterProperties方法中调用了一个抽象方法,这个方法实在哪个子类中实现的呢?
mybatis与sping结合的所有初始化就结束了,到这里不仅早就有了Configuration类,sqlSession也被初始化完成了,下面就是用sqlSession调用执行方法的起始,getMapper的时候了,那么这个是怎么处理的呢,上面看到MapperFactoryBean还继承了工厂bean,所以这个bean被实例化的时候会调用他的getObeject方法,在这个方法中移花接木,生成了代理。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
其实只有这么一句,不过也看到了通过接口,调用getMapper方法了,这样就又归到了前面博客执行接口方法的流程里了,在这个方法调用完最终返回的是一个传入接口的动态代理。
-------------------------------------------------------------------------------20170910凌晨2:09的分割线----------------------------------------------------------------------------------
没什么想说的,其实也没有想象中的那么逻辑复杂,只是需要一点spring源码的知识。下一步期望可以分析一下Mybatis的sqlNode的问题,感觉这里还是不行,还有就是下一个系列SpringMVC的源码分析。