上期中我们主要学习了Spring的动态bean注册,其中的主要接口是ImportBeanDefinitionRegistrar,在文中我们还主要学习接口的上游做了哪些事情。今天我们主要通过mybatis的mapper管理来学习一下该接口的下游方法调用过程。据此也尝试搞清楚mybatis的mapper的管理过程。
对此我们就从registerBeanDefinitions方法看起。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//拿到具体的注解并转化成成annotationAttributes
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//实例化一个自定义的扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
//获取注解元素annotationClass
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
//获取要被扫描过程检测的接口,由markerinterface元素指定。
Class markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
//设置类名生成器
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
}
//设置mapper管理器,会将扫描的mapper类放到核心配置类configuration中
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
//设置sqlsessionTemplate的名称
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
//设置sqlsessionfactory的名称
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
//获取要扫描的包名称
String[] var10 = annoAttrs.getStringArray("value");
int var11 = var10.length;
int var12;
String pkg;
for(var12 = 0; var12 < var11; ++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//从beasePackages中获取要扫描的包名
var10 = annoAttrs.getStringArray("basePackages");
var11 = var10.length;
for(var12 = 0; var12 < var11; ++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
//从basePackageClasses中获取要扫描的类的全路径,然后获取要扫描的包名
Class[] var15 = annoAttrs.getClassArray("basePackageClasses");
var11 = var15.length;
for(var12 = 0; var12 < var11; ++var12) {
Class clazz = var15[var12];
basePackages.add(ClassUtils.getPackageName(clazz));
}
String mapperHelperRef = annoAttrs.getString("mapperHelperRef");
String[] properties = annoAttrs.getStringArray("properties");
if (StringUtils.hasText(mapperHelperRef)) {
scanner.setMapperHelperBeanName(mapperHelperRef);
} else if (properties != null && properties.length > 0) {
scanner.setMapperProperties(properties);
} else {
try {
scanner.setMapperProperties(this.environment);
} catch (Exception var14) {
LOGGER.warn("只有 Spring Boot 环境中可以通过 Environment(配置文件,环境变量,运行参数等方式) 配置通用 Mapper,其他环境请通过 @MapperScan 注解中的 mapperHelperRef 或 properties 参数进行配置!如果你使用 tk.mybatis.mapper.session.Configuration 配置的通用 Mapper,你可以忽略该错误!", var14);
}
}
//设置可以被扫描到的标志
scanner.registerFilters();
//进行for循环遍历
scanner.doScan(StringUtils.toStringArray(basePackages));
}
在registerfilters方法中,就是通过设置需要被扫描的类的一些标记信息。要么是注解,要么是接口。
public void registerFilters() {
boolean acceptAllInterfaces = true;
if (this.annotationClass != null) {
//设置需要被扫描的注解,通过上面的注解上的元素设定
this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
//设置需要被拦截的接口
if (this.markerInterface != null) {
this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
//是否拦截所有的接口
if (acceptAllInterfaces) {
this.addIncludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
//不需要被拦截的类型
this.addExcludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info") ? true : metadataReader.getAnnotationMetadata().hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
}
});
}
通过上述两个步骤,我们知道mybatis先是通过@mapperscan注解设置一些值,然后通过设置需要被拦截的类的一些基本信息。然后就要开始for循环逐个扫描包路径了。
public SetdoScan(String... basePackages) {
//直接进行扫描,将符合条件的类beandefinition信息进行返回
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//对符合条件的bean进行一些填充
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
寻找符合条件的bean。
public SetfindCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
//根据设置的拦截信息进行判断
private boolean indexSupportsIncludeFilters() {
for (TypeFilter includeFilter : this.includeFilters) {
if (!indexSupportsIncludeFilter(includeFilter)) {
return false;
}
}
return true;
}
private boolean indexSupportsIncludeFilter(TypeFilter filter) {
if (filter instanceof AnnotationTypeFilter) {
Class annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
annotation.getName().startsWith("javax."));
}
if (filter instanceof AssignableTypeFilter) {
Class target = ((AssignableTypeFilter) filter).getTargetType();
return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
}
return false;
}
//这块判断是接口哦,他可以扫描接口
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
通过上述对bean是否符合条件进行判断之后,就开始对扫描到的bean,也就是我们的mapper进行处理了。如下所示:
private void processBeanDefinitions(SetbeanDefinitions) {
Iterator var3 = beanDefinitions.iterator();
while(var3.hasNext()) {
//拿到具体的mapper
BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
//设置构造函数
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//设置该bean的name,mapperfactoryBean。这里设置主要是为了注入到mapperfacatorybean中
definition.setBeanClass(this.mapperFactoryBean.getClass());
if (StringUtils.hasText(this.mapperHelperBeanName)) {
definition.getPropertyValues().add("mapperHelper", new RuntimeBeanReference(this.mapperHelperBeanName));
} else {
if (this.mapperHelper == null) {
this.mapperHelper = new MapperHelper();
}
definition.getPropertyValues().add("mapperHelper", this.mapperHelper);
}
//设置addToConfig属性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
//sqlSessionFactory,设置sqlSessionFactory类
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
//设置sqlSessionTemplate类名称
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
this.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) {
this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(2);
}
}
我们通过查看MapperFactoryBean的构造函数发现如下:
通过上述操作,我们知道我们的的mapper已经进入beandefinition中了,也就是mybatis需要的哪些接口。通过上述的一些分析。接口扫描和注册基本已经完成。现在就成了spring的整合问题。为此有了mybatis-spring的jar包用来整合。
sqlSessionFactoryBean类实现了接口initalizingbean。我们先看看这个接口做了哪些事情。
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener;
我们看一下具体的实现
@Override
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");
//创建sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
//xml解析
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
//设置xml路径等信息
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
//建一个解析器
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType)
.forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream()
.filter(clazz -> !clazz.isInterface())
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
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();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
//开始解析了
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
//进行xml转类
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//将转出来的类转变为实体
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析xml的
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//在config中添加到configuration
configuration.addMapper(boundType);
}
}
}
在addMapper方法中
public void addMapper(Classtype) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//创建一个代理类
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//缓存起来
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
这里的parse方法执行结束之后,我们的xml就进入knownMappers 保存。
Map, MapperProxyFactory> knownMappers = new HashMap<>();
但是这个addmapper又是在哪里调用的?
我们发现在MapperFactoryBean中有相关的操作。但是没有发现是谁调用了checkDaoConfig接口,所有去父类看一下。
最后在父类中找到了initalizingBean的接口。于是有和spring有了关系。
public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
public DaoSupport() {
}
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
//这里进行了调用用
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
protected abstract void checkDaoConfig() throws IllegalArgumentException;
protected void initDao() throws Exception {
}
}
而这里的mapperInterface则是我们spring接口扫描的结果。我们还看到在addmapper方法的时候有判断这个接口对应的xml是否已经创建成功了。
同时,我们也发现MapperFactoryBean类也实现了FactoryBean,也就是这里的getObject方法。我们以前学习FactoryBean的时候说如果spring的getBean在内部找不到具体的类的话,就会从其他实现了FactoryBean的接口中获取。那么就这也就是我们开发中使用注解@Autowired注入了一个接口的原因所在。
通过对MapperFactoryBean的学习,我们发现这才是最后的大boss,除此那么既然发生了聚合,那么肯定需要一些基础的东西。于是我们发现
也就是说MapperFactoryBean的时候需要SqlSessionFactory,然后通过sqlsessionFactory创建SqlSessionTemplate。最终调用的还是sqlSessionfactionbean中的configuration,configuration又调用了mapperRegistry。而最终也是通过JDK代理实现的。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxymapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxymapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
通过上述的分析,我们大概得可以知道mybatis的过程。首先通过spring接口扫描,然后再将扫描到的接口注入到mapperfactorybean中。而sqlSessionFactoryBean也是和spring相关系的,其通过配置文件找到需要解析的xml文件,然后实例化成代理类,并交由knownMappers进行管理。当mapperfactorybean初始化的时候,将扫描到的接口和xml解析的具体实体进行一一对应起来并放入到mapperRegistry中,等待spring注入的时候通过getBean方法进行实例化。能够注入的原因是mapperfactorybean实现了factoryBean接口。
最后我们大概得说一下,这些流程中的关键类,mapperfactorybean是主要的类。其中聚合sqlsessiontemplate,sqlsessiontemplate是从sqlsessionfactory创建而来。sqlsessionfactory又是从sqlsessionfactorybean中而来,sqlsessionfactorybean主要是解析xml文件并将其保存到knownMappers中的。
至此mybatis的主体逻辑大概想清楚了,那么sql的执行过程又是怎么样的?我们下期再看这个问题吧!
参考文献:
https://www.cnblogs.com/hei12138/p/mybatis-spring.html