本文主要解析spring是如何与mybatis进行整合,整合的过程中需要哪些组件的支持。以前面提到过的配置例子《spring源码学习之aop事物标签解析》
整合的过程中需要使用以下这个依赖包:
org.mybatis
mybatis-spring
1.2.2
spring启动的时候需要使用一个bean.xml配置文件,
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
bean.xml文件中就涉及到整合的配置信息,整合使用到以下的配置:
首先我们分析一下,这个MapperScannerConfigurer类在实例化过程中做了什么主要的事情。首先看看它的类图:
这个类中主要的方法就是这个postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,看看具体内容:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
this.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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
这个方法中创建了一个spring-mybatis.jar包中的ClassPathMapperScanner扫描器,这个扫描器继承了spring的ClassPathBeanDefinitionScanner。
ClassPathMapperScanner这个扫描器的主要的作用有以下几个:
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
sbd.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
sbd.setBeanClass(MapperFactoryBean.class);
sbd.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
以上就是实例化MapperScannerConfigurer类的主要工作,总结起来就是扫描basePackage包下所有的mapper接口类,并将mapper接口类封装成为BeanDefinition对象,注册到spring的BeanFactory容器中。以下时序图不代表实际过程。
mapper接口注册之后,在什么地方实例化和使用呢?后面在分析。
接着看看spring和mybatis整合的另外一个标签。
这个sqlSessionFactory的实现类中做了什么事情。首先看看这个类的声明:
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
//省略代码
}
从配置来看,这个Bean至少提供了两个方法,一个是和dataSource有关,一个和mapperLocations有关,
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
this.dataSource = ((TransactionAwareDataSourceProxy)dataSource).getTargetDataSource();
} else {
this.dataSource = dataSource;
}
}
public void setMapperLocations(Resource[] mapperLocations) {
this.mapperLocations = mapperLocations;
}
这个两个方法,啥也没干,就是做了一次赋值操作。在来一张Bean的类图,实现了几个接口,那就必须实现这几个接口的方法。那就继续看看实现的方法中,都干了什么事情。
在这个bean被创建的过程中,首先被调用的方法是afterPropertiesSet,这个方法是接口InitializingBean中的方法。
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
在这个buildSqlSessionFactory()方法中,做了很多事情,但是有些不是必须做的,为了简化分析,我们只关心必须要做的事情。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration configuration;
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
//省略代码
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] arr$ = this.mapperLocations;
len$ = arr$.length;
for(i$ = 0; i$ < len$; ++i$) {
Resource mapperLocation = arr$[i$];
if (mapperLocation != null) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} finally {
ErrorContext.instance().reset();
}
}
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
首先通过一个关键字new创建了对象Configuration,这个对象是mybatis框架的一个核心类,在这里我们不做详细介绍,以后再剖析。接着又创建了new SpringManagedTransactionFactory(),后面介绍这个类的作用,此处略过。接着继续创建new Environment(this.environment, this.transactionFactory, this.dataSource),这个Environment类中持有事物工厂和数据源的引用。接下来就是创建XMLMapperBuilder对象,并且调用了xmlMapperBuilder.parse()方法,这个方法的详细,不在此分析,也不是我们这篇文章要记录的重点,否则会偏离我们的主题,parse()这个方法就是在解析mapperLocation变量所代表的就是mybatis的一个xml配置文件,mapperLocation-->AuthUserMapper.xml,AuthUserMapper.xml的部分代码如下:
//此处代码有省略
update t_auth_user
set user_account = #{userAccount,jdbcType=VARCHAR},
password = #{password,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP},
is_delete = #{isDelete,jdbcType=TINYINT}
where id = #{id,jdbcType=BIGINT}
解析这个xml文件内容,然后将解析的结果封装成不同的对象,存放到Configuration对象中。
xmlMapperBuilder.parse()方法执行完成之后,调用this.sqlSessionFactoryBuilder.build(configuration),这个sqlSessionFactoryBuilder 构造器在哪儿创建的呢?其他它就是SqlSessionFactoryBean的一个私有类变量,初始化SqlSessionFactoryBean的时候,就实例化了这个sqlSessionFactoryBuilder。
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
看看这个build()方法做什么了。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
构造器做的事情很简单,直接创建了DefaultSqlSessionFactory,其中还持有Configuration对象的引用。到此afterPropertiesSet()方法做的做的事情就结束了。
总结起来,就是创建了几个对象,依次是mybatis的核心类Configuration、spring和mybatis集成的事物工厂类SpringManagedTransactionFactory、mybatis的Environment类、mybatis的DefaultSqlSessionFactory类,同时还完成了对mybatis的xml文件解析,并将解析结果封装在Configuration类中。以下时序图不代表实际的过程。
调用完上面的afterPropertiesSet方法之后,第二个被调用的就是onApplicationEvent方法,这个方法的调用时机是,spring容器初始化完成之后,该方法是接口ApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
if (this.failFast && event instanceof ContextRefreshedEvent) {
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
调试发现this.failFast这个变量的值是false,所以这个方法不会执行,在此就不做重点分析了。
调用完上面的onApplicationEvent方法之后,第三个被调用的就是getObject方法,该方法是接口FactoryBean
一般情况下,Spring通过反射机制利用
实现了FactoryBean
通过这段对FactoryBean的简短介绍就知道,getObject()方法被调用时机是在spring实例化SqlSessionFactoryBean的过程中。那么调用的getObject()方法返回的是什么对象呢,看看方法内容:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
从内容上看,是直接返回了一个SqlSessionFactory类的实例,这个sqlSessionFactory对象是在什么时候创建的呢?其实就是本文上面介绍的afterPropertiesSet()方法中,sqlSessionFactory引用的对象就是mybatis的DefaultSqlSessionFactory类的实例。至于这个类是什么作用,在哪儿用的,后面在作介绍。
上面两个标签的解析过程分析完成,现在有两个问题没有答案,第一个是basePackage基础包下面扫描出来的mapper接口怎么实例化的?第二个是这两个标签创建出来的对象怎么配合使用的?
在本系列的例子中,spring容器在启动的过程中,需要去实例化AuthUserServiceImpl服务类,
@Service
public class AuthUserServiceImpl implements IAuthUserService {
@Resource
private AuthUserMapper authUserMapper;
public AuthUserServiceImpl(){
logger.info("创建 com.test.bean.AuthUserServiceImpl");
}
}
这个类依赖了AuthUserMapper接口,在实例化AuthUserServiceImpl的过程中需要,首先去实例化这个AuthUserMapper接口,但是接口是不能被实例化的,接下来分析这个接口实现类的创建过程。以下时序图不代表实际执行过程。
对上面的图做一个简单的说明,spring在初始化的过程中,会去创建AuthUserServiceImpl类,创建完成之后,会进行属性赋值,AuthUserMapper这个mybatis接口就是AuthUserServiceImpl的一个属性,
首先根据这个mapper的名字从spring的BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,AuthUserMapper对应的BeanClass就是MapperFactoryBean,这是为什么呢?在上面分析的内容中提到过,也就是在创建MapperScannerConfigurer对象的时候设置的。
接着就是创建MapperFactoryBean对象了,创建完成之后,就需要对属性进行赋值,他有哪些属性需要赋值呢?这是在创建AuthUserMapper所对应BeanDefinition对象的时候决定的,回顾上面创建MapperScannerConfigurer对象的那部分内容就知道。其中有一个属性就是SqlSessionFactoryBean,要实例化这个对象,这个就需要用到下面这个标签了
上面的内容也已经分析过这个SqlSessionFactoryBean的创建过程。
MapperFactoryBean对象的属性设置完成之后,就调用它的getObject()方法,来获取authUserMapper对应的实现类,从上面图中可以看出来,最后返回的就是一个代理类,这个代理类使用jdk的动态代理创建出来的。
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(),
new Class[]{this.mapperInterface}, mapperProxy);
这个MapperProxy类就是InvocationHandler的实现类:
public class MapperProxy implements InvocationHandler, Serializable {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
}
程序在调用authUserMapper对象的某个方法的时候,就会调用到MapperProxy对象的invoke()方法,去完成对数据库的操作。
最后附上一张类图,spring和mybatis整合过程中创建的类: