Mybatis對Spring的整合實現(xiàn)
本文只討論整合Spring,Mybatis是如何整合到Spring生態(tài)中的
接口掃描的MapperScan的實現(xiàn)和擴展
@MapperScan
, 元標注了@Import注解蝇刀,導入了一個MapperScannerRegistrar
的Configuration Class
, 申明如下
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
我們都知道ImportBeanDefinitionRegistrar
是Spring注入Configuration Class到容器中的一種常見手段, 常見的還有@Import
螟加,ImportSelector
.. , 該實現(xiàn)類的核心邏輯在registerBeanDefinitions()
中,如下
//importingClassMetadata 為當前標注了@Import的Configuration Class的注解元信息
//BeanDefinitionRegistry registry 為當前BeanFacatory的引用
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1. 獲取@MapperScan注解的屬性信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 2. 注冊一個名為 MapperScannerConfigurer的 Bean到IOC容器中
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
// 具體注冊Bean的邏輯
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
//1. 構造BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
//2. 設置自定義的注解熊泵,在后面自定義掃描有大用處
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
//3. 設置自定義的接口 Class仰迁,在后面自定義掃描有大用處
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
//....省略部分PropertyValues的屬性賦值
MapperScannerConfigurer
的用處以及實現(xiàn)原理
是一個
BeanDefinitionRegistryPostProcessor
的實現(xiàn)類,該類型會在Spring容器啟動刷新時進行回調
查看源碼發(fā)現(xiàn)其類的聲明如下
//1. 發(fā)現(xiàn)其是一個BeanDefinitionRegistryPostProcessor 顽分, 該類型接口會在IOC容器刷新的時候進行回調
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
//2. 能在回調方法中發(fā)現(xiàn)其核心做了兩件事情
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders(); //解析相關包名占位符
}
//2.1 創(chuàng)建自定義的ClassPathBeanDefinitionScanner(Spring中@ComponentScan核心處理類)并添加自定義的掃描類型
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
//2.2 進行掃描獲取BeanDefinition徐许,并注冊到容器中
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
接下來我們來看ClassPathMapperScanner
組件的用處 , 他其實是擴展了spring的@ComponentScan
的組件掃描方式卒蘸,核心看registerFilters()
方法雌隅,里面添加了要掃描的TypeFilter
的方式
public void registerFilters() {
boolean acceptAllInterfaces = true; //1. 這個標志位是否要掃描包下所有的接口
//2. 這里的annotationClass是前面注冊MapperScannerConfigurer時傳遞進來的自定義注解屬性
if (this.annotationClass != null) {
// 2.1 這里添加IncludeFilter表示,要添加一個允許的掃描注解缸沃,只要標注了該注解就會被ClassLoader掃描到
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
//3. 這里的annotationClass是前面注冊MapperScannerConfigurer時傳遞進來的自定義接口Class
if (this.markerInterface != null) {
//3.1 掃描自定義的接口類型舔箭,并且
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
//不能實現(xiàn)類Class,只能是抽象接口或者抽象類
return false;
}
});
acceptAllInterfaces = false;
}
//4. 如果沒有自定義注解或者自定義接口掃描仆潮,那么添加一個TypeFilter默認全部掃描所有
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
掃描時如何根據IncludeFilter/ExcludeFilter
進行掃描和過濾傀广?核心方法調用鏈如下
//調用鏈
//ClassPathMapperScanner#doScan() -> ClassPathBeanDefinitionScanner#doScan() -> ClassPathScanningCandidateComponentProvider#findCandidateComponents() -> scanCandidateComponents()
//其中scanCandidateComponents()方法具體實現(xiàn)如下
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
//1. 獲取傳遞進來的掃描包路徑
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//2. 使用ResourceLoader加載資源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
//3. 使用ASM進行元信息讀取
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//4. 這里很關鍵里面會進行IncludeFilter和ExcludeFilter的判斷,也是能自定義擴展組件掃描的核心方法
if (isCandidateComponent(metadataReader)) {
// 5. 拼裝成BeanDefinition翘单,后面會給BeanDefinition設置beanClass為MapperFactoryBean代理對象
//6. 最后注冊到IOC容器中吨枉,此時我們已經可以使用Mybatis的Mapper來完成依賴注入和依賴查找了
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
//省略部分無關源碼...
其中isCandidateComponent()
實現(xiàn)如下
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) { //遍歷所有的ExcludeFilter蹦渣,若有匹配的則返回false不進行掃描
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
//遍歷所有的IncludeFilter,若匹配則進行Conditional條件注解判斷貌亭,這里includeFilters中就包括了之前
//ClassPathMapperScanner#registerFilters()方法中注冊的includeFilters柬唯。這也是為什么我們配置了
// @MapperScan(basePakages="xxxx")就能掃描到xxx包下的所有類到ioc容器中的所有原理
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}