1.@MapperScan
常见使用方式,在springboot启动类添加注解@MapperScan配置
@MapperScan(basePackages = "com.xxx.xxx.mapper")
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
@MapperScan注解代码如下, 使用@Import完成mpper对象的扫描注入spring容器
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise
* annotation declarations e.g.:
* {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
* @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
*/
String[] value() default {};
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces
* with at least one method will be registered; concrete classes will be
* ignored.
*/
String[] basePackages() default {};
...
}
@Import的用法参考:spring注解之@Import注解的三种使用方式
1.1 MapperScannerRegistrar
ResourceLoaderAware 获取资源加载器,可以获得外部资源文件
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
...
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 默认添加addIncludeFilter new TypeFilter(){}
scanner.registerFilters();
// 扫描路径,@MapperScan配置的basePackages
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
接下来看ClassPathMapperScanner.doScan,扫描包路径,加载资源
1.2 ClassPathMapperScanner
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类ClassPathBeanDefinitionScanner的doScan扫描方法
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;
}
ClassPathMapperScanner继承ClassPathBeanDefinitionScanner,ClassPathBeanDefinitionScanner.doScan是springIOC容器扫描创建BeanDefinition的地方
ClassPathMapperScanner.doScan(String... basePackages) ->super.doScan(basePackages)//也就是ClassPathBeanDefinitionScanne.doScan(String... basePackages)
1.3 ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner.doScan是springIOC容器扫描创建BeanDefinition的地方
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 查找候选spring bean组件
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历注册bean定义到spring容器
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册bean定义到spring容器
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
1.4 ClassPathScanningCandidateComponentProvider
findCandidateComponents //有两种方式addCandidateComponentsFromIndex和scanCandidateComponents;
/**
* Scan the class path for candidate components.
* @param basePackage the package to check for annotated classes
* @return a corresponding Set of autodetected bean definitions
*/
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
addCandidateComponentsFromIndex是spring5@Indexed 解密相关的,我们主要看scanCandidateComponents;
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// PathMatchingResourcePatternResolver扫描路径资源
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 {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 这里对扫描的资源进行过滤匹配
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
...
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);//PathMatchingResourcePatternResolver扫描路径资源
扫描到资源,接下来要对资源进行过滤匹配,也就是找到我们@Mapper注解标记的类
isCandidateComponent 方法是对扫描的资源进行过滤匹配
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
// 过滤器匹配资源
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
最后通过ClassPathMapperScanner.registerFilters();默认添加的new TypeFilter(){},加载扫描到的资源
addIncludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } });
1.5 ClassPathMapperScanner.processBeanDefinitions
回到ClassPathMapperScanner.doScan扫描加载到资源注册到spring IOC,接着执行processBeanDefinitions,BeanDefinition的后置处理
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 设置MapperFactoryBean代理mapper接口
definition.setBeanClass(this.mapperFactoryBean.getClass());
...
}
}
// 设置MapperFactoryBean工厂bean代理替换mapper接口
definition.setBeanClass(this.mapperFactoryBean.getClass());
MapperFactoryBean是用来创建MyBatis Mapper对象的,MapperFactoryBean也实现了FactoryBean接口,间接实现InitializingBean接口
到此,完成mapper接口对象的扫描注入
2.MybatisAutoConfiguration自动装配,mapper接口加@Mapper
2.1 MapperScannerRegistrarNotFoundConfiguration
在MybatisAutoConfiguration有个内部类MapperScannerRegistrarNotFoundConfiguration
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
加载条件@ConditionalOnMissingBean(MapperFactoryBean.class)也就是没有用第一种@MapperScan
当满足加载条件,同时加载@Import({ AutoConfiguredMapperScannerRegistrar.class })->也就是MapperScannerRegistrarNotFoundConfiguration的内部类AutoConfiguredMapperScannerRegistrar.class
2.2 AutoConfiguredMapperScannerRegistrar
实现ImportBeanDefinitionRegistrar接口的方法,在加载AutoConfiguredMapperScannerRegistrar调用,具体参考@Import加载导入bean到spring ioc的流程
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logger.debug("Searching for mappers annotated with @Mapper");
// 创建mapper扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
// 获取扫描路径,也就是springboot启动类包名 具体参照springboot启动过程
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
// 添加扫描注解类@Mapper
scanner.setAnnotationClass(Mapper.class);
// 接上一步,添加扫描过滤器addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
scanner.registerFilters();
// 扫描路径
scanner.doScan(StringUtils.toStringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}
2.3 ClassPathMapperScanner
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类ClassPathBeanDefinitionScanner的doScan扫描方法
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;
}
ClassPathMapperScanner继承ClassPathBeanDefinitionScanner,ClassPathBeanDefinitionScanner.doScan是springIOC容器扫描创建BeanDefinition的地方
ClassPathMapperScanner.doScan(String... basePackages) ->super.doScan(basePackages)//也就是ClassPathBeanDefinitionScanne.doScan(String... basePackages)
2.4 ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner.doScan是springIOC容器扫描创建BeanDefinition的地方
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 查找候选spring bean组件
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历注册bean定义到spring容器
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册bean定义到spring容器
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
2.5 ClassPathScanningCandidateComponentProvider
findCandidateComponents //有两种方式addCandidateComponentsFromIndex和scanCandidateComponents;
/**
* Scan the class path for candidate components.
* @param basePackage the package to check for annotated classes
* @return a corresponding Set of autodetected bean definitions
*/
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
addCandidateComponentsFromIndex是spring5@Indexed 解密相关的,我们主要看scanCandidateComponents;
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// PathMatchingResourcePatternResolver扫描路径资源
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 {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 这里对扫描的资源进行过滤匹配
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
...
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);//PathMatchingResourcePatternResolver扫描路径资源
扫描到资源,接下来要对资源进行过滤匹配,也就是找到我们@Mapper注解标记的类
isCandidateComponent 方法是对扫描的资源进行过滤匹配
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
// 过滤器匹配资源
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
最后通过AbstractTypeHierarchyTraversingFilter.match -> AnnotationTypeFilter.matchSelf 这里匹配加载的资源类有没有我们在ClassPathMapperScanner.setAnnotationClass(Mapper.class)的 注解
2.6 ClassPathMapperScanner.processBeanDefinitions
回到ClassPathMapperScanner.doScan扫描加载到资源注册到spring IOC,接着执行processBeanDefinitions,BeanDefinition的后置处理
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 设置MapperFactoryBean代理mapper接口
definition.setBeanClass(this.mapperFactoryBean.getClass());
...
}
}
// 设置MapperFactoryBean工厂bean代理替换mapper接口
definition.setBeanClass(this.mapperFactoryBean.getClass());MapperFactoryBean是用来创建MyBatis Mapper对象的,MapperFactoryBean也实现了FactoryBean接口,间接实现InitializingBean接口
到此,完成mapper接口对象的扫描注入
3.MapperScannerConfigurer
BeanDefinitionRegistryPostProcessor
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;
/**
* This property lets you set the base package for your mapper interface files.
* <p>
* You can set more than one package by using a semicolon or comma as a separator.
* <p>
* Mappers will be searched for recursively starting in the specified package(s).
*
* @param basePackage base package name
*/
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
/**
* Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
*
* @param addToConfig
* @see MapperFactoryBean#setAddToConfig(boolean)
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the
* specified annotation.
* <p>
* Note this can be combined with markerInterface.
*
* @param annotationClass annotation class
*/
public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
}
/**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the
* specified interface class as a parent.
* <p>
* Note this can be combined with annotationClass.
*
* @param superClass parent class
*/
public void setMarkerInterface(Class<?> superClass) {
this.markerInterface = superClass;
}
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
* <p>
* @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
*
* @param sqlSessionTemplate
*/
@Deprecated
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
* <p>
* Note bean names are used, not bean references. This is because the scanner
* loads early during the start process and it is too early to build mybatis
* object instances.
*
* @since 1.1.0
*
* @param sqlSessionTemplateName Bean name of the {@code SqlSessionTemplate}
*/
public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
}
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
* <p>
* @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
*
* @param sqlSessionFactory
*/
@Deprecated
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
* <p>
* Note bean names are used, not bean references. This is because the scanner
* loads early during the start process and it is too early to build mybatis
* object instances.
*
* @since 1.1.0
*
* @param sqlSessionFactoryName Bean name of the {@code SqlSessionFactory}
*/
public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
}
/**
*
* @since 1.1.1
*
* @param processPropertyPlaceHolders
*/
public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
this.processPropertyPlaceHolders = processPropertyPlaceHolders;
}
/**
* {@inheritDoc}
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* {@inheritDoc}
*/
@Override
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Gets beanNameGenerator to be used while running the scanner.
*
* @return the beanNameGenerator BeanNameGenerator that has been configured
* @since 1.2.0
*/
public BeanNameGenerator getNameGenerator() {
return nameGenerator;
}
/**
* Sets beanNameGenerator to be used while running the scanner.
*
* @param nameGenerator the beanNameGenerator to set
* @since 1.2.0
*/
public void setNameGenerator(BeanNameGenerator nameGenerator) {
this.nameGenerator = nameGenerator;
}
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
/**
* {@inheritDoc}
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
/**
* {@inheritDoc}
*
* @since 1.0.2
*/
@Override
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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
/*
* 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);
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 = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
}
}
private String updatePropertyValue(String propertyName, PropertyValues values) {
PropertyValue property = values.getPropertyValue(propertyName);
if (property == null) {
return null;
}
Object value = property.getValue();
if (value == null) {
return null;
} else if (value instanceof String) {
return value.toString();
} else if (value instanceof TypedStringValue) {
return ((TypedStringValue) value).getValue();
} else {
return null;
}
}
}