這篇筆記主要來就,mybatis是如何利用spring的擴展點來實現和spring的整合spring
1.mybatis和spring整合以後,咱們就不須要使用sqlSession.selectOne()這種方式了,能夠直接從spring容器中獲取到接口的代理對象,而後調用對應的目標方法,那麼,mybatis在將接口交給spring管理的時候,用到了三個擴展點:sql
1.1 factoryBean mapperFactoryBean就是實現了factoryBean,而後,經過getObject方法來返回一個代理對象mybatis
1.2 mapperFactoryBean同時繼承了SqlSessionDao,SqlSessionDao繼承了DaoSupport,DaoSuppor有實現了InitializingBean,在初始化mybatis接口的時候,會調用DaoSupport的afterPropertiesSet()方法,也就是spring的初始化方法app
1.3 mapperScannerRegistrar實現了ImportBeanDefinitionRegistrar接口,在registerBeanDefinitions方法中,會對mapperScan註解聲明的包,進行掃描,class,掃描到beanDefinitionMap中,而後完成bean的初始化ui
2.mybatis在和spring整合的時候,須要用到一個註解 @MapperScan,這個註解使用了@Import,引入了mapperScannerRegistrar,關於import註解的做用,在spring源碼解析(一)中有介紹,不詳細說了,在將類put到beanDefinitionMap中的時候,會執行mapperScannerRegistrar的registryBeanDefintions方法,這時候,會調用doScan方法,將mapperScan對應包下的class掃描出來,放到beanDefinitionMap中,這裏要提的一個點是:this
@ComponentScan和@MapperScan,兩個註解的包可能會同樣,掃描出來的全部.class文件都是同樣的,可是注入到beanDefinitionMap中的時候,ComponentScan註解注入的是@Component、@Controller、@Repository、@Service註解對應的class,mapperScan注入的是接口對應的類,關於這個點,我debug看過源碼,在後面會把代碼貼出來spa
1 public Set<BeanDefinitionHolder> doScan(String... basePackages) { 2 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); 3 if (beanDefinitions.isEmpty()) { 4 this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); 5 } else { 6 this.processBeanDefinitions(beanDefinitions); 7 } 8 9 return beanDefinitions; 10 }
這個方法就是mapperScannerRegistrar中要調用的掃描方法,supper.doScan()會把包下全部的接口注入到beanDefinitionMap中,在注入完以後,會對mybatis的beanDefinition進行一些處理,在第六行的這個方法中,debug
1.把當前beanDefinition的beanClass設置爲mapperFactoryBean.calss,這樣設置,是爲了spring在對bean進行初始化的時候,會執行bean的初始化方法(就是上面說到的afterPropertiesSet())方法,在執行mybatis接口的初始化方法的時候,會根據beanClass,來調用mapperFactoryBean的getObject()方法來返回一個代理對象;設計
2.把beanDefinition的注入模型(autowiredMode)設置爲2(byType);這裏會設計到spring的注入模型,簡單而言,若是是byType,那麼,就不須要在bean中添加@Autowired和@Resource,只須要提供set方法便可(注意:這裏的byType和@Resource不同)代理
因爲mapperFatoryBean繼承了daoSupport,daoSupport又實現了InitializingBean,因此,spring容器在對mybatis的接口進行初始化以後,進行屬性注入,而後會進行初始化,調用DaoSuppro的afterPropertySet方法,其中,會調用子類的checkDaoConfig()方法,在checkDaoConfig中,調用了configuration.addMapper(this.mapperInterface);這個方法就是mybatis中,將class放到knownMappers這個map的操做
1 protected void checkDaoConfig() { 2 super.checkDaoConfig(); 3 Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required"); 4 Configuration configuration = this.getSqlSession().getConfiguration(); 5 if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { 6 try { 7 configuration.addMapper(this.mapperInterface); 8 } catch (Exception var6) { 9 this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6); 10 throw new IllegalArgumentException(var6); 11 } finally { 12 ErrorContext.instance().reset(); 13 } 14 } 15 16 }
那mapperFactoryBean的getObject()方法是在何時執行呢?假如說咱們在service中注入了mybatis的dao接口,在實例化service的時候,會進行屬性注入,屬性注入的時候,會發現,service中須要注入dao,這時候,會調用getBean()從spring容器中獲取,若是dao,沒有實例化,就會去實例化,在實例化完成以後,放到單實例池中,而後,會調用getObject()
在調用getObject的時候,會經過jdk動態代理來生成一個代理對象
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 2 MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); 3 if (mapperProxyFactory == null) { 4 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 5 } else { 6 try { 7 return mapperProxyFactory.newInstance(sqlSession); 8 } catch (Exception var5) { 9 throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); 10 } 11 } 12 }
這裏生成代理對象和原生mybatis生成代理對象有一個區別,就是這裏的sqlSession,原生的,用的是DefaultSqlSession,mybatis和spring整合以後,這裏用的是sqlSessionTemplate,那爲何會是sqlSessionTemplate呢?
在mapperFactoryBean的父類,SqlSessionDaoSupport中,會對sqlSession賦值,這時候,是直接new SqlSessionTemplate(SqlSessionFactory),因此,在和spring整合以後,mybatis用的sqlSession是sqlSessionTemplate
在實際調用目標方法的時候,會被mapperProxy的invoke方法攔截,和原生mybatis不一樣的時候,在判斷是select仍是update,以後,原生的mybatis會調用sqlSession.selectOne();和spring整合以後,會調用SqlSessionTemplate.selectOne(),而後再調用sqlSessionInterceptor.invoke()方法