承接前文Spring mybatis源碼篇章-Mybatis的XML文件加載,本文將在前文的基礎上講解Spring在Mybatis整合方面的另一動作
前話
根據前文的分析可得到以下結論
-
MappedStatement是mybatis操作sql語句的持久層對象,其id由注解模式的
${mapperInterface類全名}.${methodName}
或者XML模式的${namespace}.${CRUD標簽的id}
確定,且是唯一的 -
Mybatis對每個CRUD語句都會生成唯一的MappedStatement保存至Configuration的mappedStatements(Map集合)中
-
Mybatis提供注解模式和XML模式生成MappedStatement,在兩者同時存在的情況下,前者會覆蓋后者
-
XML模式生成的MappedStatement,是保存在org.apache.ibatis.session.Configuration對象中的mappedStatements屬性中(Map),並且也將
${namespace}
對應的DAO類存放至mapperRegistry屬性里,供外部調用。
本文主要分析的是第四點,因為第四點的mapperRegistry類有一個特別的方法
// SqlSession就是關鍵
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {}
我們都知道Spring在整合Mybatis的時候都會配置一個SqlSessionFactoryBean對象來生成一個SqlSessionFactory,而這個SqlSessionFactory就是作為SqlSession(數據庫會話)的關鍵部分,那么Spring又怎么把這個對象與DAO接口類關聯放在mapperRegistry里呢????
老方式
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.test.sqlmapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
上述的配置是針對單個的mapperInterface注入到應用程序中,試想如果有很多的接口則會導致Spring主配置文件臃腫,所以上述的辦法已過時
新方式
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<!-- optional unless there are multiple session factories defined -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
采用MapperScannerConfigurer掃描類來實現對basePackage指定的DAO接口集合統一關聯SqlSession,這就節省了之前老配置很多的代碼空間。
MapperScannerConfigurer
直接一睹MapperScannerConfigurer類的源碼風采,優先查看其內部屬性
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
....
}
其源碼上的注釋其實寫的很清楚了,注釋篇幅過長,就不在這里展示了,稍微提下這個類的相關使用:
basePackage 基本屬性,接口類掃描的包路徑,支持,;分隔
sqlSessionFactoryBeanName 當有多個SqlSessionFactory環境時,官方通過其來指定加載特定的sqlSessionFactory,value即為bean的id值(建議使用此屬性)
sqlSessionFactoty 默認是不用填的,其會去尋找id為sqlSessionFactory的sqlSessionFactory實例,sqlSessionTemplate的操作類似
annotationClass 注解類,其會去Spring環境下尋找擁有此注解的接口類,並忽略basePackage的屬性,默認為null
markerInterface 父類接口類,其會去尋找繼承此接口類的子接口類但不包括父類接口,並忽略basePackage的屬性,與
annotationClass
並存,默認為null
MapperScannerConfigurer#postProcessBeanDefinitionRegistry()
直接進入其關鍵方法,觀察下是如何進行掃描的
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//支持${basePackage}形式
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//base classpath environment to scan
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);
//base annotationClass and markerInterface properties to register interface filters
scanner.registerFilters();
// scan
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
上述代碼也就是設置相應的屬性給ClassPathMapperScanner,具體的如何掃描我們繼續往下看
ClassPathBeanDefinitionScanner
掃描的具體操作由ClassPathMapperScanner的父類ClassPathBeanDefinitionScanner來完成,我們簡單的看下其中的邏輯
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
// 找尋classpath對應的目錄,其中的解析幫助類為PathMatchingResourcePatternResolver
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍歷
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 設置基本的屬性,比如lazy-init/autowire-mode等
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 對針對@mapper注解的bean
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 確保不重復注冊到bean工廠
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上述代碼最關鍵的便是找尋對應目錄的所有interface接口,其是通過PathMatchingResourcePatternResolver幫助類來完成的,后續筆者獨立成篇加以分析
對找尋的beanDefinitions集合過程中也會進行filters過濾,即用到了annotationClass和markerInterface屬性
ClassPathMapperScanner
針對上述的符合條件后獲取的beanDefinitions集合,子類便要進行最后的加工處理
/**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//由父類去找到符合條件的interface類,並轉化為bean類
Set<BeanDefinitionHolder> 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) {
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
//最終將definition包裝成MapperFactoryBean,beanClass設置為其內部屬性MapperInterface
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//根據sqlsessionFactoryBeanName尋找運行狀態的SqlsessionFactory的虛引用,但並沒有去真實加載
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
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;
}
//當沒有指定SqlSession對象,則設置MapperFactoryBean自動去找尋
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,由其完成最終的關聯
MapperFactoryBean
筆者此處只關注其關鍵方法checkDaoConfig(),源碼如下
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
// 判斷mapperInterface接口是否已被注冊過
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 此處就跟mybatis加載主配置文件時對mapper節點指定package屬性或者mapperClass屬性的入口是一樣的;xml方式的加載也是會走這一步
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();
}
}
}
上述主要是調用Configuration#addMapper()方法來將相應的DAO接口放入mapperRegistry中的knownMappers屬性,我們可以看下這個屬性的類型
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
恩,有動態代理的味道~~~
怎么取相應的DAO接口呢供外界調用呢,就是getObejct()方法
public T getObject() throws Exception {
// 間接關聯了SqlSession與mapperInterface
return getSqlSession().getMapper(this.mapperInterface);
}
通過上述的代碼便調用了上文所提及問題的mapperRegistry#getMapper()方法了,但具體的如何調用我們后文分析~~
總結
作下簡單的小結
MapperScannerConfigurer類主要實現將basePackage包下的所有接口類都包裝成MapperFactoryBean對象,內含相應的SqlSessionFactory數據庫會話
MapperScannerConfigurer類默認情況下在形成MappedStatement的過程中會優先去找尋與接口同目錄下的XML文件來加載生成。這點與Mybatis的主文件加載方式類同~~~只不過其更方便
MapperScannerConfigurer一般結合sqlSessionFactoryBean的mapperLocations屬性來完成Mybatis主文件的mappers的工作~~
官方以及筆者推薦大家使用MapperScannerConfigurer與SqlSessionFactoryBean搭配使用~~~~