Mybatis加載Mapper
注冊方式 | 注冊時機 | 特點 |
---|---|---|
@MapperScan | Bean定義階段注冊接口定義 | 批量注冊,推薦方式 |
@Mapper (接口注解) | 同 @MapperScan | 需每個接口單獨標注 |
XML 配置 <mapper> | MyBatis 初始化時 | 傳統方式,不依賴 Spring 容器 |
SqlSessionTemplate 直接獲取 | 調用時 | 編程式獲取,不自動注冊為 Bean |
@MapperScan注冊mapper
// 使用:在啟動類上添加注解
@MapperScan("com.zy.**.mapper")
@Import(MapperScannerRegistrar.class) // 此處使用了@Import注解,用于導入其他配置
@Repeatable(MapperScans.class)
public @interface MapperScan {...;
}
/**
1.指示要導入的一個或多個組件,通常是@Configuration類。
2.提供與Spring XML中的<import/>元素等效的功能。
3.允許導入@Configuration類,ImportSelector和ImportBeanDefinitionRegistrar實現,以及常規組件類(從4.2開始,類似于AnnotationConfigApplicationContext.register)。
4.導入的@Configuration類中聲明的@Bean定義應該使用@Autowired注入來訪問。也可以自動連接聲明bean的配置類實例。
5.如果需要導入XML或其他非@Configuration bean定義資源,請改用@ImportResource注解。
*/public @interface Import {/*** {@link Configuration @Configuration}, {@link ImportSelector},* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.*/Class<?>[] value();}
// 實現了ImportBeanDefinitionRegistrar接口,此處涉及到Spring加載BeanDefinition的邏輯
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {private ResourceLoader resourceLoader;// 重寫ImportBeanDefinitionRegistrar 接口中的registerBeanDefinitions 方法@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 1. 獲取@MapperScan注解屬性AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));// 2. 實際注冊邏輯if (mapperScanAttrs != null) {registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));}}/**annoMeta 被注解類的元數據annoAttrs @MapperScan 注解的屬性值registry : Spring bean 定義注冊器beanName :要注冊的 Bean 名稱*/void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,BeanDefinitionRegistry registry, String beanName) {
// 創建MapperScannerConfigurer 的通用Bean定義構建器。此處還只是構造builder。BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);// 啟用屬性占位符處理(如 ${jdbc.url})builder.addPropertyValue("processPropertyPlaceHolders", true);// 注解類過濾:如果設置了annotationClass(如@Mapper),則只掃描帶該注解的接口Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {builder.addPropertyValue("annotationClass", annotationClass);}
// 標記接口過濾:如果設置了 markerInterface, 則只掃描實現該接口的MapperClass<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {builder.addPropertyValue("markerInterface", markerInterface);}
// Bean 名稱生成器Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));}
// 工廠類覆蓋Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);}
// SQL 會話模板引用:支持指定特定的 SqlSessionTemplate BeanString sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}
// SQL 會話工廠引用String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}List<String> basePackages = new ArrayList<>();// 添加 value 屬性值(包路徑)basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
// 添加 basePackages 屬性值basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
// 添加basePackageClasses的包路徑basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
// 使用默認配置類所在包if (basePackages.isEmpty()) {basePackages.add(getDefaultBasePackage(annoMeta));}
// 延遲初始化String lazyInitialization = annoAttrs.getString("lazyInitialization");if (StringUtils.hasText(lazyInitialization)) {builder.addPropertyValue("lazyInitialization", lazyInitialization);}
// Bean 作用域String defaultScope = annoAttrs.getString("defaultScope");if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {builder.addPropertyValue("defaultScope", defaultScope);}
// 設置掃描包路徑(逗號分割)builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));// for spring-nativebuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注冊Bean 定義。上述都是在配置builder, 此處才開始注冊bean definitionregistry.registerBeanDefinition(beanName, builder.getBeanDefinition());}
}
MapperScannerConfigurer 作為BeanDefinitionRegistryPostProcessor執行, 中重寫了 postProcessBeanDefinitionRegistry 方法,最終調用doScan(basePackages);掃描Mapper
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {@Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {// 1. 調用父類掃描方法獲取Bean定義Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {logger.warn("No MyBatis mapper was found...");} else {// 2. 處理掃描到的Bean定義processBeanDefinitions(beanDefinitions);}return beanDefinitions;}
// 省略部分代碼private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {for (BeanDefinitionHolder holder : beanDefinitions) {GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();// 3. 獲取Mapper接口名String mapperClassName = definition.getBeanClassName();// 4. BeanClass 轉換為 MapperFactoryBean// 關鍵步驟1:添加構造參數(原始Mapper接口)definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClassName);try {// 關鍵步驟2:設置 MapperInterface 屬性(Spring Native 兼容)definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));} catch (ClassNotFoundException ignore) {// ignore}// 關鍵步驟3:替換 Bean 類為 MapperFactoryBeandefinition.setBeanClass(this.mapperFactoryBeanClass);// 省略部分SqlSessionFactory 、sqlSessionTemplate相關內容。優先級:SqlSessionTemplate > SqlSessionFactory// 5. 設置自動裝配模式:當未顯示配置工廠/模板時,啟用按類型自動裝配if (!explicitFactoryUsed) {LOGGER.debug("Enabling autowire by type...");definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}}
}
轉換前Bean定義:
BeanDefinition {beanClass = com.example.UserMapper // 原始接口scope = singleton...
}
轉換后 Bean 定義
BeanDefinition {beanClass = MapperFactoryBean // 替換為工廠類constructorArgs = [com.example.UserMapper] // 原始接口作為構造參數properties = {sqlSessionFactory = ref("sqlSessionFactory") // 自動裝配addToConfig = true}...
}
實際效果:
// 用戶代碼
@Autowired
UserMapper userMapper; // 實際發生
MapperFactoryBean factoryBean = new MapperFactoryBean(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory);
UserMapper proxy = factoryBean.getObject(); // 返回 MyBatis 代理
// MapperFactoryBean 的 getObject()
@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface); // 此處就是獲取的代理類對象 }
// 從 @Autowired 到factoryBean.getObject(); 的過程:
@Autowired-> AutowiredAnnotationBeanPostProcessor.postProcessProperties()-> InjectionMetadata.inject()-> DefaultListableBeanFactory.doGetBean()-> AbstractBeanFactory.getObjectForBeanInstance(),發現 MapperFacoryBean 是 UserMapper 類型的工廠-> AbstractBeanFactory..getObjectFromFactoryBean()-> FactoryBeanRegistrySupport.doGetObjectFromFactoryBean()-> FactoryBean<?>.getObject()