需要了解的前置知识:
- Bean的生命周期
- JDK动态代理
- FactoryBean实例化
在spring的配置文件中引入下面两个bean,spring启动的时候,会加载这两个bean
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.tt.spring.demo.pojo"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.tt.spring.demo.dao"/>
</bean>
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,因此spring会先进行实例化,并调用postProcessBeanDefinitionRegistry方法扫描BeanDefinition
在MapperScannerConfigurer类的postProcessBeanDefinitionRegistry方法中,创建了ClassPathMapperScanner对象,并调用了scan方法进行bean扫描
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// ...省略一堆set方法
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
在ClassPathMapperScanner的doScan方法中,根据指定的basePackages,扫描指定的bean(通过重写isCandidateComponent方法,让接口可以作为beanDefinition)
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
扫描完所有指定路径下的beanDefinition后,调用MapperScannerConfigurer的processBeanDefinitions方法对beanDefinition进行增强,我们重点看两个地方:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
for (BeanDefinitionHolder holder : beanDefinitions) {
definition.setBeanClass(this.mapperFactoryBeanClass); // (1)
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
// 我们在上面定义了sqlSessionFactoryBeanName,因此会走这个分支
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); // (2)
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
}
- 代码(1)处,设置了beanClass,该beanDefinition的实例实际就是mapperFactoryBeanClass对应的实例,默认为MapperFactoryBean.class
- 代码(2)处,spring创建实例时会先创建sqlSessionFactoryBeanName实例,然后调用setSqlSessionFactory方法设置该属性
最后spring将这些beanDefinitions都注册到spring中,等到后面将这些beanDefinition实例化
实例化:
由于在BeanDefinition中设置了beanClass为MapperFactoryBean,因此实例化该bean实际创建的是MapperFactoryBean对象,它是一个FactoryBean,会调用getObject方法创建bean对象
下面我们看下MapperFactoryBean的getObject方法
// MapperFactoryBean.class
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// MapperRegistry.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession); (1)
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
在代码(1)处真正实例化,继续往里看如何实例化的
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
实际就是创建了MapperProxy的代理对象,mapperInterface就是我们在Dao层创建的mapper接口,因此当我们在Service层注入的Dao层对象,实际就是MapperProxy的代理对象,调用入口就是MapperProxy类的invoke方法,最终会调用下面的方法执行增删改查操作
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// ...省略部分代码
result = sqlSession.selectOne(command.getName(), param);
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// ...省略部分代码
return result;
}