group : com.baomidou
version:3.5.2.2-SNAPSHOT
baomidou官網可以從快速開始了解到,除了配置數據源,最重要的就是@MapperScan
注解,在 Spring Boot 啟動類中添加 @MapperScan
注解,掃描 Mapper 文件夾。
@MapperScan
按照慣例,先看注釋。在用java config的方式的時候使用@MapperScan注解來注冊Mybatis mapper接口。然后他給了一個配置的示例。
@Configuration@MapperScan("org.mybatis.spring.sample.mapper")public class AppConfig {@Beanpublic DataSource dataSource() {return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();}@Beanpublic DataSourceTransactionManager transactionManager() {return new DataSourceTransactionManager(dataSource());}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource());return sessionFactory.getObject();}}
然后我們看下里面具體有些什么屬性,大概了解一下。需要注意的是這個注解中@Import了MapperScannerRegistrar.class。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;String sqlSessionTemplateRef() default "";String sqlSessionFactoryRef() default "";Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;String lazyInitialization() default "";String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
-
value()
: 指定要掃描的包路徑,用于指定需要掃描的包。可以指定多個包路徑。basePackages() 屬性的別名。 允許更簡潔的注釋聲明,例如:@MapperScan(“org.my.pkg”) 而不是 @MapperScan(basePackages = “org.my.pkg”)}。 -
basePackages()
: 同樣是指定要掃描的包路徑,與value()
屬性作用相同,可以指定多個包路徑。Mybatis接口的掃描包路徑。 -
basePackageClasses()
: 指定要掃描的類,可以通過指定類來確定要掃描的包路徑。可以指定多個類。basePackages的類型安全替代方法,用于指定要掃描components注解的包。將掃描每個指定類的包。 -
nameGenerator()
: 指定自定義的 Bean 名稱生成器,用于生成掃描到的 Mapper 接口對應的 Bean 的名稱。 -
annotationClass()
: 指定自定義的注解類型,用于標識被掃描的 Mapper 接口。默認為Annotation.class
,即不指定注解。 -
markerInterface()
: 指定一個標記接口,用于限定被掃描的 Mapper 接口必須繼承該標記接口。 -
sqlSessionTemplateRef()
: 指定一個注入的SqlSessionTemplate
Bean 的名稱,用于指定要使用的SqlSessionTemplate
。 -
sqlSessionFactoryRef()
: 指定一個注入的SqlSessionFactory
Bean 的名稱,用于指定要使用的SqlSessionFactory
。 -
factoryBean()
: 指定一個自定義的MapperFactoryBean
類型,用于創建 Mapper 接口對應的 Bean 實例。 -
lazyInitialization()
: 指定是否啟用延遲初始化。默認為空字符串,表示不啟用延遲初始化。 -
defaultScope()
: 指定掃描到的 Mapper 接口對應的 Bean 的默認作用域。默認為AbstractBeanDefinition.SCOPE_DEFAULT
。
@Import(MapperScannerRegistrar.class)
引入了MapperScannerRegistrar,實現了ImportBeanDefinitionRegistrar。也就是說MapperScannerRegistrar的registerBeanDefinitions方法將會被執行。
spring專題有描述過ImportBeanDefinitionRegistrar的時機在invokeBeanFactoryPostProcessors階段,有興趣的朋友們也可以關注下。下面就是MapperScannerRegistrar的核心代碼。
@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,BeanDefinitionRegistry registry, String beanName) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {builder.addPropertyValue("annotationClass", annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {builder.addPropertyValue("markerInterface", markerInterface);}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);}String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}List<String> basePackages = new ArrayList<>();basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));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);}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);registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}
此段源碼其實非常簡單,就是讀取注解信息,然后注冊了一個MapperScannerConfigurer類的BeanDefinition。
MapperScannerConfigurer
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor、InitializingBean、ApplicationContextAware、BeanNameAware。比較重要的是BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry方法將在invokeBeanFactoryPostProcessors中的后續被執行。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}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));}if (StringUtils.hasText(defaultScope)) {scanner.setDefaultScope(defaultScope);}scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}
源碼分析:
this.processPropertyPlaceHolders這個屬性是在創建的時候設置的,默認就是true
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);
所以processPropertyPlaceHolders()方法默認的時候一定會執行的。
首先看注釋,BeanDefinitionRegistries在應用啟動早期被調用,且早于BeanFactoryPostProcessors。這就意味著PropertyResourceConfigurer將不會被加載,因為其實現了BeanFactoryPostProcessor;所以這個類的屬性將會加載失敗,為了避免這類情況,所以在此處找到所有PropertyResourceConfigurers的定義信息,且運行他們的postProcessBeanFactory方法并更新他們的值。
/** BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that* PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will* fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean* definition. Then update the values.*/private void processPropertyPlaceHolders() {Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,false, false);if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBeanDefinition(beanName);// PropertyResourceConfigurer does not expose any methods to explicitly perform// property placeholder substitution. Instead, create a BeanFactory that just// contains this mapper scanner and post process the factory.DefaultListableBeanFactory factory = new DefaultListableBeanFactory();factory.registerBeanDefinition(beanName, mapperScannerBean);for (PropertyResourceConfigurer prc : prcs.values()) {prc.postProcessBeanFactory(factory);}PropertyValues values = mapperScannerBean.getPropertyValues();this.basePackage = getPropertyValue("basePackage", values);this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);this.lazyInitialization = getPropertyValue("lazyInitialization", values);this.defaultScope = getPropertyValue("defaultScope", values);}this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders).orElse(null);this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);}
這個方法的作用是確保在應用啟動時,處理所有的屬性占位符,使得配置的值能夠正確的諸如到當前類的屬性中。這對于需要在應用程序啟動時讀取配置信息并進行相應處理的情況非常有用。
在方法內部創建了ClassPathMapperScanner對象。并設置了屬性值,注冊了過濾器(包含過濾器和排除過濾器),執行了scan方法。
package-info結尾的類名將會被排除加載。默認所有的類都會被加載。
ClassPathMapperScanner
ClassPathMapperScanner類繼承了ClassPathBeanDefinitionScanner類。
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
所以此處將會調用父類ClassPathBeanDefinitionScanner的Scan方法。
public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();doScan(basePackages);// Register annotation config processors, if necessary.if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);}
ClassPathBeanDefinitionScanner的scan方法會執行doScan方法,也就是子類的實現。
ClassPathMapperScanner#doScan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {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 {processBeanDefinitions(beanDefinitions);}return beanDefinitions;
}
然后執行父類的doscan方法,返回的是BeanDefinitionHolder集合,也就是把包路徑中的類掃描注冊到容器中。
如果掃描為空,就提示一下,如果返回有值的話就進入processBeanDefinitions方法。這個就比較重要了。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {AbstractBeanDefinition definition;BeanDefinitionRegistry registry = getRegistry();for (BeanDefinitionHolder holder : beanDefinitions) {definition = (AbstractBeanDefinition) holder.getBeanDefinition();boolean scopedProxy = false;if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {definition = (AbstractBeanDefinition) Optional.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));scopedProxy = true;}String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName+ "' mapperInterface");// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59try {// for spring-nativedefinition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));} catch (ClassNotFoundException ignore) {// ignore}definition.setBeanClass(this.mapperFactoryBeanClass);definition.getPropertyValues().add("addToConfig", this.addToConfig);// Attribute for MockitoPostProcessor// https://github.com/mybatis/spring-boot-starter/issues/475definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);boolean explicitFactoryUsed = false;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;}if (!explicitFactoryUsed) {LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}definition.setLazyInit(lazyInitialization);if (scopedProxy) {continue;}if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {definition.setScope(defaultScope);}if (!definition.isSingleton()) {BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {registry.removeBeanDefinition(proxyHolder.getBeanName());}registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());}}
}
代碼非常長哈,里面也就一些issue的記錄注釋,有興趣的可以看一看,拓展一下。
其實里面做的事情就是BeanDefinition的屬性設置,需要注意的是掃描的這些Mapper類的BeanDefinition的beanClass都是MapperFactoryBean.class。因為我們使用的Mapper類都是接口。
寫在最后
@MapperScan注解,實際上做的事情就是掃描Mapper類到容器中。引入的類也是為了加載和處理Mapper類。但是除了@MapperScan之外有個一個SPI機制引入的類MybatisPlusAutoConfiguration,將在下一章進行分析。