SpringBoot整合MyBatis源码分析(二)

555 阅读5分钟

1:在上一篇文章中已经了解了SpringBoot是如何将我们的mapper层接口生成了代理类,所以今天就看下第二部分,SpringBoot如何解析MyBatis的sql的xml文件

2:首先还得从MyBatis的starter开始说起,我们在使用Mybatis的时候需要引入一个MyBatis的starter的依赖,我们可以查看spring.factories文件,然后找到MybatisAutoConfiguration这个类,所以这个类就是我们开始解析的起点。至于为什么??大家可以去看下juejin.cn/post/689443…这篇文章详细记录了如何自定义一个starter,所以我们进入MybatisAutoConfiguration这个类这个类

3:MybatisAutoConfiguration这个类

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
//重点关注下这个注解,也就是属性源配置,我们配置MyBatis的配置都是要参照这个属性源的,可以进去看看
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
    private final MybatisProperties properties;
    private final Interceptor[] interceptors;
    private final TypeHandler[] typeHandlers;
    private final LanguageDriver[] languageDrivers;
    private final ResourceLoader resourceLoader;
    private final DatabaseIdProvider databaseIdProvider;
    private final List<ConfigurationCustomizer> configurationCustomizers;
    

4:查看MyBatis的属性源配置类,SpringBoot会去解析以 "mybatis" 为前缀的属性配置信息,当然包括我们需要配置的sql的配置文件路径,也就是mapperLocations

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private Class<?> typeAliasesSuperType;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
    private Properties configurationProperties;
    @NestedConfigurationProperty
    private Configuration configuration;
    

5:有人会问,MyBatis的确是配置了MybatisAutoConfiguration这个类,那SpringBoot为什么就会解析它呢??

6:解析MybatisAutoConfiguration这个类原理,在SpringBoot启动类上的注解@SpringBootApplication中有一个@EnableAutoConfiguration,在这个注解中有@Import(AutoConfigurationImportSelector.class)如图,相信看过我前面几篇文章的人就会知道对于@Import注解中的类SpringBoot会进行处理,但是它处理的时间与@MapperScan中@Import处理时间不同

7:处理@SpringBootApplication注解中@Import中的类时机

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
        //AutoConfigurationImportSelector.class处理的时机是在这个方法,我们可以debug看看,
        //可以在AutoConfigurationImportSelector这个类的protected AutoConfigurationEntry getAutoConfigurationEntry(
		AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {}
        //这个方法打上一个端点,会发现最终会执行到这个方法
        this.deferredImportSelectorHandler.process();
    }
    

8:执行AutoConfigurationImportSelector类的getAutoConfigurationEntry()方法

protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //这个方法就是去获取所有在META-INF目录下的spring.factories文件,并获取到我们在文件中配置的属性类的全路径,并初始化他们,是在SpringFactoriesLoader类的loadSpringFactories方法中获取的,可以在下图看到,解析出来之后的确是有MyBatisAutoConfiguration这个配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        
        
        //得到这了配置类并不一定会去解析,需要进行一遍过滤,过滤的原理就是在这些配置上有各种的@Condition注解
        //如果条件都满足才会解析这些配置类,至于@Condition注解的原理就是实现Condition这个接口,并实现matches这个方法,如果满足
        //就会返回true,如果不满足就会返回false,关于@Condition注解原理在我主页有一篇文章已经详细介绍过了,
        //并且还自定义过一个Condition注解,这里就不详细说了
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }	
    

9:经过过滤之后我们可以发现MyBatisAutoConfiguration这个配置类是符合@Condition条件的,所以SpringBoot会去解析它

@Configuration
//SpringBoot会去解析它,说明了 SqlSessionFactoryBean.class 这个类是存在的,我们进入到这个类中去
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {

10 进入 SqlSessionFactoryBean.class,发现它实现了InitializingBean这个接口,实现了这个接口那么就一定会执行afterPropertiesSet()方法,所以我们直接进入到这个方法中去

 public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
 
 

11:执行afterPropertiesSet方法

public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        //核心方法
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }
    
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        //中间省略了很多的代码,可以直接来到这里,这里的mapperLocations 就是我们在配置文件中配置好的sql的
        //xml配置文件路径,所以这里不会为空
        if (this.mapperLocations != null) {
            Resource[] var3 = this.mapperLocations;
                int var4 = var3.length;

                for(int var5 = 0; var5 < var4; ++var5) {
                    Resource mapperLocation = var3[var5];
                    if (mapperLocation != null) {
                        try {
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                            //核心方法,在这里就开始解析我们的sql的配置文件了,解析就是Mybatis源码相关的了,所以这里就此打住了                           
                            xmlMapperBuilder.parse();
                        } catch (Exception var19) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                        } finally {
                            ErrorContext.instance().reset();
                        }


                    }
                }
        } 

        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

12:SpringBoot解析MyBatis配置文件到解析sql的xml配置文件大致流程就是这样,大家可以自行debug查看更加细致的流程。MyBatis解析之后就会将我们的sql语句解析成各种形式的SqlNode,然后会封装成一个SqlSource,最终会被分装到一个MapperStatement对象,而当我们执行到mapper层接口的方法之后,就会根据指定的key去找到对应的MapperStatement对象,拿到对应的SqlSource里面的sql语句,并执行。

13:SpringBoot整合MyBatis源码就分析到这里啦,如果文章有错误的话欢迎大家指出,我是个小白,不要喷我写的low哈