看到阿里這道面試題的時候,我就知道是時候看下mybatis源碼了面試
<!-- 會話工廠bean sqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 數據源 -->
<property name="dataSource" ref="datasource"></property>
<!-- 別名 -->
<property name="typeAliasesPackage" value="com.jesse.bookstore.entities"></property>
<!-- sql映射文件路徑 -->
<property name="mapperLocations" value="classpath*:com/zhangguo/bookstore/mapper/*Mapper.xml"></property>
</bean>
<!-- 自動掃描對象關係映射 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定會話工廠,若是當前上下文中只定義了一個則該屬性可省去 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<!-- 指定要自動掃描接口的基礎包,實現接口 -->
<property name="basePackage" value="com.jesse.bookstore.mapper"></property>
</bean>
複製代碼
首先,Mybatis在初始化SqlSessionFactoryBean的時候,找到mapperLocations路徑去解析裏面全部的XML文件spring
1. 根據mapper中的每句SQL生成對應的SqlSourcesql
Mybatis會把每一個SQL標籤封裝成SqlSource對象。而後根據SQL語句的不一樣,又分爲動態SQL和靜態SQL。其中,靜態SQL包含一段String類型的sql語句;而動態SQL則是由一個個SqlNode組成。 緩存
** 以下面demo 就生成dynamicSqlSource** 生成的sqlsource2. 建立MappedStatement XML文件中的每個SQL標籤就對應一個MappedStatement對象,這裏面有兩個屬性很重要。 bash
3. 緩存到Configuration 全部xml解析完後,configuration對象具備全部sql信息 mybatis
configuration是mybatis很是重要的一個屬性講原理以前咱們得知道mybatis是怎麼用的app
public interface UserMapper {
List<User> getUserList();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userDao;
@Override
public List<User> getUserList() {
return userDao.getUserList();
}
}
複製代碼
userDao沒有任何實現,爲何能夠執行呢?ide
首先配置掃描器 post
配置了掃描器 又是怎麼生效的呢查看源碼注意到 有這麼一個類 ui
它實現了BeanDefinitionRegistryPostProcessor。 在spring中,它能夠 動態的註冊Bean信息,方法 postProcessBeanDefinitionRegistry()public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//建立ClassPath掃描器,設置屬性,而後調用掃描方法
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAnnotationClass(this.annotationClass);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
//若是配置了annotationClass,就將其添加到includeFilters
scanner.registerFilters();
scanner.scan(this.basePackage);
}
複製代碼
複製代碼ClassPathMapperScanner繼承自Spring中的類ClassPathBeanDefinitionScanner,因此scan方法會調用到父類的scan方法,而在父類的scan方法中又調用到子類的doScan方法。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//調用Spring的scan方法。就是將基本包下的類註冊爲BeanDefinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
}
複製代碼
super.doScan(basePackages)是Spring中的方法。我主要看它返回的是BeanDefinition的集合。 三、配置BeanDefinition 上面已經掃描到了全部的Mapper接口,並將其註冊爲BeanDefinition對象。接下來調用processBeanDefinitions()要配置這些BeanDefinition對象。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
//將mapper接口的名稱添加到構造參數
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//設置BeanDefinition的class
definition.setBeanClass(this.mapperFactoryBean.getClass());
//添加屬性addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
//添加屬性sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
......
}
}
複製代碼
複製代碼處理的過程很簡單,就是往BeanDefinition對象中設置了一些屬性。咱們重點關注兩個。
設置beanClass
設置BeanDefinition對象的BeanClass爲MapperFactoryBean<?>。這意味着什麼呢?以UserMapper爲例,意味着當前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那麼在IOC初始化的時候,實例化的對象就是MapperFactoryBean對象。
設置sqlSessionFactory屬性
爲BeanDefinition對象添加屬性sqlSessionFactory,這就意味着,在爲BeanDefinition對象設置PropertyValue的時候,會調用到setSqlSessionFactory()。
查看MapperFactoryBean
上面步驟瞭解到 咱們以前在BeanDefinition對象添加屬性sqlSessionFactory,也意味着setSqlSessionFactory()會被執行 進到裏面能夠看到sqlSession實際上就是SqlSessionTemplate 最終是給sqlSessionProxy實例化了一個jdk代理對象 在setSqlSessionFactory這個方法裏,sqlSession獲取到的是SqlSessionTemplate實例。而在SqlSessionTemplate對象中,主要包含sqlSessionFactory和sqlSessionProxy,而sqlSessionProxy其實是SqlSession接口的代理對象。如今每個mapper都是一個MapperFactoryBean MapperFactoryBean是一個工廠Bean
如今咱們經過spring注入一個Mapper @Autowired UserMapper userMapper 裝配時會執行如下代碼
有個問題是knownMappers是從哪兒來的呢?它爲何能夠根據type接口就能獲取到MapperProxyFactory實例呢? 查看DaoSupport發現它實現了InitializingBean 因此會在類初始化時 調用afterPropertiesSet,最終會調用到addMapper的方法getMapper 執行到new Instance()
也就是說 最終getObject獲取到的是一個MapperProxy 此時注入的就是一個MapperProxy當執行userMapper.XXX()時,會進入
重要的方法下面的mapperMethod.execute(sqlSession, args)這個方法比較簡單,就是根據節點的類型,進行相應的處理。好比節點是insert 那就走到insert的邏輯
若是節點類型是select,方法返回值是list,因此代碼執行了這個方法
重點方法在 sqlSession.selectList(command.getName(), param, rowBounds);上面講到sqlSession是sqlSessionTemplate 進入方法
sqlSessionProxy也是個代理對象,總之它實際會調用到SqlSessionInterceptor.invoke()。總的來講,就是 經過statement全限定類型+方法名拿到MappedStatement 對象,而後經過執行器Executor去執行具體SQL並返回
參考: [1]: juejin.im/post/5c84b4… [2]: juejin.im/post/5c84b4…