9 整合 Mybatis
9 整合 Mybatis
前述
MyBatis 是支持普通 SQL 查询、存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。
9.1 MyBatis 独立使用
MyBatis 独立使用
MySQL 独立使用步骤如下。
- 建立 PO。
- 建立 Mapper。
- 建立配置文件。
- 建立映射文件。
- 建立测试类。
具体使用示例代码见 com.stu.spring.context.chapter09.mybatis.demo.CityMapperTest
。
配置文件说明
明细见 MyBatis 官网配置。
MyBatis 的配置文件配置说明如下。
- configuration:根元素。
- properties:用于配置外在化。
- settings:用于配置 MyBatis 的运行时方式,一些全局性的配置。
- typeAliases:配置类型别名,可以在 xml 中用别名取代全限定名。
- typeHandlers:配置类型处理器,即定义 Java 类型与数据库中的数据类型之间的转换关系。
- objectFactory:用于指定结果集对象的实例是如何创建的。
- plugins:MyBatis 的插件,如配置拦截器,用于拦截 sql 语句的执行等可以修改 MyBatis 内部的运行规则。
- environments:配置数据源信息、连接池、事务属性等。
- transactionManager:事务管理器。
- dataSource:数据源。
- mappers:配置 SQL 映射文件。
- ...
9.2 Spring 整合 MyBatis
Spring 整合 MyBatis
基于 MyBatis 配置上更改,几个变动如下。
- Spring 配置文件。
- MyBatis 配置文件。
- 映射文件(保持不变)。
- 测试。
使用示例代码见 com.stu.spring.context.chapter09.mybatis.springdemo.SpringMybatisTest
。
9.3 源码分析
通过配置文件 chapter09\springMybatisTest.xml,我们知道配置的 bean 是成树状结构的,而在树的最顶层是类型为 org.mybatis.spring.SqlSessionFactoryBean 的 bean,它将其他相关 bean 组装在了一起,我们从这个类开始。
9.3.1 sqlSessionFactory 的创建
SqlSessionFactoryBean 类层次结构
org.mybatis.spring.SqlSessionFactoryBean 类层次结构如下。
SqlSessionFactoryBean
Object (java.lang)
ApplicationListener (org.springframework.context)
EventListener (java.util)
FactoryBean (org.springframework.beans.factory)
InitializingBean (org.springframework.beans.factory)
- InitializingBean:实现此接口的 bean 会在初始化时调用其 afterPropertiesSet() 来进行 bean 的逻辑初始化。
- FactoryBean:一旦某个 bean 实现此接口,那么通过 getBean 方法获取 bean 时其实是获取此类的 getObject() 返回的实例。
1. SqlSessionFactoryBean 的初始化
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 初始化核心逻辑
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
}
buildSqlSessionFactory() 方法不难理解。其可以读配置文件中属性(读取的属性列表可查看源码)等,最终使用 sqlSessionFactoryBuilder 实例解析到的 configuration 创建 SqlSessionFactory 实例。
2.获取 SqlSessionFactoryBean 实例
实现了 FactoryBean,即调用下面方法。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
9.3.2 MapperFactoryBean 的创建
MapperFactoryBean 类层次结构
MyBatis 原始方法示例中的 CityMapper cityMapper = sqlSession.getMapper(CityMapper.class); 以及 Spring MyBatis 中的 CityMapper cityMapper = (CityMapper) context.getBean("cityMapper"); 代码。Spring 其实在 bean 的创建过程中使用了原生的 MyBatis 方法进行了封装。
结合 XML 配置 <bean id="cityMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
,我们分析 MapperFactoryBean 类,它的类层次结构如下。
MapperFactoryBean
SqlSessionDaoSupport (org.mybatis.spring.support)
DaoSupport (org.springframework.dao.support)
Object (java.lang)
InitializingBean (org.springframework.beans.factory)
FactoryBean (org.springframework.beans.factory)
1. MapperFactoryBean 的初始化
public abstract class DaoSupport implements InitializingBean {
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 配置的验证以及 DAO 的初始化工作,核心方法,分析重点
this.checkDaoConfig();
try {
// 模板方法,设计为留给子类做进一步逻辑处理.
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
protected void checkDaoConfig() {
// 父类中对于 sqlSession 不为空的验证,SqlSessionDaoSupport 中实现
super.checkDaoConfig();
// 映射接口的验证
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
// 映射文件存在性验证
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
// 设定 sqlSessionFactory
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
// 父类中对于 sqlSession 不为空的验证
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
关注如下要点。
- 父类中对于 sqlSession 不为空的验证。
- 映射接口的验证。
- 映射文件存在性验证。
2. 获取 MapperFactoryBean 的实例
// 一个 Mapper 对应一个 MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
9.3.3 MapperScannerConfigurer
MapperScannerConfigurer 类层级
一个个的配置 Mapper 映射会很低效,可以配置扫描特定的包,自动成批地创建映射器,大大地减少配置的工作量。
示例代码见 com.stu.spring.context.chapter09.mybatis.springdemo.SpringMybatisTest。
观察下面配置,我们从 MapperScannerConfigurer 类开始分析。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.stu.spring.context.chapter09.mybatis.demo.mapper"></property>
</bean>
MapperScannerConfigurer 类层级如下。
MapperScannerConfigurer
Object (java.lang)
ApplicationContextAware (org.springframework.context)
Aware (org.springframework.beans.factory)
BeanDefinitionRegistryPostProcessor (org.springframework.beans.factory.support)
BeanFactoryPostProcessor (org.springframework.beans.factory.config)
BeanNameAware (org.springframework.beans.factory)
Aware (org.springframework.beans.factory)
InitializingBean (org.springframework.beans.factory)
afterPropertiesSet
实现了 InitializingBean 接口,找到 afterPropertiesSet 代码,里面没有太多逻辑实现。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
}
postProcessBeanFactory
实现了 BeanFactoryPostProcessor 接口,找到 postProcessBeanFactory 代码,里面没有太多逻辑实现。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
}
postProcessBeanDefinitionRegistry
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
/**
* 1. processPropertyPlaceHolders 属性的处理
* BeanDefinitionRegistries 会在应用启动的时候调用,并且会早于 BeanFactoryPostProcessors 的调用,
* 这就意味着 PropertyResourceConfigurers 还没有被加载所有对于属性文件的引用将会失效。
* 为避免此种情况发生,此方法手动地找出定义的 PropertyResourceConfigurers 并进行提前调用以保证对于属性的引用可以正常工作。
*/
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);
// 2. 根据配置属性生成过滤器
scanner.registerFilters();
// 3. 扫描 Java 文件
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
private void processPropertyPlaceHolders() {
// 找到所有已经注册的 PropertyResourceConfigurer 类型的 bean。
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext)
.getBeanFactory().getBeanDefinition(beanName);
// 模拟 Spring 中的环境来用处理器。处理完后 factory 失效
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean);
// 将映射的 bean 注册到环境中来进行后处理器的调用,
for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
}
PropertyValues values = mapperScannerBean.getPropertyValues();
// 调用完成,即找出所有 bean 中应用属性文件的变量并替换
this.basePackage = updatePropertyValue("basePackage", values);
// 将模拟 bean 中相关的属性提取出来应用在真实的 bean 中。
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
}
}
}
1. processPropertyPlaceHolders 属性的处理
使用示例介绍。假定有配置文件 test.properties,其配置内容为 basePackage=com.stu.spring.context.chapter09.mybatis.demo.mapper。
默认由于加载顺序原因,PropertyPlaceholderConfigurer 还没有被调用,即 test.properties 文件中的属性 还没有加载至内存中,Spring 还不能直接使用它。
为了解决这个问题,Spring 提供了 processPropertyPlaceHolders 属性,如下,可以让配置生效。
<!--
...
<value>config/test.properties</value>
...
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="${basePackage}"></property>
<property name="processPropertyPlaceHolders" value="true"></property>
</bean>
processPropertyPlaceHolders 函数主要做的工作如下。
- 找到所有已经注册的 PropertyResourceConfigurer 类型的 bean。
- 模拟 Spring 中的环境来用处理器。
2. 根据配置属性生成过滤器
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 1 对于 annotationClass 属性的处理
if (this.annotationClass != null) {
// 生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是 AnnotationTypeFilter。
// 保证在扫描对应的 Java 文件时只接受标记有注解为 annotationClass 的接口。
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 2 对于 markerInterface 属性的处理
if (this.markerInterface != null) {
// 生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是 AssignableTypeFilter。
// 表示扫描过程中只有实现 markerInterface 接口的接口才会被接受。
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// 3 全局默认处理。在 1 和 2 都不满足时,Spring 会为我们增加一个默认的过滤器 TypeFilter 接口的局部类,旨在接受所有接口文件。
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
// 不扫描 package-info 文件
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
}
3. 扫描 Java 文件
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// 如果配置了 includeAnnotationConfig,则注册对应注解的处理器以保证注解功能的正常使用。
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
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) {
// 扫描 basePackage 路径下的 java 文件
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 解析 scope 属性
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) {
// 如果是 AnnotatedBeanDefinition 类型的 bean,需要检查下常用注解,如 Primary、Lazy 等
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检查当前 bean 是否已经注册
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 如果当前 bean 是用于生成代理的 bean 那么需要进一步处理
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
}
ClassPathMapperScanner.doScan
// 注意下多态调用
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
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;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// 开始构造 MapperFactoryBean 类型的 bean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}
findCandidateComponents
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (FileNotFoundException ex) {
if (traceEnabled) {
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
/**
* 判断当前扫描的文件是否符合要求,而我们之前注册的过滤器信息也正是在此时派上用场
*/
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;
}
}
附录一、SQL 脚本
SQL 脚本
CREATE TABLE city (
id INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50),
age INT
);